Repository navigation
[Bug] Terminated sub-agent output is unrecoverable: no closing message, no registry entry, no read-back path #9124
Replies: 2 comments 1 reply
Follow-up & Connection to #9056I noticed #9056 was posted a day earlier with a very similar root-cause symptom. Connecting the two highlights a broader architectural gap in the subagent lifecycle:
|
|
I'd treat this as a task-lifecycle and output-durability problem, rather than relying entirely on the subagent's final message. A useful design would separate the task's execution state from its produced output:
As a practical workaround, asking each subagent to write its report to a file early is sensible. However, that should be a fallback rather than a requirement for every agent prompt. I'd add tests for termination before any output, termination after partial output, cancellation after a complete report, and normal completion. Those cases should produce distinguishable results so the parent can decide whether to retry or recover the existing output. The general principle is that task completion and result delivery are separate concerns. Making the result durable removes a fragile dependency on one final message. Hope this helps! If this answers the issue, would you consider marking it as the accepted answer? |
Uh oh!
There was an error while loading. Please reload this page.
Summary
When a sub-agent dispatched through the
subagenttool terminates instead of settling normally, the parent agent receives a settlement notice that carries no closing message, the finished child disappears fromlist_agents, andsend_messageto it fails. There is no tool in the parent's toolset that can read the child's output afterwards.For any workflow whose entire value is the child's report, one early termination therefore destroys the deliverable silently — and the parent cannot even tell what was lost, or whether anything was produced at all.
Environment
0.2.0-rc.2(local build; worktree at commita360dd5da7, tagdsh-v0.2.0-rc.2merged on branchlocal-build-and-deploy)v24.21.0subagent({ run_in_background: true })→ returns{ kind: "continuable", subagentId }spawn_teammateteammates were created (relevant to E2 below)What happened (parent-side observation)
Six sub-agent dispatches in one session. Two terminated before settling and their output was unrecoverable:
subagent(review round 1)subagent(review round 2)subagentwith a "write your report to a file first" instruction in the promptThe user could see the two lost reports in the UI, which is how we know content existed. The parent agent had no way to read them. (Dispatches 3–6 are also the workaround — see the end.)
Evidence
E1 — settlement notice without a closing message
Terminated child:
Normally settled child, same session, same tool:
The absence of the
Its closing message:block is the only signal — the notice does not say why there is none.E2 — a finished child is not addressable
packages/subagent/tool-subagent-control/src/list-agents.tsaccepts ascope: 'children' | 'descendants'request, but neither scope surfaces a child that has already finished. The error text says "teammate", and in this session no teammates existed — so the lookup appears to be against the live agent registry, from which a settled child is gone.E3 — no read-back in the tool surface
The parent's sub-agent controls are
subagent,subagent_fork,send_message,list_agents,interrupt_agent,list_subagent_models. None reads a child's transcript or output.job_outputis documented for background jobs (shell), not sub-agents.Contradiction with the documented design
The source reads as though termination should preserve output:
packages/subagent/subagent/src/assistant-output.ts— "select the last non-empty assistant message … Selection is independent of the run's stop reason."packages/subagent/subagent/src/lifecycle.ts:35—readonly output?: readonly ContentBlock[]; "The epoch's final assistant content, absent when it produced none or failed."packages/subagent/subagent/src/continuation-messages.ts:139-148— the settlement notice buildssummaryfromsettlementSummary(childId, terminal.stopReason)andclosingTextfromterminal.output.out-of-process.ts:196,200→{ output: parts.collectOutput(), stopReason: 'aborted' };subagent-dsh-sdk/src/run.ts:326→ the same forturn === 'cancelled'.packages/subagent/tool-subagent/src/index.ts:386— "returns a subagent id you can continue withsend_message".So exactly one of two things is true, and the parent cannot distinguish them:
lifecycle.ts:35's "produced none or failed"). Then the missing notice payload is expected — but the notice should still say so, and there should still be a way to inspect what happened.The ambiguity is itself the defect: with no reason string and no read-back, a losing dispatch is indistinguishable from a child that never got started. It also means case 2 cannot be ruled out from the parent side.
Why it matters
job_outputand stay readable after settling. Sub-agents do not. The same harness gives you a read-back path for a shell command but not for a delegated agent.send_messagecontinuation implies an addressable history around a child; for finished children there is none.Suggested directions
AssistantOutputFold), bounded if necessary.subagent_output(id)tool, or makelist_agents(scope: 'descendants')include finished children with an output reference — mirroring whatjob_outputalready does for shell jobs.send_message" reads as indefinite; if continuation only works while the child is live, it would help to say so explicitly.Workaround for anyone hitting this
Put "write your final report to
<path>as your first action, then return a short closing message" in every dispatch prompt.After adopting this, all four subsequent dispatches preserved their output regardless of how they ended, and the reports were on disk before the process settled — so the parent could recover them even from a failed notice. It costs one line of prompt and removes the single point of failure.
(The general lesson: treat a child's closing message as a best-effort channel and give the deliverable a durable address. But a harness should not require every user to discover that independently.)
All reactions