feat(lineage): confirm Claude subagent parent edges from the parent's Agent PostToolUse (#422) - #486
Conversation
🦋 Changeset detectedLatest commit: 6992c89 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 331cd59029
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const confirmation = event === 'tool/after' && toolName !== undefined && SPAWN_TOOLS[host](toolName) | ||
| ? SPAWN_CONFIRMATIONS[host](native) | ||
| : undefined; |
There was a problem hiding this comment.
Ignore Agent resume responses when confirming spawn edges
When Claude invokes Agent with tool_input.resume for an existing subagent, its PostToolUse still identifies that existing conversation via tool_response.agentId; Claude Code documents that an agent ID resumes the existing subagent. Treating every such response as a new spawn confirmation overwrites the original toolCallId, and if another conversation resumes the agent, spawnConfirmed also reparents it and rebases its descendants even though no new lineage edge was created. Exclude resume calls from this confirmation path.
Useful? React with 👍 / 👎.
…PostToolUse (#422) Claude Code names no parent on any hook a subagent emits, but the parent's Agent PostToolUse carries the spawn tool_use_id, the caller's identity and tool_response.agentId, the child. The registry treats that hook as the host's word on the edge: it confirms a spawn-window match (resolution: confirmed once every edge to the root is host-named), fills in sibling toolCallIds claimed blind, places a start no window could (unplaced starts keep the start's own facts meanwhile), moves a child filed under the wrong parent and re-bases its descendants, and holds a child named before its start arrives. Adds 'confirmed' to AgentLineageResolution and the wire/Workbench mirrors, records the confirmation on the Claude capability table's lineage rows, and renders a Conversation lineage section on the generated hosts page.
…egression tests on the 2.1.259 orchestration capture (#422)
…he worktree-proximity provenance enum (#422)
331cd59 to
6992c89
Compare
Framework-side support for the Claude Code lineage gaps tracked in #422 — no vendor involvement. Refs #422 (does not fix it:
SubagentStartand the subagent's own hooks still carry no parent; the framework now takes the host's own after-the-fact word for the edge instead of inferring alone).Gaps, verdicts, what changed
SubagentStartnames no parentclaude-2.1.259-orchestration.ndjsonrow 82 (depth-2 child):{session_id, agent_id: "ac26f1…", agent_type}— nothing naminga5dc65…; same at depth 1 (rows 14, 16, 65)AgentPostToolUsecarries the spawntool_use_id, the parent'sagent_id(absent for the root) andtool_response.agentId= the child (rows 13, 17, 99, 101). The registry behindrequest.lineagetreats that as the host naming the edge: it confirms the spawn-window match (resolution: 'confirmed'once every edge to the root is host-named), fills insubagent.toolCallIdfor siblings claimed blind, places a start no window could, moves a mis-filed child (re-basing descendants), and holds a child named before its start (row 13 precedes row 14).agent_id+ the rootsession_idSubagentStart; a foreground child only after itsSubagentStop, so its own events stayregistryfor life.task_started.spawn_depthis not a hook fieldconfirmedonly when the whole chain is. Matchedspawn_depth1 and 2 in the orchestration run.agent_transcript_path/transcript_pathas a parent key<session>/subagents/agent-<agent_id>.jsonlfor depth-1 and depth-2 alike;transcript_pathis the root's on every hookbackground_tasks[]onStop/SubagentStopas an edge source[{id, type: "subagent", status, description, agent_type}]— notool_use_id, no parentPreToolUse(plugin installed mid-turn, runtime restart)Envelope evidence (
fixtures/host-lineage/claude-2.1.259-orchestration.ndjson, PR #455, live Claude Code 2.1.259)Also
claude-2.1.257.ndjsonrows 17/41 andclaude-2.1.257-foreground.ndjsonrows 47/49 (the sametool_response.agentIdshape in 2.1.257).Docs cited (maintainer-uploaded):
hooks-2.md— "Common input fields" (agent_id/agent_typeare the only subagent fields hooks add), "SubagentStart"/"SubagentStop" (agent_transcript_pathunder the parent's transcript folder,background_tasksscoped to the parent session), "PostToolUse" (tool_use_id,tool_response);sub-agents-3.md— "Resume subagents" (Claude receives the subagent's agent ID on completion — the id the registry matches; one-shot Explore/Plan agents are documented as returning none, though the 2.1.259 background Explore response did carryagentId; a response without one keeps the registry's own match).Implementation
packages/rsc-runtime/src/lineage/state.ts—LineageNode.confirmed,UnplacedStart(+ parkedconfirmations),SpawnConfirmationSchema,spawnConfirmed/startUnplacedevents; reducer places/confirms/moves, re-bases descendants, applies parked confirmations when the parent is placed, drops unplaced starts with their retired root. New fields are optional, so persisted journals rehydrate unchanged.packages/rsc-runtime/src/lineage/registry.ts—SPAWN_CONFIRMATIONS.claudereadstool_response.agentId/status; thetool/afterpath dispatchesspawnConfirmed(also when the carrier is an unplaced start);observeStartdispatchesstartUnplacedwhen no spawn can be claimed;resolvereportsconfirmedonly when every edge up to the root is host-named.AgentLineageResolutiongains'confirmed'(@agent-bundle/runtime), mirrored inagent-bundleRequestLineageProvenanceand the workbench schema.packages/agent-bundle/src/adapters/capabilities/claude-2.1.250.json—lineage.parent/lineage.depthreasons + evidence (rows above, withheld-row replays, transcript-path andbackground_tasksshapes); the generated hosts page gains a "Conversation lineage" section (website/plugins/generated-reference.ts).docs/entry-conventions.md,website/docs/{en,zh}/guide/authoring/mcp.mdx,docs/audits/2026-09-03-host-lineage-matrix.md(open-item row closed).examples/worktree-proximity(src/state.ts, README): its identity-provenance enum, which mirrorsAgentLineageResolutionsince chore(examples): use built-in framework surface over hand-rolled setup #473, gainsconfirmed(the Examples check failed on the merge commit without it).Agentcall withtool_input.resume(sub-agents reference, "Resume subagents": a resume starts a new run under the same agent id) is not a spawn: it opens no spawn window, a failure discards no claim, and itsPostToolUseagentIdconfirms nothing — so a resume from another conversation cannot re-parent the resumed agent or overwrite itstoolCallId(SPAWNS_NEW_AGENTinregistry.ts).Tests
packages/rsc-runtime/tests/lineage-registry.test.ts(54 passing):confirmedfor every hook, general-purposeregistryat its start thenconfirmed, sequential and nestedregistryfor life; every child nodeconfirmed: truewithtoolCallId= its spawn call; Explore held from row 13 and completed by row 14 (startedAt,type).id-not-resolvableuntil row 99 places it under the sequential agent with row 82/98 times.Agentcall withtool_input.resumeopens no window, a start arriving meanwhile cannot claim it, and itsPostToolUsenaming the resumed agent leaves that agent's parent andtoolCallIduntouched.toolCallIdfill-in, contradiction re-parenting with descendants, completed materialisation, replay idempotency, invalid confirmations ignored.confirmed.pnpm lint,pnpm typecheck,test:unit(3040),test:route-unit(44),test:projection(145),pnpm docs:site:buildall green locally.Review status
331cd59(one P2 thread,registry.ts: resume responses must not confirm spawn edges) — addressed in the next push withSPAWNS_NEW_AGENTand the resume test above. No PR comments are posted per maintainer instruction; this section is the record.6992c89be: all checks green exceptRelease gates, which fails inpnpm audit:release(npm auditagainst the retired npm advisories endpoint) exactly as onmain(20a8150df,d9e4589f5,ad3bd24e5); merged under the maintainer's merge-gate relaxation for that job.