Skip to content

SymbolStar/OpenForge

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

104 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

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 @<agent>, 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/<thread-id>/        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

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/<thread-id>/                                      β”‚
β”‚       β”œβ”€β”€ 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

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)

{"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/<id>                          β†’ { squad, threads }
PATCH  /api/squads/<id>                          β†’ update (name/members/archived/…)
DELETE /api/squads/<id>                          β†’ delete
POST   /api/squads/<id>/threads                  β†’ create thread + opening post

GET    /api/threads/<id>                         β†’ thread detail + posts
POST   /api/threads/<id>/posts                   β†’ append post
                                                   body: { content, speaker?, parent_post_id? }
POST   /api/threads/<id>/posts/<pid>/reactions   β†’ toggle reaction
                                                   body: { emoji, actor? }
POST   /api/threads/<id>/close                   β†’ mark closed
GET    /api/threads/<id>/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 <X> mutates agent:<id>: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

# 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

# Inspect data
ls ~/.openclaw/openforge/threads/
cat ~/.openclaw/openforge/threads/<thread-id>/events.jsonl | jq -c
cat ~/.openclaw/openforge/squads.json | jq

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.

About

πŸ”¨ Multi-agent task tracker for OpenClaw. Threads are tasks; @ assigns the next agent. Slack-style three-pane UI, JSONL event log, zero dependencies.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors