This document is the tour a developer new to the wanman codebase should read first. It covers the shape of the supervisor, how agents are spawned and talk to each other, and where state lives.
+----------------+ +--------------------------------+
| wanman CLI | | Supervisor process (local) |
| (host shell) | JSON-RPC | |
| | -----------> | MessageStore ContextStore |
| send/recv/ | HTTP :3120 | TaskPool ArtifactStore |
| task/agents/ | | InitiativeBoard |
| artifact/... | | CronScheduler |
+----------------+ | Relay LoopEventBus |
| |
| +--------------------------+ |
| | AgentProcess[] | |
| | (spawns Claude/Codex | |
| | CLI children) | |
| +--------------------------+ |
+----------------+---------------+
|
v
Claude Code / Codex subprocess
(per-agent worktree + $HOME)
The CLI is a thin JSON-RPC 2.0 client. Everything interesting — process management, persistence, routing — lives in the supervisor. The supervisor is a plain local Node.js process; the CLI only needs WANMAN_URL to reach it.
Every agent has a lifecycle declared in the agents config:
start()
+-> runLoop()
+- relay.recv() -- pull pending messages
+- spawnClaudeOrCodex() -- boot the CLI subprocess with those as prompt
+- wait() -- block until the subprocess exits
+- sleep(RESPAWN_DELAY_MS) -- cooldown
+- repeat
- Each loop iteration spawns one fresh CLI subprocess.
- If messages are pending, they become the initial prompt. Otherwise a default "you are alive, check your inbox" prompt is used.
- Exit → cooldown → respawn. The loop is interrupted by
handleSteer()(see below).
start()
+-> state = 'idle' (no CLI subprocess running)
+- trigger() or handleSteer()
+- relay.recv()
+- spawnClaudeOrCodex()
+- wait()
+- state = 'idle' again
- Initial state is
idle; no CPU is spent until something pokes it. - A steer-priority message or a cron tick triggers a single execution, after which the agent falls back to
idle. - Each spawn is stateless: the next trigger gets a fresh CLI session.
start()
+-> state = 'idle' (no CLI subprocess running)
+-> lastSessionId = null
trigger() / handleSteer()
+- relay.recv()
+- spawnClaudeCode({ resumeSessionId: lastSessionId }) # claude --resume <id>
+- onSessionId(id => lastSessionId = id) # captured from system/init
+- wait()
+- if resumeMissed(): # stale session
lastSessionId = null
respawn without --resume # cold-start fallback
+- state = 'idle' again (but lastSessionId is preserved)
- Same idle CPU profile as
on-demand, but conversation context survives idle periods because the next spawn passes the captured Claudesession_idas--resume <id>. - The first trigger always cold-starts (no captured session yet).
- If the local Claude CLI has dropped the session id (rotated, manually deleted, etc.), the runtime detects the failure via stderr + exit code, clears the cached id, and re-spawns once without
--resume. No agent gets stranded. - Useful for stateful long-running roles where keeping a process alive forever would be wasteful but losing context every trigger is also wrong (e.g. a "support" agent that should remember the customer between messages).
- Claude-only. The resume mechanism depends on
claude --resumeand Claude Code'ssystem/initsession id; Codex has no equivalent in this runtime today. Pairingidle_cachedwithruntime: codex(or lettingWANMAN_RUNTIME=codexflip the effective runtime) is rejected at supervisor startup so the misconfig surfaces loudly instead of silently degrading toon-demandsemantics.
| State | Meaning |
|---|---|
idle |
Waiting (on-demand normal state). |
running |
A Claude Code or Codex subprocess is executing. |
stopped |
Manually stopped or shutting down. |
error |
Recent crash; the supervisor will retry. |
| Priority | Value | Effect |
|---|---|---|
steer |
0 | Interrupts the target's current subprocess; next loop iteration picks it up first. |
followUp |
1 | Normal queueing; handled in timestamp order on the next loop. |
sender Relay target agent
| | |
|-- agent.send(steer) ------->| |
| |-- messageStore.enqueue() -----|
| |-- steerCallback(agent) ------>|
| | |-- kill(currentProcess)
| | |
| | (next loop) |
| | |-- relay.recv()
| | | (steer sorted first)
| | |-- spawnClaudeOrCodex(steerMsg)
The relay's steer callback tells the AgentProcess to SIGKILL its current Claude/Codex child. The run loop's normal respawn path then picks the steer message up first because of the SQL ordering.
- Messages are persisted in SQLite (
messagestable) beforesend()returns. recv()returns pending (delivered = 0) rows and marks them delivered in a single transaction — no double-delivery.- Ordering:
ORDER BY CASE priority WHEN 'steer' THEN 0 ELSE 1 END, timestamp ASC.
interface AgentMessage {
id: string // UUID
from: string // sender agent name (or "system")
to: string // recipient agent name
priority: 'steer' | 'followUp'
content: string // plain text prompt body
timestamp: number // unix ms
delivered: boolean
}Cross-agent shared key/value storage in SQLite. Useful for system-wide state like "last build result" or "current MRR".
interface ContextEntry {
key: string
value: string
updatedBy: string // agent name
updatedAt: number // unix ms
}RPC methods: context.get, context.set, context.list. set is an upsert (INSERT ... ON CONFLICT DO UPDATE).
The supervisor also owns structured state beyond raw messages:
- TaskPool — agent-owned tasks with status (
pending,in_progress,done,blocked), priorities, and--afterdependencies.wanman task listrenders a dependency-aware view. - InitiativeBoard — longer-lived multi-task initiatives.
- ArtifactStore — structured outputs (research summaries, plans, etc.) produced via
wanman artifact put. Artifacts have kind, path, content, and JSON metadata. - HypothesisPool — experiment-style hypotheses with status transitions.
- ChangeCapsulePool — proposed change bundles agents can review.
All of these are just SQLite tables under a single wanman.db, reachable via JSON-RPC.
Two asynchronous inputs to the matrix besides agent-to-agent messages:
POST /events— an external system (CI, a webhook, a human script) pushes anExternalEventobject. The supervisor iterates agents and, for any whosedefinition.events[]includes the event type, enqueues a follow-up message with the serialized payload.- CronScheduler — runs every 60 seconds, checks each agent's
cronexpression, and fires a follow-up message (plus ahandleSteer()for on-demand agents) when it matches. Standard 5-field cron:min hour dom mon dow.
These are the two seams where wanman plugs into "the rest of your infrastructure" without hard-coding any particular webhook provider.
Each agent child is a Claude Code or Codex subprocess. The supervisor selects between them via WANMAN_RUNTIME (default claude) and a per-agent override if present.
claude-adapter.ts/claude-code.ts— spawnsclaudewith the agent's system prompt, injects skill files, streams structured events back to the supervisor.codex-adapter.ts— same shape, targeting@openai/codex.WANMAN_CODEX_MODELandWANMAN_CODEX_REASONING_EFFORTcontrol model selection.
Both adapters emit the same AgentRunEvent stream, so the supervisor and CLI don't care which is running. Adding a new adapter is a matter of implementing that event contract and registering it in agent-process.ts.
Before spawning the first child, the supervisor sets up, per agent:
- A worktree materialized from the current
HEADunder.wanman/worktree/. Agents edit this, not your real checkout. - A per-agent
$HOMEunder.wanman/home/<agent>/with generatedwanmanandpnpmwrappers. Shell profile writes,.npmrcedits, etc. stay contained. - A per-agent
.claude/(or.codex/) under that home, so the two agents don't step on each other's CLI state.
The wanman wrapper inside each agent's $PATH points at the same CLI binary but presets WANMAN_AGENT_NAME so wanman recv without args "just works" from the agent's perspective.
packages/core/skills/*/SKILL.md ship alongside the runtime bundle. At supervisor startup, setupSharedSkills() (in shared-skill-manager.ts) materializes them into each agent's ~/.claude/skills/ so Claude Code auto-discovers them.
Skill snapshots — immutable copies tied to a specific run — are written to the directory resolved by WANMAN_SKILL_SNAPSHOTS_DIR, or a sibling of the shared-skills dir, or $TMPDIR/wanman-skill-snapshots as a last resort. This is the mechanism that lets you audit exactly which skill version an agent had available for a given task.
Skills shipped today:
artifact-naming,artifact-quality— conventions for agent-produced artifacts.cross-validation— CEO consistency checks across agent outputs.research-methodology— market/data research methodology.wanman-cli— CLI command reference agents read at runtime.workspace-conventions— file layout agents should follow inside their workspace.
Two persistence layers, both optional to the agent code:
- Local SQLite (
dbPathin agents config) — always present. Messages, context, tasks, artifacts, hypotheses, capsules. Durable across supervisor restarts inside the same workspace. @sandbank.dev/db9brain adapter (optional) — if the runtime is configured with a db9 connection (token + db name), it mirrors artifacts and context to a cross-run, cross-machine store. Useful for fleets of supervisors sharing memory, or for post-run analysis. The OSS build treats db9 as an optional peer dependency — missing it just disables the mirror.
| Endpoint | Method | Purpose |
|---|---|---|
/health |
GET | Supervisor + per-agent state snapshot. |
/rpc |
POST | JSON-RPC 2.0 — the main CLI surface. |
/events |
POST | External-event ingress. |
Main RPC methods:
| Method | Purpose |
|---|---|
agent.send / agent.recv / agent.list |
Inter-agent messaging. |
context.get / context.set / context.list |
Shared context. |
task.* / initiative.* / capsule.* / artifact.* / hypothesis.* |
Structured state. |
event.push |
Same as POST /events, for RPC clients. |
health.check |
Health snapshot over RPC. |
Errors use the standard JSON-RPC codes (-32700 parse, -32600 invalid request, -32601 method not found, -32602 invalid params, -32603 internal) plus -32000 (agent not found) and -32001 (agent not running).
packages/
cli/ Commands (send, recv, task, artifact, run, takeover, watch).
Speaks JSON-RPC to the supervisor. No business logic.
core/ Shared types and JSON-RPC protocol definitions.
core/skills/ ships the SKILL.md bundle.
host-sdk/ Programmatic embedding SDK for host-side integrations.
runtime/ The supervisor. Agent process manager, SQLite stores,
Claude/Codex adapters, cron scheduler, event router.
For the CLI surface and env vars, see the README.