Repository navigation
Conversation
tbroadley
force-pushed
the
fix/subagent-spans
branch
10 times, most recently
from
October 20, 2026 20:37
b49fefb to
77d755a
Compare
… CLI agents `ACPAgent.__call__` replaced `self.ready`, so a caller that started the agent with `start_soon` and then awaited `ready` waited on a stale event forever. `ready` is now renewed at session teardown instead. (A caller still waits forever if the session fails to start, as before.) The default `cwd="/home/user"` doesn't exist in most images. With no `cwd`, all three ACP agents (Claude Code, Codex, Gemini CLI) now use `resolve_agent_cwd`, like the CLI agents: the sandbox's working directory, or the user's home when that is `/`. Behaviour change, for callers that pass no `cwd`: - the session runs there rather than in `/home/user`; - skills (`.claude/skills`, `.gemini/skills`) and ACP Codex's `AGENTS.md`/`.codex/` are written there; - `agent.cwd` is "" until the agent is called. `cwd=""` is resolved the same way as no `cwd`.
Claude Code launches sub-agents in the background by default. Their span closed at the `async_launched` tool result, which is only a launch acknowledgement, so every model call the sub-agent made afterwards was recorded in the parent's span. Background spans now stay open until the sub-agent's task notification (or `reset()`): the `system` task_notification event, or, for Claude Code versions that print none, the `<task-notification>` in the parent's `user` line. A call whose history holds the launching tool call (the parent, quoting the prompt) is not attributed to the sub-agent. Remote agents and per-block tool results are handled. A notification with no tool_use_id is matched by the task id, read from the structured result or, failing that, the result text.
tbroadley
force-pushed
the
fix/subagent-spans
branch
7 times, most recently
from
October 21, 2026 14:21
36f08b1 to
d91d1c0
Compare
… own spans - Forks are matched by their "Fork started" placeholder and directive, so parallel forks are no longer attributed to each other. - Each resumed run (SendMessage or a queued message) gets its own `agent-<id>-runN` span, opened from the SendMessage, lazily from a run boundary in the call's history, or from `task_started`. Run ids are matched to runs in order, so a lagging `task_started` doesn't open a spurious run, and a failed SendMessage starts no run. - Claude Code's own in-run turns (output-limit recovery, stop-hook feedback, compaction, ...) are not counted as run boundaries; the classifier mirrors Claude Code 2.1.303's. - A sub-agent that compacts is recognised by its summary, which starts its history from then on. - A call whose history holds a tool call one of a sub-agent's earlier calls made, starts with its prompt or summary, and holds no launches but its own, is that sub-agent's. So agents launched with the same prompt get their own spans: identical first calls go to each in launch order (a failed or refused call's retry keeps its agent). - A finished sub-agent only matches a call whose history starts with its prompt, so the main agent quoting it (e.g. after compaction) stays in its own span. Prompt matches are ranked, ties between agents with the same prompt go to the outer span, and agent names are scoped to the launching agent. - A run we open without seeing who resumed it goes under its launcher's current span, not the span current at launch, which may have ended (a checkpoint span, or an earlier run of the launcher). The consumer is split up to keep this tractable: - `subagents`: the sub-agents and their runs, with lookups by each id and the invariants in one place; - `attribution`: which sub-agent made a call (pure); - `shapes`: what Claude Code's messages look like (all the version-specific text); - `live_consumer`: span lifecycle, from the bridge and stream-json. A failure in this tracking is logged, and costs the call its span (it goes to the outer one) rather than failing the model call. Tests make such failures raise. Recorded Claude Code runs are replayed in tests, checking each call's span and when each span opens and closes. 2.1.303: foreground, background, SendMessage and queued resumes, parallel background agents, agents with the same prompt, parallel and resumed forks, a nested agent, a blocking SubagentStop hook, and compaction of a sub-agent and of the main agent beside one; 2.1.304: a resumed fork, and a compacting sub-agent (its continuation lists the tools used before the summary).
The ACP agents (`interactive_claude_code`, `interactive_codex_cli`) installed no `ModelEventSink`, so every sub-agent model call landed in the parent's span. Both now install their consumer and reset it after the bridge exits. ACP Claude Code has no stream-json, so `BridgeOnlyLiveConsumer` closes spans when the sub-agent's run ends, or from the result or `<task-notification>` the parent sees. Calls after a reply-closed run stay in that run's span. Each parent message is handled once, so a retried request (e.g. after a refusal) doesn't close a later run, and a launch acknowledgement that arrives after the run ended is still counted. Installing a `ModelEventSink` turns off the bridge's streaming of partial model output to the transcript for these agents, as for the CLI agents.
tbroadley
force-pushed
the
fix/subagent-spans
branch
from
October 21, 2026 17:00
d91d1c0 to
69d79e1
Compare
1 of 4 tasks
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
✨ claude-opus-5-5 | Comment not reviewed by tbroadley
Part of #131. Replaces #114, which fixes only the first case below. Touches the same code as #111, so whichever lands second needs a rebase.
Problem
Some sub-agents record their model calls in the parent's agent span:
Tools that rebuild each agent from its span (e.g. Inspect Scout) lose the sub-agent's work, and can replace the main agent's history when a run ends mid-sub-agent.
Separately, the ACP agents were hard to drive: a caller awaiting
readyafterstart_soonblocked forever, and the defaultcwd="/home/user"doesn't exist in most images.Fix
One commit per change:
readyandcwd.readyis renewed at session teardown. With nocwd, all three ACP agents useresolve_agent_cwd, like the CLI agents. Behaviour change: sessions, skills and ACP Codex'sAGENTS.md/.codex/go to the image's working directory instead of/home/user.reset().agent-<id>-runNspan. A compacted sub-agent is recognised by its summary. Claude Code's own in-run turns (output-limit recovery, hook feedback, compaction) don't count as new runs. The consumer is split intosubagents(state and invariants),attribution(which sub-agent made a call),shapes(Claude Code's version-specific text) andlive_consumer(span lifecycle).BridgeOnlyLiveConsumer, which closes spans when the run ends or when the parent sees its result. As with the CLI agents, this turns off the bridge's streaming of partial model output to the transcript for these agents.Tests
make checkis clean.-runNspans, main-agent calls in the outer span, and every span closed when its run ended:test_claude_code_subagent_spans_replay.py, which checks each call's span and when each span opens and closes.Agent review
Two pairs of independent reviewers (gpt-6-astra, claude-opus-5-5), seven rounds, about 50 findings, each reproduced with a failing test. The correctness findings are fixed with regression tests, including ones that put calls in the wrong span: agents with the same prompt, the main agent after compaction, and lagging or failed resumes.