Skip to content

fix(server,web,mobile): keep a subagent's own work inside its spawn card - #539

Merged
incognitojam merged 1 commit into
mainfrom
styal/organize-agent-work-log
Sep 29, 2026
Merged

incognitojam merged 1 commit into
mainfrom
styal/organize-agent-work-log

Conversation

@incognitojam

Copy link
Copy Markdown
Owner

Note

A Claude subagent's own background shells and nested agents leaked into the parent timeline as "Received N updates" rows and one-agent "Kicked off 1 subagent" cards. The server now attributes those tasks to the subagent that started them, and web and mobile keep them inside that subagent's single spawn card.

Problem

When a Claude subagent started its own background shells or its own agents, the parent thread's timeline filled with stray rows. Each finished shell became a "Received N updates" row. Each nested agent became a separate "Kicked off 1 subagent" card, placed wherever it happened to start, often between unrelated messages.

The clients already hide a subagent's internal work, but only when the task carries agentId (its owner). The Claude adapter never set it. It looked for the launching tool call among streamed tool calls, but Claude streams tool-call deltas only for the parent conversation. A subagent's tool calls arrive only as whole assistant snapshots tagged with parent_tool_use_id, and the adapter used those snapshots only to refine the model name. In practice no task row ever got agentId.

Upstream has the same gap. The open related PRs don't cover this path: pingdotgg#10575 still depends on child stream events, and pingdotgg#14272 and pingdotgg#14031 address resumed-subagent labels and reactivation.

Fix

  • Server (Claude adapter): records which subagent made each tool call from its assistant snapshots, bounded to 512 entries per session. task_started uses that record when no streamed call matches. A subagent's shells therefore carry the subagent's agentId, and so do its nested agents. A resumed subagent keeps its owner. Its later messages also still resolve to it, because they keep the original Agent call as their parent even though the SDK re-registers the task under the SendMessage call.
  • Web and mobile: a nested agent joins its owner's spawn card instead of opening its own. Its owner's first row has already decided that card. The existing quiet-timeline rule already hides a subagent's own shells once they carry agentId.

Existing threads are not repaired, because their persisted task rows have no owner to group by.

Validation

  • Live run against an isolated dev server with a real Claude Sonnet 5.5 thread. The parent launched one background subagent and ended its turn. After that, the subagent started a background sleep shell and a nested background agent, and the nested agent started its own shell. The persisted task rows had the expected owners:
    • The subagent's shell and the nested agent were attributed to the subagent.
    • The nested agent's shell was attributed to the nested agent.
    • When the CLI resumed the subagent on its own, it stayed unowned.
  • Timeline: one "Ran 2 subagents ✓ completed" card at the spawn point, with no "Received N updates" rows and no extra cards. The Agents panel listed both agents.
  • Focused tests: one each for the adapter, web and mobile. Each fails without the fix and passes with it. The adapter test covers shells, nested agents, completions, and a shell started after a resume. Full ClaudeAdapter.test.ts, session-logic.test.ts and threadActivity.test.ts pass, and server, web and mobile typecheck.
  • Not verified: the mobile client was not run. Mobile is covered by its unit test only.
Before After
Before: stray one-agent cards and update rows After: one spawn card for the whole run

Written by an agent (Claude Code, claude-opus-5-5).

Claude streams tool-call deltas only for the parent conversation, so a
subagent's tool calls reach the adapter only as whole assistant
snapshots. task_started looked for the launching call among streamed
in-flight tools, never found it, and so never stamped agentId. Every
background shell and nested agent a subagent started then rendered in
the parent timeline as "Received N updates" rows and one-agent spawn
cards placed wherever it happened to start.

The adapter now records the owner of each tool call seen in a subagent
snapshot and uses it when the task starts. A resumed subagent keeps its
owner and still resolves its messages, which keep the original Agent
call as their parent. Web and mobile fold a nested agent into its
owner's spawn card.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@incognitojam
incognitojam merged commit aaf370f into main Sep 29, 2026
29 of 31 checks passed
@incognitojam
incognitojam deleted the styal/organize-agent-work-log branch September 29, 2026 16:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant