ADR-108: Sub-agent fires as first-class playbook fires
Status: Proposed Date: 2026-04-26 Related: ADR-067 (playbooks-unified-primitive), ADR-090 (ai-as-scaffold-pipelines-as-runtime), ADR-104 (dry-run-on-history-adaptive-gate), ADR-107 (gates-as-playbook-primitives)
Context
Today the factory runs two parallel execution worlds and only one of them is visible.
The playbook runtime is first-class. Fires land as rows in playbook_runs, stream over the Server-Sent Events (SSE) endpoint built in warp #225, render through the LiveRunView stepper-and-terminal surface from warp #224, and surface in /operate for review.
The sub-agent fleet is second-class. When the parent Claude Code session fires a sub-agent (today's morning swarm fired GAA/GAB/GAC/GAD/GAF/GAG nine times) the work exists only as /tmp/claude-1000/.../tasks/<id>.output JSONL transcripts on the operator's laptop, visible only via the parent agent's after-the-fact summary. There is no DB row, no SSE stream, no /operate entry, no /playbooks page. The operator cannot watch sub-agent work happen.
ADR-107 made the principle explicit: every safety mechanism in the factory must survive the handoff away from Claude. The corollary is the visibility one: every execution surface must survive the handoff too. If "what an agent did last night" lives only in the parent's chat scrollback, removing the parent removes the audit trail. The 2026-04-26 deploy postmortem (warp #321) called this out as the next gap to close.
The work to close it is tracked in warp #333 (sub-tickets P-A1 through P-A6).
Decision
Sub-agent invocations register as kind: agent_task rows in the existing playbook_runs table and ride the same SSE / DB / UI surface as any other playbook fire. The parent Claude session continues to fire sub-agents through the existing Agent tool; a bridge tails the sub-agent's JSONL transcript and forwards parsed events (text content, tool-use markers, completion + token usage) to the runtime's existing SSE endpoint. The UI reuses LiveRunView at /playbooks/agent_task?run=<id>, with panels for the prompt brief, the stepper (derived from TodoWrite or chunk markers), the streamed terminal, the tool-use sidebar, and the outputs (PRs opened, files changed, warp items completed). Spawn relationships are tracked via parent_run_id, so today's 4-in-flight swarm becomes a navigable parent-spawn tree rather than a flat list.
The runtime stays minimal in the same shape ADR-107 required for gates: no agent-specific logic, just one new kind value and the same execution-event contract every other fire uses.
Consequences
What becomes easier.
- Sub-agent work surfaces in
/operatenext to ansible and shell fires, with akindchip filter (warp #320 cross-link). The "I can't see what agents are doing" gap closes in the same UI surface that already exists. - The parent-spawn tree is navigable. Click into a parent fire, see its children + grandchildren, drill into any node's
LiveRunView. The morning's swarm becomes a tree, not a chat-scrollback hunt. - Cost and token telemetry per agent and per spawn-tree fall out for free — the
<usage>blocks already arrive in completion notifications, the tracking shape from warp #297 (cost dashboard widget) reuses directly. - Audit trails outlive the parent session. A fresh agent or operator can read what last night's swarm did from
playbook_runs, not from the previous parent's/tmptranscripts.
What becomes harder.
- A bridge daemon now sits between the
Agenttool and the runtime SSE endpoint, tailing JSONL and forwarding events. It is one more component to deploy, restart, and watch. The mitigation: it lives in the same controller surface as the playbook runtime and reuses its SSE plumbing, so failures show up in the same/operatehealth pane. playbook_runsschema gains akind: agent_taskvalue plus the agent-specific input/output columns (prompt,model,time_box_minutes,parent_session_id,parent_run_id,final_report,files_changed,prs_opened,warp_items_completed). A migration must run before sub-agents stream.LiveRunViewgrows agent-task panels (brief, outputs). Existing ansible/shell renderings stay unchanged, but the component file approaches its 900-line ceiling — a split may be needed (panel components per kind).
What we sign up to maintain.
- The bridge daemon, including its lifecycle, restart policy, and back-pressure behaviour when the runtime SSE endpoint is slow.
- The
playbook_runs.kind=agent_taskschema and migrations for new fields. The schema is the contract every consumer (UI,/operate, audit, cost dashboard) reads from. - The parent-spawn-tree model.
parent_run_idmust be populated on every nested fire, with a recursion-depth guard so a runaway agent firing itself indefinitely fails fast.
Alternatives considered
- Leave sub-agents as JSONL only and surface them via a separate
/agentspage. Rejected: it preserves the two-world split, doubles the UI surface, and means cost dashboards //operate/ audit queries each need a parallel code path. The whole point of ADR-067's unified-primitive line is one execution surface; this would re-fork it. - Make the parent agent POST events directly (no bridge daemon). Rejected: it couples the
Agenttool to runtime endpoint shape changes, and silently drops events any time the parent is offline (laptop closed, session crashed). The bridge tails the file, so events survive parent-session death. - Track sub-agents in warp items only (no
playbook_runsrows). Rejected: warp items are work intent, not execution traces. Cross-cutting queries like "what did the fleet run yesterday" mix the two and lose the per-event timelineLiveRunViewneeds.
Supersedes: none Superseded by: none yet