Sub-Agents
Multi-pane orchestration using Zellij for full observability. Sub-agents are spawned as separate WASM plugin instances with file-based communication.
Design
Each sub-agent runs in its own Zellij pane as a separate cowboy plugin instance. The parent spawns via zellij action launch-plugin, passing configuration through a serialized config string. Communication is entirely file-based.
Principles
- Full observability -- each sub-agent is a visible Zellij pane
- File-based communication -- all state lives on disk (prompt.md, response.md, lock)
- Recovery --
scan_existing()re-discovers sub-agent state from disk after crash - Optional context inheritance -- via
contextparameter:"none"(fresh context, default),"summary"(summarizer-produced session summary), or"last_n"(system prompt + last 10 turns) -- configured with cheap models by default
Directory Structure
/home/agent/subagents/<type>-<uuid6>/
prompt.md # Task from parent (with YAML frontmatter: type, id, allowed_tools)
conscious.md # Working context (YAML frontmatter + markdown body)
response.md # Final output (written by sub-agent, triggers completion)
lock # Timestamp lock (exists while running)
tools.json # Filtered tool manifest for this sub-agent type
File Protocol
| File | Written By | Read By | Semantics |
|---|---|---|---|
prompt.md | Parent | Sub-agent | Initial task; loaded on first timer tick |
tools.json | Parent | Sub-agent | Subset of parent's tool manifest |
conscious.md | Sub-agent | Parent/observers | Live frontmatter: status, tokens_used, current_task, progress |
response.md | Sub-agent | Parent | Final result; presence + lock removal = completion |
lock | Sub-agent | Parent | Liveness signal; removal without response.md = failure |
Sub-Agent Types
Defined in SubAgentType enum. Each type restricts tool access:
| Type | Tools | Default Model |
|---|---|---|
| Research | read, search, find, web-search, ls | gpt-4o-mini / claude-3-5-haiku |
| Code | read, write, search, find, bash, ls, hashline-read, hashline-edit | gpt-4o-mini / claude-3-5-haiku |
| Review | read, search, find, bash, ls | gpt-4o-mini / claude-3-5-haiku |
Cost optimization: SubagentConfig::cheap_defaults() prefers OpenAI gpt-4o-mini, falls back to Anthropic haiku.
Lifecycle
- Parent calls
spawn_subagent(task, type)-- createsSpawnCommand - Parent writes
prompt.mdandtools.jsonviarun_sh(WASM can't write directly) - Parent launches plugin pane via
zellij action launch-pluginwith serialized config - Sub-agent loads -- on first timer tick, reads prompt.md + tools.json via
run_command, writes lock - Sub-agent works -- processes prompt as user message, updates conscious.md
- Sub-agent completes -- calls
submit_responsetool, which writes response.md, removes lock, closes pane - Parent detects completion via heartbeat polling (
check_subagent_status) and reads response.md - Parent injects result as
[Subagent Result: <id>]user message, continues conversation
Status Detection
Parent polls via shell command that checks all subdirectories:
lockexists -> Runninglockgone +response.mdexists -> Completedlockgone + noresponse.md-> Failed
Polling happens on heartbeat timer (every ~30s idle, ~0.3s active).
Recovery
SubAgentManager::scan_existing() walks the base directory, discovers sub-agents by presence of prompt.md, infers type from ID prefix, and determines status from lock/response files. This enables recovery after harness crash.
Cleanup
cleanup_completed() removes sub-agent directories older than 1 hour (CLEANUP_AGE_SECS = 3600).
Not Yet Implemented
- history/ directory for compaction snapshots (design doc mentioned, not in code)
- Sibling communication via inbox directories
- Tee-style buffering (ring buffer + async writer) -- infeasible in WASM/WASI environment
- PID-based liveness -- lock file uses timestamp instead (std::process::id unsupported in WASI)