Expand description
Did this subagent actually finish, or was it cut off?
SubAgentStatus::Abandoned is — as its own doc comment says — “always an
inference, never an observation”. Until this module existed, the inference
was “the subagent transcript has no terminal Result line, therefore it was
interrupted” (scan.rs::reconcile_stale_subagents). That premise does not
hold: no AgentMux transcript contains a "type":"result" line at all —
not the subagent sidechain files, not the parent session file. The line type
the old check waited for is absent from the format this deployment writes
(entrypoint: sdk-cli), so SubAgentStatus::Completed was unreachable and
every Agent-tool dispatch ended up displayed as “interrupted”, including the
ones that returned full results the parent went on to use.
Evidence and the full chain:
docs/reports/REPORT_SUBAGENT_COMPLETION_NEVER_DETECTED_2026_09_05.md.
The replacement uses the strongest evidence available, and it is an
observation rather than an inference: the parent’s own tool_result for
the dispatch’s tool_use_id. If the parent recorded a result, the subagent
demonstrably returned — whatever its transcript does or doesn’t end with.
The correlation key is already on disk: Claude Code writes an
agent-<id>.meta.json sidecar next to each subagent transcript carrying the
parent-side toolUseId.
Deliberately kept free of watcher state so the decision is unit-testable on
its own — the same reasoning that split parse.rs out.
Structs§
- Subagent
Meta 🔒 - One subagent’s
.meta.jsonsidecar. Only the fields we use are modelled; the sidecar also carriesagentType/spawnDepth/model.serdeignores the rest.
Constants§
- DESCRIPTION_
NAME_ 🔒MAX_ CHARS - Longest description kept as a name. The tool asks for 3–5 words; this only stops a runaway one from filling the row.
Functions§
- description_
for 🔒 - The subagent’s display name from the parent’s description of its task, read
from the same sidecar.
Nonewhen there is none (a workflow member, a sidecar not written yet); the subagent is then named by Haiku on demand. - display_
name_ 🔒from_ description - A description as a one-line name: whitespace collapsed, capped,
Noneif nothing is left. - parent_
tool_ 🔒results - Every
tool_use_idthe parent transcript has recorded atool_resultfor, mapped to that result’sis_errorflag. - parent_
tool_ 🔒results_ at - Read an already-resolved parent transcript. An unreadable/absent file
yields an empty map, which makes
terminal_statusfall back to the oldAbandonedinference — conservative, and identical to pre-fix behaviour. - parent_
transcript_ 🔒for - The parent session transcript for a subagent transcript.
- read_
meta 🔒 - sidecar_
path 🔒 …/agent-<id>.jsonl→…/agent-<id>.meta.json.- terminal_
status 🔒 - What an
Activesubagent’s status should become once its parent turn is confirmed idle. - tool_
use_ 🔒id_ for - The parent-side
tool_use_idfor a subagent transcript, read from its sidecar.Nonewhen there is no sidecar, it is unreadable, it is malformed, or it predates the field — all of which must degrade to the old conservative behaviour rather than erroring. - transcript_
for_ 🔒sidecar …/agent-<id>.meta.json→…/agent-<id>.jsonl;Nonefor any other file. The inverse ofsidecar_path, so the watcher can route a sidecar write to the transcript whosetool_use_idit carries.