An open API service indexing awesome lists of open source software.

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.

Awesome Lists containing this project

README

          


OpenForge

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.