Skip to content

feat(lineage): confirm Claude subagent parent edges from the parent's Agent PostToolUse (#422) - #486

Merged
ScriptedAlchemy merged 3 commits into
mainfrom
feat/claude-host-gaps-in-framework
Sep 4, 2026
Merged

feat(lineage): confirm Claude subagent parent edges from the parent's Agent PostToolUse (#422)#486
ScriptedAlchemy merged 3 commits into
mainfrom
feat/claude-host-gaps-in-framework

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Framework-side support for the Claude Code lineage gaps tracked in #422 — no vendor involvement. Refs #422 (does not fix it: SubagentStart and 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

Gap (#422) Envelope evidence Verdict What the framework does now
SubagentStart names no parent claude-2.1.259-orchestration.ndjson row 82 (depth-2 child): {session_id, agent_id: "ac26f1…", agent_type} — nothing naming a5dc65…; same at depth 1 (rows 14, 16, 65) Supported (derived, self-correcting) The parent's Agent PostToolUse carries the spawn tool_use_id, the parent's agent_id (absent for the root) and tool_response.agentId = the child (rows 13, 17, 99, 101). The registry behind request.lineage treats 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 in subagent.toolCallId for 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).
A subagent's own hooks name no parent rows 83–97: every hook of the depth-2 child carries only its agent_id + the root session_id Supported the same way The child's events resolve through its node; the node's edge is confirmed by rows 99/101. Timing is honest: a background child is confirmed right after (2.1.259: before) its SubagentStart; a foreground child only after its SubagentStop, so its own events stay registry for life.
Depth not delivered no depth field on any hook; stream-json task_started.spawn_depth is not a hook field Derived Depth = placed parent's depth + 1, re-based when a confirmation moves the parent; confirmed only when the whole chain is. Matched spawn_depth 1 and 2 in the orchestration run.
agent_transcript_path / transcript_path as a parent key rows 57, 61, 98, 100: <session>/subagents/agent-<agent_id>.jsonl for depth-1 and depth-2 alike; transcript_path is the root's on every hook Host-blocked Flat; cannot bind a nested child to its parent. Recorded in the capability evidence.
background_tasks[] on Stop/SubagentStop as an edge source row 34: [{id, type: "subagent", status, description, agent_type}] — no tool_use_id, no parent Host-blocked A flat set of running background children; no edges.
Missed spawn PreToolUse (plugin installed mid-turn, runtime restart) replay of the capture with row 64 or row 81 withheld Supported An unplaced start keeps id/type/time/stop and any confirmations it issued for its own children; rows 99/101 then place the sequential agent under the root and the depth-2 child under it with the start/stop times the hooks carried.

