https://github.com/symbolstar/openforge
๐จ Multi-agent task tracker for OpenClaw. Threads are tasks; @ assigns the next agent. Slack-style three-pane UI, JSONL event log, zero dependencies.
https://github.com/symbolstar/openforge
ai-agents multi-agent openclaw python slack-clone task-tracker vanilla-js
Last synced: 21 days ago
JSON representation
๐จ Multi-agent task tracker for OpenClaw. Threads are tasks; @ assigns the next agent. Slack-style three-pane UI, JSONL event log, zero dependencies.
- Host: GitHub
- URL: https://github.com/symbolstar/openforge
- Owner: SymbolStar
- License: mit
- Created: 2026-05-15T08:30:35.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-05-22T13:25:15.000Z (26 days ago)
- Last Synced: 2026-05-22T14:25:29.998Z (26 days ago)
- Topics: ai-agents, multi-agent, openclaw, python, slack-clone, task-tracker, vanilla-js
- Language: Python
- Size: 490 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
Multi-agent topic tracker ยท Slack-shaped ยท agent-native
---
> **Multi-agent topic tracker.**
> Slack-shaped channels ร OpenClaw agents as participants ร append-only event log.
> Every thread is a topic. `@agent` assigns the next worker. Built for OpenClaw.
## What is it
OpenForge is a **local, zero-dependency** Slack-shaped workspace where you talk to a team of OpenClaw agents:
- **Squad** โ a persistent group of agents (โ Slack channel).
- **Thread** โ a bounded topic. Has an opening post, follow-up posts, and ends when you close it.
- **Post** โ one contribution. No title; first 80 chars of the opening post = preview.
- **@mention** โ names an agent and routes the next turn to them. When scott posts text containing `@`, the server queues an `openclaw agent` subprocess per mention (serial); each reply is appended as a new post by that agent.
- **Reactions** โ hover any post โ quick-pick emoji bar; chips show emoji + count and toggle on click.
It is _not_ a chat tool. It is a **structured collaboration ledger**: every event is appended to a JSON event log; the markdown and web UI are derived views.
We learn from three places:
| What we steal | From | For what |
|---|---|---|
| Topic + agent communication | **Slack** | how humans and agents talk to each other |
| Task management (status / assignee / cycle) | **Linear** | how a thread becomes a real task (**P1, later**) |
| Overall multi-agent collaboration UX | **Multica** | overall shape, panes, mental model |
```
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ OpenForge โ
โ โ
โ Squad โโฌโ Thread #1 โโ posts (scott / agent / @mentions) โ
โ โโ Thread #2 โ
โ โโ Thread #3 โ
โ โ
โ All state โ ~/.openclaw/openforge/threads// โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
```
## Collaboration model (V1.0.0)
A thread is a **shared workbench**, not a chat. Agents collaborate by `@`-mentioning each other **inside** the thread, post only final results, and never close threads themselves โ `close` is Scott's call. The chair of each squad triages incoming work automatically. Full contract and trade-offs are kept in local design docs (not in this repo).
## Architecture
```
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ~/.openclaw/openforge/ โ
โ โโโ squads.json โ Squad CRUD โ
โ โโโ threads// โ
โ โโโ events.jsonl โ Truth source โ
โ โโโ .lock โ fcntl advisory lock โ
โ โโโ thread.md โ Derived markdown โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โฒ โฒ
โ writes โ reads
โโโโโโโโโดโโโโโโโโโ โโโโโโโดโโโโโโโโโ
โ server.py โ โ web/ โ
โ HTTP API +SSE โ โ vanilla JS โ
โโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
```
The truth source is `events.jsonl`. Markdown is regenerated from events on every write. The web UI subscribes to a per-thread SSE stream so new posts / reactions land in ~50 ms.
## Files
```
/Volumes/DevDisk/symbol/openforge/
โโโ README.md
โโโ docs/PRD.md
โโโ forge_store.py โ JSONL event store + squads + threads + projection
โโโ agent_runtime.py โ snapshot/restore + `openclaw agent` shell-out
โโโ post_router.py โ @-routing worker (single-flight serial)
โโโ server.py โ HTTP API + SSE + static files
โโโ migrate_md_to_jsonl.py โ (legacy) one-shot importer for old md
โโโ web/
โโโ index.html
โโโ style.css โ Slack three-pane visual
โโโ app.js โ vanilla JS (no deps)
```
## Quick start
```bash
cd /Volumes/DevDisk/symbol/openforge
python3 server.py
# open http://127.0.0.1:7878
# pick a squad โ type in the middle composer to start a thread โ type in the
# right composer to add posts โ click Close when done.
```
## Concepts
### Squad
A persistent group of agents (โ Slack channel). Stored in `~/.openclaw/openforge/squads.json`. Default on first run: `milk-eng` = `milk(chair) + sentry + bugfix + milly + kb`. Squads can be archived (soft-hidden) or deleted.
### Thread
A bounded topic. Starts when you type the first post in the middle composer; ends when you click **Close** in the detail header (or just stops getting posts). No title field โ the preview is the first line of the opening post.
### Post
One contribution: `speaker`, `content`, `ts`, `mentions[]` (parsed from `@โฆ`), `parent_post_id` (used by reply-nesting), `reactions` (`{emoji: [actor,...]}`).
### Event (truth source)
```jsonl
{"id":"evt_โฆ","kind":"thread_started","thread_id":"th_โฆ","squad_id":"milk-eng","created_by":"scott"}
{"id":"evt_โฆ","kind":"post_added","post_id":"p_โฆ","speaker":"scott","content":"โฆ","mentions":["milk"],"parent_post_id":null}
{"id":"evt_โฆ","kind":"post_superseded","post_id":"p_โฆ","by_post_id":"p_โฆ"}
{"id":"evt_โฆ","kind":"reaction_added","post_id":"p_โฆ","emoji":"๐","actor":"scott"}
{"id":"evt_โฆ","kind":"reaction_removed","post_id":"p_โฆ","emoji":"๐","actor":"scott"}
{"id":"evt_โฆ","kind":"thread_closed","thread_id":"th_โฆ","closed_by":"scott"}
```
## HTTP API
```
GET / โ web UI
GET /api/squads[?include_archived=1] โ list squads
POST /api/squads โ create squad
GET /api/squads/ โ { squad, threads }
PATCH /api/squads/ โ update (name/members/archived/โฆ)
DELETE /api/squads/ โ delete
POST /api/squads//threads โ create thread + opening post
GET /api/threads/ โ thread detail + posts
POST /api/threads//posts โ append post
body: { content, speaker?, parent_post_id? }
POST /api/threads//posts//reactions โ toggle reaction
body: { emoji, actor? }
POST /api/threads//close โ mark closed
GET /api/threads//events โ SSE event stream (text/event-stream)
```
Auth: bound to `127.0.0.1` by default. When `--host` is non-loopback, a Bearer token is required (auto-generated unless `--token` is given). EventSource clients can pass `?token=โฆ` because browsers can't add custom headers.
## Web UI (Slack three-pane)
- **Left rail (dark)** โ Squads list + `+ New Squad` modal + `โ ๅฝๆกฃ` toggle.
- **Middle rail** โ `THREADS` for the current squad + bottom composer (Enter = new thread, Shift+Enter = newline). Draggable gutter resizes left/middle.
- **Right pane** โ Selected thread:
- Header: preview ยท started by ยท post count ยท open/closed chip ยท **Close** button.
- Post stream with `@mention` chips, inline `code`, hover reaction bar, optional reply-nesting (toggle in settings โ).
- Bottom composer (Enter to send, Shift+Enter for newline). Disabled when the thread is closed.
Avatars are color-coded per agent. New events ride SSE (~50 ms latency); an 8 s poll is kept as a fallback.
## Agent main-session safety
`openclaw agent --session-id ` mutates `agent::main.sessionId` on older builds. `agent_runtime.py` snapshots the original pointer before each turn and restores after. The router also has `post_router.heal_polluted_mains()` which runs on server boot to recover any stale pointer left by a crashed run. We also pass `--local` (โฅ 2026.5.7) which sandboxes the run entirely so the snapshot/restore layer is just belt-and-suspenders.
## CLI cheatsheet
```bash
# Web
python3 server.py # 127.0.0.1:7878
python3 server.py --port 8080
python3 server.py --host 0.0.0.0 # auto bearer token
# Local dev service (manual review only โ see policy below)
bin/forge dev # 127.0.0.1:7879, seeded fixtures, isolated data dir
bin/forge dev-reset # wipe + reseed
bin/forge dev-stop # stop running dev server
# Inspect data
ls ~/.openclaw/openforge/threads/
cat ~/.openclaw/openforge/threads//events.jsonl | jq -c
cat ~/.openclaw/openforge/squads.json | jq
```
## Dev service policy
`bin/forge dev` is a **human-only manual-review tool**. It is **not** part of
any agent's workflow.
**Why this rule exists** โ real incident 2026-05-26: a routed agent (judy)
ran `bin/forge dev` from inside her exec tool to "verify her own PR in a
browser." `forge dev` is a foreground daemon, so the agent subprocess never
returned, the router's in-flight slot stayed pinned for ~11 minutes, and
every subsequent @mention to judy in that thread was silently dropped. Two
human kills + a process-group fix later (#5), we agreed on the policy below.
**Allowed**
- Scott (or any human reviewer) running `bin/forge dev` to eyeball a UI PR.
- Stop with `Ctrl-C` or `bin/forge dev-stop`.
**Not allowed for agents**
- Spawning `bin/forge dev` (or any other long-lived service) from inside a
routed agent turn. Routed agent turns are bounded subprocesses; they must
exit cleanly.
- Verifying UI behavior by "just starting the dev server". Agents verify
via `pytest`, `curl` against an already-running service, or by attaching
screenshots a human captured separately.
**Future** โ a headless Playwright smoke harness that **starts โ asserts โ
exits** within the agent turn timeout is acceptable (it's not a long-lived
service). Tracked in TODO under "DX / tests".
## Roadmap
### Shipped
- โ
Squad / Thread / Post model + CRUD UI
- โ
Squad archive (soft-hide)
- โ
Post routing (`@agent` โ `openclaw agent --local --json` reply)
- โ
SSE live event push
- โ
Reply-to-post nesting (`parent_post_id`, feature flag in settings)
- โ
Reactions (hover picker + emoji chips, toggle semantics)
### Next
- Per-thread or per-squad "main agent" so follow-ups don't always need `@`
- Persisted user identity (currently hard-coded `scott`)
- Scheduled-thread templates (standup returns as a thin layer)
- Search / filter across threads
### P1 โ task management (separate PRD)
- Linear-style fields on a thread: status / priority / assignee / due / cycle
- Board view (kanban by status)
- Cycle view (sprint-style)
## Not goals
- โ Multi-user auth or hosted SaaS โ OpenForge is a local cockpit for one operator.
- โ Database โ JSONL on disk is enough; SQLite is the migration path if needed.
- โ A general chat tool โ every thread is task-shaped, with an opening and a closing.