Repository navigation
Measure the fidelity of forwarded subagent tool payloads #19
Description
Activity
Measured against a live run of
@anthropic-ai/claude-agent-sdk@0.3.170, using T3's own query options (includePartialMessages: true,systemPrompt: {preset: "claude_code"}). ATaskwas launched with instructions to make the subagent do all the reading; it ranRead,Read,Bash(grep). 55 messages captured.1. Do subagent tool calls become T3 activities today? — No.
message type total with parent_tool_use_idstream_event32 0 assistant6 3 user5 4 system10 0 result/rate_limit_event2 0 Subagent content arrives only as complete
assistant/usermessages, never asstream_event. The seven subagent messages interleave exactly as expected:user parent=toolu_01Urr… blocks=['text'] <- the Task prompt assistant parent=toolu_01Urr… blocks=['tool_use'] <- Read user parent=toolu_01Urr… blocks=['tool_result'] assistant parent=toolu_01Urr… blocks=['tool_use'] <- Read user parent=toolu_01Urr… blocks=['tool_result'] assistant parent=toolu_01Urr… blocks=['tool_use'] <- Bash (grep) user parent=toolu_01Urr… blocks=['tool_result']T3 emits tool items only from
handleStreamEvent'scontent_block_startpath (ClaudeAdapter.ts:2249-2275). The complete-message path,handleAssistantMessage, walks content blocks and acts only onExitPlanMode(line 2522); every othertool_useblock falls through toturnState.items.push(...)and emits nothing.So subagent tool calls currently surface nowhere in T3 — not in chat, not in the event store. The earlier assumption in Close the agent-lenses data gap that they were "already in the event store, flattened" is wrong. They are on the wire and dropped.
2. Are the forwarded payloads full or abridged? — Full.
The SDK's "enough for a heartbeat counter" hedge describes which blocks are forwarded, not their fidelity. Verbatim from the capture:
TOOL_USE name=Read input={"file_path": "/tmp/subagent-probe/alpha.txt"} TOOL_RESULT is_error=None content="1\talpha line one\n2\talpha needle here\n…" TOOL_USE name=Bash input={"command": "grep -rniE 'needle' /tmp/subagent-probe/ …", "description": "Recursively grep for 'needle' case-insensitive"} TOOL_RESULT is_error=False len=9535 (complete grep output)Full
file_path, fullcommand, fulldescription, complete multi-KBtool_resultcontent,is_errorpresent. Everything mindwalk'sTargetsneed — paths and line ranges — is available. The attribution approach is viable.Consequences
-
Persist the subagent attribution edge grows. It is not "stop discarding a field." T3 must start emitting tool items from the complete-message path for subagent blocks, attributed with
parentToolCallId. Still off the same stream — no sidechain files, no second parser — but real adapter work, and it must not double-emit for the main agent, whose blocks already come throughstream_event. -
The main-trace exclusion rule costs nothing. Chat shows no subagent work today, so excluding it from the 3D main trace matches T3's current behavior and mindwalk's. No divergence either way.
-
New decision surfaced: emitting these items makes them visible in chat by default, which collides with the model-only boundary set in Close the agent-lenses data gap. Keeping chat unchanged now requires clients to suppress items carrying a
parentToolCallId. -
Incidental: the subagent reached for
Bash+greprather than theGreptool, so subagent search classification lands on the command path — relevant toclassifyToolActionin Project T3's activity stream onto mindwalk's Trace model.
Method: standalone harness against the SDK directly (no T3 stack), isolating the SDK's forwarding behavior from T3's handling. Not committed — it is 40 lines and fully described above.
-
Measured and answered — see the resolution comment. Subagent blocks are forwarded in full but only as complete assistant/user messages, which T3 currently drops.
Part of #1
Question
First — do subagent tool calls become T3 activities at all today?
This precedes the fidelity question below, and the answer resizes Persist the subagent attribution edge.
Tool items are emitted from
handleStreamEvent'scontent_block_startpath (ClaudeAdapter.ts:2249-2275), and T3 setsincludePartialMessages: true(line 3544). ButhandleAssistantMessage— the path a complete assistant message takes — inspects content blocks and only acts onExitPlanMode(line 2522); every othertool_useblock emits nothing.SDKPartialAssistantMessagedoes carryparent_tool_use_id(sdk.d.ts:3735), so the streaming path is possible but unconfirmed.stream_events → they're already in the event store and in chat, flattened and unattributed. Persist the subagent attribution edge is "stop discarding a field."assistantmessages → they are dropped and appear in no T3 surface today. That ticket becomes "start emitting subagent items and attribute them" — bigger, though still off the same stream, not sidechain files.It also decides whether the main-trace exclusion rule in Project T3's activity stream onto mindwalk's Trace model creates any visible divergence between the 3D surface and chat: in the second case, chat shows nothing during a
Taskeither, so there is none.Then — fidelity.
Do the subagent
tool_use/tool_resultblocks that the Claude Agent SDK forwards onto the parent stream carry fullinputandresultpayloads, or something abridged?This is the one fact Close the agent-lenses data gap could not settle from types, and the whole attribution decision rests on it.
The SDK forwards subagent tool blocks by default, tagged
parent_tool_use_id— but the doc hedges: "only tool_use/tool_result blocks from subagents are emitted (enough for a heartbeat counter)" (sdk.d.ts:1596-1603,@anthropic-ai/claude-agent-sdk@0.3.170). A heartbeat needs only a name and a count. A lens needsTargets— file paths and line ranges — which come frominputandresult.If the payloads are full, the decision stands as written. If they are abridged, lenses degrade to shapeless dots and the approach needs rethinking.
Method: run T3 locally (
test-t3-app), launch a realTaskon a Claude thread that makes the subagent read and edit files, and capture the raw SDK messages beforeClaudeAdapternormalizes them. Same shape as Capture live tool-call payloads for Cursor/Grok and OpenCode.Record: a verbatim sample of a forwarded subagent
tool_useand its matchingtool_result, and an explicit verdict on whetherinputandresultare complete. Also worth noting while there: whether a subagent'scontext_compactionis forwarded, and whethermessage.modeldiffers from the parent's.