Envelope evidence (fixtures/host-lineage/claude-2.1.259-orchestration.ndjson, PR #455, live Claude Code 2.1.259)

row 12: PreToolUse  root  Agent tool_use_id=…tiwmPd subagent_type=Explore
row 13: PostToolUse root  Agent tool_use_id=…tiwmPd tool_response={isAsync:true,status:"async_launched",agentId:"aa618caf4dda3c6e7"}
row 14: SubagentStart agent_id=aa618caf4dda3c6e7 agent_type=Explore             ← after its own PostToolUse
row 15: PreToolUse  root  Agent tool_use_id=…hwTns2 subagent_type=general-purpose
row 16: SubagentStart agent_id=aa75e336e8b0d3b74 agent_type=general-purpose
row 17: PostToolUse root  Agent tool_use_id=…hwTns2 tool_response={isAsync:true,status:"async_launched",agentId:"aa75e336e8b0d3b74"}
row 64: PreToolUse  root  Agent tool_use_id=…D8rWgf run_in_background=false
row 65: SubagentStart agent_id=a5dc6534fe667e88c
row 81: PreToolUse  agent_id=a5dc6534fe667e88c Agent tool_use_id=…3VEKa4 run_in_background=false
row 82: SubagentStart agent_id=ac26f10e077adc16f                                 ← depth 2; names no parent
row 98: SubagentStop  agent_id=ac26f10e077adc16f agent_transcript_path=…/<session>/subagents/agent-ac26f10e077adc16f.jsonl background_tasks=[]
row 99: PostToolUse agent_id=a5dc6534fe667e88c Agent tool_use_id=…3VEKa4 tool_response={status:"completed",agentId:"ac26f10e077adc16f"}
row 100: SubagentStop agent_id=a5dc6534fe667e88c
row 101: PostToolUse root Agent tool_use_id=…D8rWgf tool_response={status:"completed",agentId:"a5dc6534fe667e88c"}

Also claude-2.1.257.ndjson rows 17/41 and claude-2.1.257-foreground.ndjson rows 47/49 (the same tool_response.agentId shape in 2.1.257).

Docs cited (maintainer-uploaded): hooks-2.md — "Common input fields" (agent_id/agent_type are the only subagent fields hooks add), "SubagentStart"/"SubagentStop" (agent_transcript_path under the parent's transcript folder, background_tasks scoped 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 carry agentId; a response without one keeps the registry's own match).

Implementation

  • packages/rsc-runtime/src/lineage/state.tsLineageNode.confirmed, UnplacedStart (+ parked confirmations), SpawnConfirmationSchema, spawnConfirmed/startUnplaced events; 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.tsSPAWN_CONFIRMATIONS.claude reads tool_response.agentId/status; the tool/after path dispatches spawnConfirmed (also when the carrier is an unplaced start); observeStart dispatches startUnplaced when no spawn can be claimed; resolve reports confirmed only when every edge up to the root is host-named.
  • AgentLineageResolution gains 'confirmed' (@agent-bundle/runtime), mirrored in agent-bundle RequestLineageProvenance and the workbench schema.
  • packages/agent-bundle/src/adapters/capabilities/claude-2.1.250.jsonlineage.parent/lineage.depth reasons + evidence (rows above, withheld-row replays, transcript-path and background_tasks shapes); the generated hosts page gains a "Conversation lineage" section (website/plugins/generated-reference.ts).
  • Docs: 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 mirrors AgentLineageResolution since chore(examples): use built-in framework surface over hand-rolled setup #473, gains confirmed (the Examples check failed on the merge commit without it).
  • An Agent call with tool_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 its PostToolUse agentId confirms nothing — so a resume from another conversation cannot re-parent the resumed agent or overwrite its toolCallId (SPAWNS_NEW_AGENT in registry.ts).

Tests

packages/rsc-runtime/tests/lineage-registry.test.ts (54 passing):

  • docs(lineage): live Claude Code 2.1.259 orchestration capture (parallel, sequential, nested spawns; resumed turns; /compact) #455 orchestration replay now asserts per-event resolution from the confirmation rows (13/17/99/101): Explore confirmed for every hook, general-purpose registry at its start then confirmed, sequential and nested registry for life; every child node confirmed: true with toolCallId = its spawn call; Explore held from row 13 and completed by row 14 (startedAt, type).
  • Fixture slices: rows 1–13/14/15–17 (confirmation before start, redelivered start, next spawn claimed by its own child).
  • Fixture with row 81 withheld: depth-2 child unplaced and id-not-resolvable until row 99 places it under the sequential agent with row 82/98 times.
  • Fixture with row 64 withheld: sequential agent and depth-2 child both unplaced; row 99's confirmation parks on the unplaced parent; row 101 places both.
  • Resume: an Agent call with tool_input.resume opens no window, a start arriving meanwhile cannot claim it, and its PostToolUse naming the resumed agent leaves that agent's parent and toolCallId untouched.
  • Synthetic cases the host did not produce live (it serialised the parallel pair): two parents with one pending spawn each, sibling cohort toolCallId fill-in, contradiction re-parenting with descendants, completed materialisation, replay idempotency, invalid confirmations ignored.
  • 2.1.257 background/foreground replays updated for confirmed.

pnpm lint, pnpm typecheck, test:unit (3040), test:route-unit (44), test:projection (145), pnpm docs:site:build all green locally.

Review status

  • Codex reviewed 331cd59 (one P2 thread, registry.ts: resume responses must not confirm spawn edges) — addressed in the next push with SPAWNS_NEW_AGENT and the resume test above. No PR comments are posted per maintainer instruction; this section is the record.
  • Head 6992c89be: all checks green except Release gates, which fails in pnpm audit:release (npm audit against the retired npm advisories endpoint) exactly as on main (20a8150df, d9e4589f5, ad3bd24e5); merged under the maintainer's merge-gate relaxation for that job.

@changeset-bot

changeset-bot Bot commented Sep 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6992c89

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@agent-bundle/runtime Patch
agent-bundle Patch

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

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-04T00:04:40.514768Z 331cd59 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@pkg-pr-new

pkg-pr-new Bot commented Sep 4, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle@486
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@486
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@agent-bundle/runtime@486

commit: 6992c89

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +602 to +604
const confirmation = event === 'tool/after' && toolName !== undefined && SPAWN_TOOLS[host](toolName)
? SPAWN_CONFIRMATIONS[host](native)
: undefined;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant