Skip to content

Measure the fidelity of forwarded subagent tool payloads #19

Description

@Dillpickleschmidt

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's content_block_start path (ClaudeAdapter.ts:2249-2275), and T3 sets includePartialMessages: true (line 3544). But handleAssistantMessage — the path a complete assistant message takes — inspects content blocks and only acts on ExitPlanMode (line 2522); every other tool_use block emits nothing. SDKPartialAssistantMessage does carry parent_tool_use_id (sdk.d.ts:3735), so the streaming path is possible but unconfirmed.

  • If subagent blocks arrive as 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."
  • If they arrive only as complete assistant messages → 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 Task either, so there is none.

Then — fidelity.

Do the subagent tool_use / tool_result blocks that the Claude Agent SDK forwards onto the parent stream carry full input and result payloads, 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 needs Targets — file paths and line ranges — which come from input and result.

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 real Task on a Claude thread that makes the subagent read and edit files, and capture the raw SDK messages before ClaudeAdapter normalizes them. Same shape as Capture live tool-call payloads for Cursor/Grok and OpenCode.

Record: a verbatim sample of a forwarded subagent tool_use and its matching tool_result, and an explicit verdict on whether input and result are complete. Also worth noting while there: whether a subagent's context_compaction is forwarded, and whether message.model differs from the parent's.

Activity

  1. Dillpickleschmidt commented on Aug 1, 2026

    @Dillpickleschmidt
    OwnerAuthor

    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"}). A Task was launched with instructions to make the subagent do all the reading; it ran Read, Read, Bash(grep). 55 messages captured.

    1. Do subagent tool calls become T3 activities today? — No.

    message type total with parent_tool_use_id
    stream_event 32 0
    assistant 6 3
    user 5 4
    system 10 0
    result / rate_limit_event 2 0

    Subagent content arrives only as complete assistant / user messages, never as stream_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's content_block_start path (ClaudeAdapter.ts:2249-2275). The complete-message path, handleAssistantMessage, walks content blocks and acts only on ExitPlanMode (line 2522); every other tool_use block falls through to turnState.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, full command, full description, complete multi-KB tool_result content, is_error present. Everything mindwalk's Targets need — paths and line ranges — is available. The attribution approach is viable.

    Consequences

    1. 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 through stream_event.

    2. 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.

    3. 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.

    4. Incidental: the subagent reached for Bash+grep rather than the Grep tool, so subagent search classification lands on the command path — relevant to classifyToolAction in 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.

  2. Dillpickleschmidt commented on Aug 1, 2026

    @Dillpickleschmidt
    OwnerAuthor

    Measured and answered — see the resolution comment. Subagent blocks are forwarded in full but only as complete assistant/user messages, which T3 currently drops.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions