Where each kind of failure should be handled
bottom-up: a failure travels upward only as far as it must
- Final outputshows coverage gaps to the reader
- Coordinatorreroutes, retries differently, or proceeds partially
- Subagentretries transient errors, reports what remains
- Toolreturns a clear, typed error —
is_error/isError
Why multi-agent errors are different
In a single loop, a tool error lands straight back in the model’s context and it can react. In a coordinator–subagent system there is a second hop. A subagent runs in its own context, and only its final message returns to the coordinator as the Agent tool result — the coordinator never sees the tool calls, error messages and retries that happened inside. Whatever the subagent writes in that final message is all the coordinator knows. If it says nothing about a failure, the failure has disappeared.
Anthropic’s write-up of its multi-agent research system names the consequence: agents are stateful and long-running, so errors compound, and a minor failure early on can send the rest of the run down the wrong path. Their fixes combine the model’s adaptability with deterministic safeguards — telling the agent when a tool is failing and letting it adapt, plus retry logic, regular checkpoints, and resuming from where the error occurred instead of restarting from scratch.
The two failure modes, and the fix
Silent suppression
- Subagent catches a timeout, returns
[] - Coordinator reads “no results found”
- Report omits a whole topic, looks complete
- Nobody knows a search never ran
Structured propagation
- Subagent retries, then reports status
failed - Includes what was tried and partial results
- Coordinator reroutes or proceeds knowingly
- Report states the gap and why
The opposite extreme is just as bad. If any single subagent error raises an exception that ends the whole task, one flaky data source throws away the work of every other subagent. The goal is neither silence nor collapse, but a failure that arrives, intact, at the level that can decide what to do.
Empty is not the same as failed
The most important distinction in this task statement is between a valid empty result and an access failure. “The search ran and found no matching filings” is a finding; the coordinator should trust it. “The search timed out” means nothing is known; the coordinator should retry, reroute or flag it. If both come back as an empty list, the system cannot tell them apart — and it will report absence of evidence as evidence of absence.
The protocols already give you the tools for this at the lowest level. In the Messages API, a failed tool call returns a tool_result with is_error: true and an instructive message; the documentation advises saying what went wrong and what to try next, such as a rate limit with a retry delay, rather than a bare “failed”. The MCP specification separates protocol errors (unknown tool, malformed request) from tool execution errors, which come back as a normal result with isError: true — API failures, invalid inputs, business-rule violations — and says clients should pass execution errors to the model so it can self-correct. (Designing those error payloads is covered in 2.2.)
| Kind of failure | Retry locally? | What to pass up |
|---|---|---|
| Transient: timeout, rate limit, overload | Yes, a bounded number of times | Only if retries run out — with attempts made |
| Invalid input: bad date format, unknown ID | Yes, with corrected input | Only if it cannot be corrected |
| Permission or access denied | No | Immediately — someone must grant access or reroute |
| Business rule: over limit, not allowed | No | Immediately — it is a decision, not a glitch |
| Valid empty result | No — it is not an error | As a finding, clearly marked as searched-and-empty |
What a subagent should hand back
Because the final message is the whole interface, design it as a contract. Ask each subagent (in its system prompt, or via structured output) to return a status, its findings, and — when anything went wrong — the failure type, what it attempted, any partial results, and what it suggests. The coordinator can then act on fields rather than guess from prose.
{
"status": "partial",
"subtopic": "Competitor pricing, Nordic region",
"findings": [
{ "claim": "Brand X cut entry-tier prices 8% in August",
"source": "https://example.com/brandx-q3", "retrieved": "2026-09-20" }
],
"failures": [
{ "type": "transient",
"what": "search_news timed out",
"attempts": 3,
"retryable": true,
"not_covered": "Brand Y and Brand Z news since June" }
],
"suggestion": "Retry news later, or use company press feeds"
}Current Claude Code builds help at this boundary too. The subagent documentation says that a subagent ending on an API error — a usage limit or repeated server error — now reports that failure to the coordinator rather than passing error text off as findings; a foreground subagent that had already written output returns it marked as cut off, and a background one is marked failed with its last output attached. A subagent that stops at its maxTurns limit has its output marked as partial, and it can be resumed to continue instead of being restarted.
A search subagent times out
is_error: timeoutpartial + failure detailsWhat the coordinator does with a failure
Coordinator options on a reported failure
- Retryable, time allowsRetry or resumeresume, don’t restart
- Other source can cover itReroutedifferent agent or tool
- Gap is minorProceed, annotatestate the gap in output
- Gap breaks the answerStop and escalatewith partial results
Traps the wrong answers are built from
| Tempting but wrong | Do this instead |
|---|---|
| Catching subagent errors and returning an empty result as success | Return a status of failed or partial with the error type, attempts and partial results. |
| Aborting the whole workflow when one subagent fails | Let the coordinator reroute, retry differently, or proceed with the gap annotated. |
| Returning a bare “operation failed” message | Say what failed, whether it is retryable, and what to try next. |
| Retrying permission or business-rule errors | Retry only transient and correctable errors; propagate the rest immediately. |
| Restarting a long run from scratch after a late failure | Checkpoint progress and resume from where the error occurred. |
You should now be able to
- Decide which errors a subagent should recover from locally and which it should propagate.
- Design a subagent result format that carries status, failure type, attempts and partial results.
- Distinguish access failures from valid empty results in tool and subagent outputs.
- Use
is_errorin tool results and know how MCP separates protocol from execution errors. - Choose between retrying, rerouting, proceeding with annotated gaps and escalating at the coordinator.