feat(agents): show Claude and Codex subagents under their agent in the sidebar
Summary
-
Claude Code and Codex subagents (Task tool,
spawn_agent) now show up nested under their parent agent in the workspace row's agents chip hover, labeled Subagent with the harness agent type (Explore,general-purpose, a Codex role). -
The notify hook used to
exit 0on any payload carryingagent_id, so subagent activity never reached the host. It is now forwarded on a separate channel that feeds a per-terminal roster and never touches the terminal's own status, chime, or resumable session id. -
Clicking a subagent row opens a subagent pane that follows the child's transcript live: the host resolves the child's file from the hook's transcript paths, parses Claude subagent JSONL and Codex child rollouts into one entry shape, and the pane polls each second while the child runs, then shows Finished. The pane tab reads "Subagent · " and its header links back to the parent agent's terminal pane.
-
An agent pane whose agent has children running shows a "N subagents" dropdown in its header; each entry opens that child's pane.
-
The chip shows a muted
+Nfor children running underneath its agents, the parent row in the hover carries the same+N, and a workspace with one agent that has children now gets the chip.
Why / Context
Both harnesses expose subagent lifecycle through hooks (agent_id + agent_type on every event fired inside a child, plus SubagentStart / SubagentStop), and Orca already surfaces this. We registered neither event and discarded the signal on purpose to keep child activity from flipping terminal status. This keeps that guarantee and adds the roster alongside it.
How It Works
agent-setup:SubagentStartandSubagentStopjoin the managed Claude (settings.json) and Codex (hooks.json) event lists.notify.shbumps to v12: when the payload hasagent_id, it POSTs{terminalId, eventType, subagent: {id, type}}tonotifications.hookand stops. No v1 fallback and nosession_id, because a Codex child'ssession_idis its own thread id, not the terminal's resumable session.host-service: Transcript paths and parsers are selected from the parent binding’s agent id through one harness registry: Claude derives the child file and reads its description sidecar, Codex consumes the child rollout directly, and unregistered harnesses use hook paths as given with generic JSONL shape detection.notifications.hookroutes asubagentpayload toTerminalAgentStore.recordSubagentEventand broadcastsagent:bindings-changed(invalidation only, no lifecycle event). The store keeps an in-memoryMap<terminalId, Map<subagentId, TerminalSubagent>>: start and tool events upsert (a lostSubagentStartself-heals on the child's next tool call),SubagentStopremoves, entries quiet for ten minutes are dropped on read, and the roster clears when the terminal's session changes or ends. Reads (get,list,listByWorkspace) attach it asbinding.subagents. Nothing is persisted: children live and die with the parent session.desktop:useDashboardSidebarWorkspaceRunningAgentsmapsbinding.subagentsonto each agent;DashboardSidebarAgentHoverRowrenders them indented under the agent with a tree guide. The chip count still counts agents only.
Manual QA Checklist
Verified in the dev desktop over CDP (Claude Code 2.1.263, Codex 0.153.0):
- Claude:
SubagentStart→PostToolUse→SubagentStoparrive withagent_id/agent_type; the Explore rows appear under the working Claude while the Task runs and disappear on stop. - Codex:
spawn_agentchild firesSubagentStart/SubagentStop(twice each, absorbed by the idempotent upsert); thedefaultrow appears under the working Codex and clears on stop. - Parent binding is untouched by child events:
lastEventType,agentSessionId, and the chime all follow the main loop only. - Subagent events for a terminal with no live agent, or after the terminal ended, are ignored.
- Rows for a child whose stop hook never arrived expire after ten minutes.
- Whole flow filmed over CDP with real input (close tabs, open Claude, prompt, chip +N, hover, click child, live pane, parent link, Finished), 107 s, console.error empty. The film is on the evidence page. It caught the SubagentStop payload bug fixed in the last commit.
- Subagent pane: Codex child opened from the hover in-app, showed prompt, tool calls and output while running, flipped to Finished on stop. Claude child verified through the same hooks and host from a Sonnet
claude -pbound to the pane's terminal id (the in-app profile had hit its Fable quota); title came from the child's meta description.
Evidence page with captures, hook traffic, and payload shapes: https://app.superset.sh/page/subagent-roster-evidence-pr-7231-4pq6vr
Testing
bun testinpackages/agent-setup(299 pass) andpackages/host-serviceterminal-agents + notifications + terminal-agents router (83 pass)bun run typecheckinapps/desktop,tsc --noEmitinpackages/host-serviceandpackages/agent-setupbunx biome checkon changed filesbun run check:i18n(new "Subagent" string translated in all 17 catalogs)
Hardening from review
- Child events naming the parent's previous session are dropped; transcript paths are kept only when absolute,
.jsonl, and under home; the roster is capped at 64 children per terminal; the tail read trusts the bytes actually read. - Harness-specific behavior lives in
packages/host-service/src/terminal-agents/subagent-harnesses/, one file per harness built withdefineSubagentHarness(parser required, stop events, parent-session check, path resolution, and description lookup default to the shared behavior). Unregistered harnesses use hook paths as given and detect the record shape. The store owns the subagent pipeline; routers only validate and broadcast. Renderer deep links go through one codec, and the page and subagent openers sharefocusOrOpenPane.
Known Limitations
- Until this ships, a prod build running alongside a dev build rewrites
~/.claude/settings.json, theCLAUDE_CONFIG_DIRprofile'ssettings.json, and~/.codex/hooks.jsonwith its older managed event list, which strips the Subagent entries. Same class of transition as any managed-event change. - Roster is in-memory, so a host-service restart forgets running children until their next tool event re-adds them; an open subagent pane then reports the child as unavailable until it speaks again.
Follow-ups
- Hold the terminal's "working" state while a child from Claude's
background_taskscensus is still running after the lead's Stop.
https://claude.ai/code/session_01T9PbNmHemSfwnJtwvWuvsU
Summary by cubic
Shows Claude and Codex subagents (Task tool, spawn_agent) nested under their parent agent in the sidebar's agents chip, and opens a live transcript pane for each when clicked. Subagent hook events are now forwarded to the host instead of dropped; the terminal's own status, chime, and session id stay untouched.
Details
- The host keeps an in-memory per-terminal roster exposed as
binding.subagents; it self-heals on missed starts, caps at 64 children, expires quiet children after ten minutes, and drops events naming a previous session. - The notify hook bumps to v14 and forwards transcript paths on subagent events; its payload extractor reads only the first field match, so Claude's repeated
agent_typein a stop event no longer 400s the post. Managed Claude and Codex configs registerSubagentStart/SubagentStopand are rewritten on next setup. - The sidebar nests each child under its agent with a muted
+Ncount, and a single agent with children now gets the chip; the accessible name includes the count. - Clicking a child deep-links into the workspace and opens a pane that tails the child's own transcript (Claude
subagents/agent-<id>.jsonl, Codex rollout), polling while it runs. Agent pane headers show an "N subagents" dropdown while children are running, and the pane's header links back to the parent terminal. - Path resolution and parser selection run through a per-harness registry; unregistered harnesses use hook paths with JSONL shape detection, and only trusted transcript paths under home are read.
- The roster is in-memory only, so a host restart forgets running children until their next tool event re-adds them.
Summary by CodeRabbit
-
New Features
- Running subagents now appear beneath their parent agents in the dashboard sidebar.
- Select a subagent to open its live transcript in the workspace.
- Transcript views display prompts, responses, thinking, tool activity, status, and waiting states.
- Easily return from a subagent transcript to its parent agent.
- Subagent labels and transcript messages are localized across supported languages.
-
Bug Fixes
- Improved handling of subagent events when parent sessions end or are unavailable.
-
Tests
- Added coverage for subagent tracking, transcripts, notifications, cleanup, and sidebar roster updates.