You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Mindwalk's agent lenses cannot be ported faithfully from T3's event store. Decide what to do about it.
The collision. Two standing decisions conflict here:
Data source is T3's own event store, so all five providers work everywhere.
Faithful port — everything except the sidebar and the judge stays identical.
Mindwalk builds lenses from separate JSONL files on disk at <session-dir>/<session-id>/subagents/*.jsonl, keyed on isSidechain and agentId (internal/adapter/claudecode/agents.go:41, and a Codex equivalent in internal/adapter/codex/agents.go). T3 never ingests those files. Its Claude adapter records only the collab_agent_tool_call launch with a subagent_type and a description (ClaudeAdapter.ts:836-846) — the subagent's own reads and edits are invisible to the event store. There is no isSidechain or parentUuid handling anywhere in T3's adapters.
So the launch is visible; the interior is not. Lenses need the interior.
Options to weigh:
Extend T3's adapters to ingest sidechain sessions into the event stream. Hardest, but it is real capability T3 lacks generally, not just a visualization concern — subagent work is currently invisible everywhere in the product.
Hybrid — main trace from the event store, lenses read provider JSONL directly when present. Breaks the all-providers promise for lenses only, and dies for remote environments.
Ship without lenses, documented as a known gap, with the HUD subagent count either hidden or showing launches without drill-in.
Whatever is chosen, the answer should say what happens to mindwalk's ○ subagent timeline marks and the HUD's subagent count, since those are visible even when the lens itself is not available.
Option 1, reframed: this is an attribution gap, not an ingest gap.
The ticket's premise — "the subagent's own reads and edits are invisible to the event store" — is wrong for Claude. T3's Claude ingest is the Agent SDK query stream, not JSONL (ClaudeAdapter.ts:4). The SDK emits subagent tool_use/tool_result blocks by default, tagged parent_tool_use_id; forwardSubagentText only adds their text and thinking on top (sdk.d.ts:1596-1603). parent_tool_use_id appears in T3 only in test fixtures, always null — no production code reads it.
So the subagent interior is already in T3's event store, flattened into the main thread and unattributed. The gap is the edge, not the data.
Option 2 (hybrid) is rejected outright — reading provider JSONL alongside T3's own parsing is exactly the duplicated mental model this repo won't carry. Option 3 (ship without lenses) is unnecessary, because option 1 costs one field.
Decisions
Extend T3's model, not the chat UI. Attribution is captured and persisted in the adapter, contract, and event store — a real T3 capability, correct for every consumer — and consumed only by the 3D surface's trace projection. ChatView and mobile render exactly what they render today. How chat should present nested subagent work is deliberately fog, not part of this port.
One field: parentToolCallId?: string, top-level on ItemLifecyclePayload (providerRuntime.ts:404), holding the launching tool call's id. Nothing else.
It is already T3's vocabulary — data.toolCallId exists; this is that id's parent. No new concept, just an edge between two things T3 already stores.
It is self-recursive, so depth and ancestry fall out of the edge. No depth, agentPath, or agentId — those are mindwalk's derived shape and would rot the moment T3 disagrees about what an "agent" is. (claudeChildSidecar.SpawnDepth confirms Claude subagents can nest; the recursive edge handles it natively.)
Everything else on mindwalk's AgentNode is derivable server-side: label/role/instructionPreview from the launch item's persisted data (raw subagent_type + description, not the pre-baked "type: label" summary), launchSeq from sequence, launchCallIdis the parent id.
It crosses the wire for free: projectActivityPayload spreads ...payload and rewrites only data, so a top-level field is untouched. No widening of the six-key whitelist that Classify read and search tool calls per provider adapter found impossible.
forwardSubagentText stays off. Mindwalk's Event is {Seq, Timestamp, Tool, Action, Targets, Outside, ResultBytes, IsError, Summary} — 100% tool-derived (model.go:70). No scene renders subagent prose.
No backfill; the retroactivity loss is accepted. Threads already in the event store have their subagent calls flattened with no edge, and the id was never written down — unrecoverable. Old threads get a main trace with subagent work inlined and no lenses. A backfill would mean re-reading ~/.claude/projects/**: the rejected hybrid, except worse — one-shot, local-Claude-only, and dead for remote and relay connections.
Lenses are a per-provider capability, expressed through mindwalk's own traceAvailability. The standing all-providers decision binds the main trace and the citymap, and is untouched; it cannot promise a provider exposes an interior it never records. This is not the hybrid — every provider populates the same one T3 field from its own transport, inside its own adapter. One model, five adapters, no second parsing path.
Provider
Launch visible
Interior attribution
Claude
yes
yes, free — parent_tool_use_id
Codex
yes
unmeasured (spawn_agent exists in rollouts)
OpenCode
yes
unmeasured
Cursor / Grok
nocollab_agent_tool_call at all
none; ACP carries no parent linkage
The edge always points at the launching tool call, and adapters do the mapping. Mindwalk has two Codex link methods — codex-agent-id and codex-parent-thread-id — so Codex's linkage may be a thread id; if so the Codex adapter resolves it to the spawn_agent call id it already saw. Provider-specific knowledge belongs in adapters; the contract stays one honest concept.
The agent graph is built from launch items alone, so the HUD button is never a lie.
Marks and count derive from collab_agent_tool_call items in the main trace — the analogue of mindwalk's Mark{Type:"subagent"} on a Task/Agent/spawn_agent call. Nothing hidden, nothing special-cased.
Every launch item becomes an AgentNode, interior or not. traceAvailability is available with attributed children, missing for a Claude thread recorded before the field ships, unavailable for a provider with no linkage. AgentRow already renders a non-available row as disabled-with-status (AgentsPanel.tsx:200-206) — shipped mindwalk behavior, not a hole we invent.
Cursor and Grok need no special case: zero launch items → count 0 → mindwalk's own stats.subagents > 0 guard hides the button. The degradation is already in the ported code.
Our links are exact by construction. Mindwalk's exact method is literally AgentLinkMethodClaudeToolUseID, reconstructed by fishing toolUseId out of a sidecar (agents.go:24-29); we are handed it. Its linkQuality grading describes uncertainty we don't have.
Attributed children are excluded from the main trace. Mindwalk's Main lens is the root session file only and never merges subagent work; T3 today inlines it. Being faithful means the main trace drops items carrying a parentToolCallId — a real behavior change, not cosmetic: fewer lit buildings, fewer edits, different regressionRate and churnFiles, and the trace goes quiet during a heavy Task. Stats become truthful per-agent. Pre-field threads and unattributed providers keep it inlined; that inconsistency is invisible in practice, since an old thread has no lens to look inconsistent against.
What this rests on
One fact could not be settled from types: the SDK doc says non-text subagent blocks are emitted "enough for a heartbeat counter" (sdk.d.ts:1600). Whether the forwarded tool_use/tool_result blocks carry full input/result — needed for Targets, i.e. file paths and line ranges — or something abridged must be measured against a live run. If abridged, lenses degrade to shapeless dots and this approach needs rethinking. Ticketed as Measure the fidelity of forwarded subagent tool payloads.
The resolution above argues from "the subagent interior is already in T3's event store, flattened into the main thread and unattributed. The gap is the edge, not the data." That is wrong, and it was disproved by live measurement in Measure the fidelity of forwarded subagent tool payloads.
What a live run of @anthropic-ai/claude-agent-sdk@0.3.170 under T3's own query options actually shows: subagent content arrives only as complete assistant / user messages, never as stream_event — 32 stream events, none carrying parent_tool_use_id. T3's ingest path consumes stream events, so today those subagent tool calls are dropped entirely. They are not in the event store, flattened or otherwise.
So the gap is the edge and the data, not the edge alone.
Every decision here survives that unchanged, which is why this is a pointer rather than a reopen:
Attribution still belongs in T3's model rather than the chat UI.
It is still one field, parentToolCallId on ItemLifecyclePayload.
Option 2 (hybrid JSONL) is still rejected; option 3 (ship without lenses) is still unnecessary.
The main trace still excludes attributed children.
What changed is the work, not the decision: Persist the subagent attribution edge has to emit these items in the first place, not merely tag items that were already flowing. That ticket is written against the corrected picture.
Recorded here because a future session loads this ticket standalone and would otherwise take the flattened-in-the-store claim at face value.
Activity
Dillpickleschmidt commentedon Aug 1, 2026
Option 1, reframed: this is an attribution gap, not an ingest gap.
The ticket's premise — "the subagent's own reads and edits are invisible to the event store" — is wrong for Claude. T3's Claude ingest is the Agent SDK
querystream, not JSONL (ClaudeAdapter.ts:4). The SDK emits subagenttool_use/tool_resultblocks by default, taggedparent_tool_use_id;forwardSubagentTextonly adds their text and thinking on top (sdk.d.ts:1596-1603).parent_tool_use_idappears in T3 only in test fixtures, alwaysnull— no production code reads it.So the subagent interior is already in T3's event store, flattened into the main thread and unattributed. The gap is the edge, not the data.
Option 2 (hybrid) is rejected outright — reading provider JSONL alongside T3's own parsing is exactly the duplicated mental model this repo won't carry. Option 3 (ship without lenses) is unnecessary, because option 1 costs one field.
Decisions
Extend T3's model, not the chat UI. Attribution is captured and persisted in the adapter, contract, and event store — a real T3 capability, correct for every consumer — and consumed only by the 3D surface's trace projection. ChatView and mobile render exactly what they render today. How chat should present nested subagent work is deliberately fog, not part of this port.
One field:
parentToolCallId?: string, top-level onItemLifecyclePayload(providerRuntime.ts:404), holding the launching tool call's id. Nothing else.data.toolCallIdexists; this is that id's parent. No new concept, just an edge between two things T3 already stores.depth,agentPath, oragentId— those are mindwalk's derived shape and would rot the moment T3 disagrees about what an "agent" is. (claudeChildSidecar.SpawnDepthconfirms Claude subagents can nest; the recursive edge handles it natively.)AgentNodeis derivable server-side:label/role/instructionPreviewfrom the launch item's persisteddata(rawsubagent_type+description, not the pre-baked"type: label"summary),launchSeqfromsequence,launchCallIdis the parent id.projectActivityPayloadspreads...payloadand rewrites onlydata, so a top-level field is untouched. No widening of the six-key whitelist that Classify read and search tool calls per provider adapter found impossible.forwardSubagentTextstays off. Mindwalk'sEventis{Seq, Timestamp, Tool, Action, Targets, Outside, ResultBytes, IsError, Summary}— 100% tool-derived (model.go:70). No scene renders subagent prose.No backfill; the retroactivity loss is accepted. Threads already in the event store have their subagent calls flattened with no edge, and the id was never written down — unrecoverable. Old threads get a main trace with subagent work inlined and no lenses. A backfill would mean re-reading
~/.claude/projects/**: the rejected hybrid, except worse — one-shot, local-Claude-only, and dead for remote and relay connections.Lenses are a per-provider capability, expressed through mindwalk's own
traceAvailability. The standing all-providers decision binds the main trace and the citymap, and is untouched; it cannot promise a provider exposes an interior it never records. This is not the hybrid — every provider populates the same one T3 field from its own transport, inside its own adapter. One model, five adapters, no second parsing path.parent_tool_use_idspawn_agentexists in rollouts)collab_agent_tool_callat allThe edge always points at the launching tool call, and adapters do the mapping. Mindwalk has two Codex link methods —
codex-agent-idandcodex-parent-thread-id— so Codex's linkage may be a thread id; if so the Codex adapter resolves it to thespawn_agentcall id it already saw. Provider-specific knowledge belongs in adapters; the contract stays one honest concept.The agent graph is built from launch items alone, so the HUD button is never a lie.
collab_agent_tool_callitems in the main trace — the analogue of mindwalk'sMark{Type:"subagent"}on aTask/Agent/spawn_agentcall. Nothing hidden, nothing special-cased.AgentNode, interior or not.traceAvailabilityisavailablewith attributed children,missingfor a Claude thread recorded before the field ships,unavailablefor a provider with no linkage.AgentRowalready renders a non-available row as disabled-with-status (AgentsPanel.tsx:200-206) — shipped mindwalk behavior, not a hole we invent.stats.subagents > 0guard hides the button. The degradation is already in the ported code.exactby construction. Mindwalk's exact method is literallyAgentLinkMethodClaudeToolUseID, reconstructed by fishingtoolUseIdout of a sidecar (agents.go:24-29); we are handed it. ItslinkQualitygrading describes uncertainty we don't have.Attributed children are excluded from the main trace. Mindwalk's Main lens is the root session file only and never merges subagent work; T3 today inlines it. Being faithful means the main trace drops items carrying a
parentToolCallId— a real behavior change, not cosmetic: fewer lit buildings, fewer edits, differentregressionRateandchurnFiles, and the trace goes quiet during a heavy Task. Stats become truthful per-agent. Pre-field threads and unattributed providers keep it inlined; that inconsistency is invisible in practice, since an old thread has no lens to look inconsistent against.What this rests on
One fact could not be settled from types: the SDK doc says non-text subagent blocks are emitted "enough for a heartbeat counter" (
sdk.d.ts:1600). Whether the forwardedtool_use/tool_resultblocks carry fullinput/result— needed forTargets, i.e. file paths and line ranges — or something abridged must be measured against a live run. If abridged, lenses degrade to shapeless dots and this approach needs rethinking. Ticketed as Measure the fidelity of forwarded subagent tool payloads.Dillpickleschmidt commentedon Aug 1, 2026
Resolved — see the resolution comment above. Follow-on work: Measure the fidelity of forwarded subagent tool payloads → Persist the subagent attribution edge → Project the agent graph and per-agent traces.
Dillpickleschmidt commentedon Aug 1, 2026
Correction to the premise — the decisions stand
The resolution above argues from "the subagent interior is already in T3's event store, flattened into the main thread and unattributed. The gap is the edge, not the data." That is wrong, and it was disproved by live measurement in Measure the fidelity of forwarded subagent tool payloads.
What a live run of
@anthropic-ai/claude-agent-sdk@0.3.170under T3's own query options actually shows: subagent content arrives only as completeassistant/usermessages, never asstream_event— 32 stream events, none carryingparent_tool_use_id. T3's ingest path consumes stream events, so today those subagent tool calls are dropped entirely. They are not in the event store, flattened or otherwise.So the gap is the edge and the data, not the edge alone.
Every decision here survives that unchanged, which is why this is a pointer rather than a reopen:
parentToolCallIdonItemLifecyclePayload.What changed is the work, not the decision: Persist the subagent attribution edge has to emit these items in the first place, not merely tag items that were already flowing. That ticket is written against the corrected picture.
Recorded here because a future session loads this ticket standalone and would otherwise take the flattened-in-the-store claim at face value.