Keep background sub-agent output in the side panel, not the main chat#151
Merged
Merged
Conversation
Background sub-agents (the Agent tool with run_in_background) spilled all their tools and thoughts into the main chat instead of their side-panel. Root cause: the live streaming handlers routed a sub-agent's child events (those carrying parent_tool_use_id) to a panel only when a panel with that id was still status === 'running'. A background sub-agent's Agent tool returns immediately with a task id, so the immediate tool_result and the backend's subagent_complete marked the panel complete (and scheduled auto-close) right away. The sub-agent's real activity streams afterward, by which point no panel is 'running', so every child event fell through into the main chat. Reload looked correct only because the replay path enforces a stronger invariant (any parent_tool_use_id event belongs to its panel, never the main chat) — but new live events kept hitting the buggy path. Fix, mirroring the replay invariant and the existing Workflow pattern: - Route any parent_tool_use_id event (thinking/token/tool_use/tool_result) to its panel by id regardless of status, and never into the main chat. - Flag run_in_background sub-agent panels (background). Treat the immediate Agent result like a Workflow launch: record it on the inline card but keep the panel open and running. Ignore the premature subagent_complete and skip these panels in finalizeRunningPanels so the launching turn's done doesn't close them. - Settle background panels when no background task is still running (handleBackgroundTasksUpdate) — the natural completion signal, since there is no per-tool_use_id done event for a detached sub-agent. An explicit /stop settles them too. - Carry the background flag through buffer replay for reconnect parity.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Background sub-agents (the
Agenttool withrun_in_background) spilled all of their tools and thoughts into the main chat instead of their dedicated side-panel. Reloading the page only half-fixed it: already-streamed output snapped into the sidebar, but every subsequent event kept spilling into the main chat.Root cause
The live streaming handlers routed a sub-agent's child events (those carrying
parent_tool_use_id) to a side-panel only if a panel with that id was stillstatus === 'running':A background sub-agent's
Agenttool returns immediately with a task id (likeWorkflowdoes). So the immediatetool_resultand the backend's immediatesubagent_completemarked the panel complete — and scheduled its auto-close — right away. The sub-agent's real activity streams afterward, by which point no panel isrunning, so each child event fell through into the main chat.Reload looked correct because the replay path (
bufferReplay.ts) enforces a stronger invariant — anyparent_tool_use_idevent belongs to its panel and never the main chat — but new live events still hit the buggy live path.Fix
Make the live path honor the same invariant the replay path already uses, and treat background sub-agent panels like the existing
Workflowpattern (spawning tool returns immediately → keep the panel open until the detached work actually settles):parent_tool_use_idevent to its panel by id regardless of status, and never into the main chat —handleThinking/handleToken/handleToolUse/handleToolResult.backgroundflag toPanelTab(frominput.run_in_background). The immediateAgentresult is recorded on the inline chat card, but the panel stays open + running; the prematuresubagent_completeis ignored;finalizeRunningPanelsskips these panels so the launching turn'sdonedoesn't close them.handleBackgroundTasksUpdateonce no background task is still running. This is the only reliable completion signal — a detached sub-agent has no per-tool_use_id"done" event (the CLI's task lifecycle is keyed bytask_id, which forAgenttasks doesn't carry the spawningtool_use_id). An explicit/stopsettles them too.backgroundflag through buffer replay so reconnect-then-live behaves consistently.Testing
npm run build(tsc + vite) — clean.eslinton the changed files — no new problems (repo's pre-existing lint state unchanged).test_streaming.py+test_autonomous_turns.py— 37 passed (no backend changes).Note: there's no frontend test harness in
web/(no vitest/jest), so this is build- and reasoning-verified; the definitive check is a live background-sub-agent run.