Skip to content

Keep background sub-agent output in the side panel, not the main chat#151

Merged
pufit merged 1 commit into
mainfrom
pufit/fix-background-subagent-panel-routing
Jun 25, 2026
Merged

Keep background sub-agent output in the side panel, not the main chat#151
pufit merged 1 commit into
mainfrom
pufit/fix-background-subagent-panel-routing

Conversation

@pufit

@pufit pufit commented Jun 25, 2026

Copy link
Copy Markdown
Member

Problem

Background sub-agents (the Agent tool with run_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 still status === 'running':

if (parentId && state.panels.some(p => p.id === parentId && p.status === 'running'))

A background sub-agent's Agent tool returns immediately with a task id (like Workflow does). So the immediate tool_result and the backend's immediate subagent_complete marked the panel complete — and scheduled its auto-close — right away. The sub-agent's real activity streams afterward, by which point no panel is running, so each child event fell through into the main chat.

Reload looked correct because the replay path (bufferReplay.ts) enforces a stronger invariant — any parent_tool_use_id event 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 Workflow pattern (spawning tool returns immediately → keep the panel open until the detached work actually settles):

  • Route any parent_tool_use_id event to its panel by id regardless of status, and never into the main chathandleThinking / handleToken / handleToolUse / handleToolResult.
  • Add a background flag to PanelTab (from input.run_in_background). The immediate Agent result is recorded on the inline chat card, but the panel stays open + running; the premature subagent_complete is ignored; finalizeRunningPanels skips these panels so the launching turn's done doesn't close them.
  • Settle background panels via handleBackgroundTasksUpdate once 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 by task_id, which for Agent tasks doesn't carry the spawning tool_use_id). An explicit /stop settles them too.
  • Carry the background flag through buffer replay so reconnect-then-live behaves consistently.

Testing

  • npm run build (tsc + vite) — clean.
  • eslint on the changed files — no new problems (repo's pre-existing lint state unchanged).
  • Backend test_streaming.py + test_autonomous_turns.py — 37 passed (no backend changes).
  • Manually traced foreground sub-agents, workflows, normal tool calls, and reload-then-live — all unaffected.

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.

Generated by Nerve

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.
@pufit pufit merged commit 12fb444 into main Jun 25, 2026
2 checks passed
@pufit pufit deleted the pufit/fix-background-subagent-panel-routing branch June 25, 2026 02:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant