Local Recovery vs Escalation
Retry transient faults; escalate the non-recoverable.
Local Recovery vs Escalation is a free Claude Architect lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Claude Architect learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Two Ways a Step Can Fail
Inside an agentic system, a single tool call or subagent step can fail for very different reasons. The architect's job is to classify the failure before reacting.
- Transient fault — a momentary, self-correcting problem: a network blip, a rate limit, a brief timeout. Retrying the exact same call may just work.
- Non-recoverable failure — a structural problem: invalid credentials, a missing permission, a malformed request, or a business rule violation. Retrying changes nothing.
The core rule of this lesson: recover transient faults locally, escalate the non-recoverable.
Local Recovery: Keep It in the Subagent
In a hub-and-spoke multi-agent system, the coordinator delegates work to subagents. When a subagent hits a transient fault, it should try to fix it where it happened — without bubbling noise up to the coordinator.
This keeps the coordinator focused on orchestration instead of low-level retries, and it preserves the coordinator's context budget. Local recovery is the first line of defense.
def run_tool_with_local_recovery(tool, args, max_attempts=3):
for attempt in range(max_attempts):
result = tool(**args)
if not result.get("isError"):
return result
# Only retry faults the result says are retryable
if result.get("isRetryable") and result.get("errorCategory") == "transient":
continue
break # validation / business / permission -> stop, escalate
return result # hand the structured error upwardLet the Error Tell You What to Do
You can only route intelligently if the failure is structured. A generic "Operation failed" blocks recovery — the agent can't tell a rate limit from a permission denial.
A well-designed MCP tool returns an error envelope:
isError: trueerrorCategory:transient/validation/business/permissionisRetryable: booleanmessage,attempted_query,partial_results
The errorCategory drives the decision: transient is a retry candidate; validation, business, and permission are not.
{
"isError": true,
"errorCategory": "transient",
"isRetryable": true,
"message": "Upstream timeout after 5s",
"attempted_query": "SELECT * FROM orders WHERE customer_id = 4821",
"partial_results": []
}Failure vs Empty Result
A subtle but exam-critical distinction: an access failure is not the same as a valid empty result.
- Failure — the query never completed (timeout, auth error). The data is unknown. This might be retryable.
- Empty — the query ran successfully and found nothing.
0 rowsis a correct, final answer. Retrying is pointless and misleading.
Conflating the two leads agents to retry forever on legitimate "no matches found" cases, or to report a real outage as "no data."
if result.get("isError"):
handle_failure(result) # access failure: maybe retry / escalate
elif len(result["rows"]) == 0:
return "No matching records found." # valid empty result, DONE
else:
return result["rows"]When to Escalate
Escalation means handing the problem up — to the coordinator, or ultimately to a human. Good escalation triggers are objective:
- An explicit human request — escalate immediately, no further attempts.
- A policy gap — the situation isn't covered by the rules the agent has.
- No progress after attempts — local recovery exhausted.
- A threshold violation — e.g. a refund exceeds an allowed limit.
Notice these are all things you can detect deterministically — not guesses about the user's mood.
Bad Escalation Triggers
Just as important: know what not to escalate on. These triggers feel reasonable but are unreliable and are classic exam distractors:
- Sentiment analysis — escalating because the message "sounds angry."
- Model self-rated confidence — "I'm only 4/10 confident, so escalate." Self-ratings are not calibrated.
- Untrained classifiers bolted on as gatekeepers.
Instead, follow the proven pattern: acknowledge the emotion, propose a concrete solution, and escalate only if the customer reiterates the request. Behavior — a repeated explicit ask — is a far better signal than inferred feeling.
Escalate WITH Context, Not Just a Shrug
When a subagent escalates, it must propagate structured context so the coordinator (or human) can act without re-doing the work:
- the failure type (the errorCategory),
- the attempted query or action,
- any partial results already gathered,
- and viable alternatives.
An escalation that says only "it failed" forces the coordinator to start from zero. An escalation carrying partial results lets the rest of the workflow continue and the human resolve faster.
def escalate(coordinator, failure):
coordinator.report(
failure_type=failure["errorCategory"],
attempted_query=failure["attempted_query"],
partial_results=failure.get("partial_results", []),
alternatives=["retry via read-replica", "ask user for order ID"],
)Don't Abort the Whole Workflow
One failed branch should not collapse the entire job. In a multi-agent research system, if one source is unreachable, the coordinator should still aggregate the successful branches and clearly annotate the gap in coverage.
Two failure modes to avoid:
- Silent suppression — swallowing the error so the final answer looks complete but isn't. This destroys trust and provenance.
- Whole-workflow abort — killing every other branch because one failed.
The middle path: continue, deliver partial results, and be explicit about what's missing.
Caps Are a Safety Net, Not the Plan
A retry loop needs a bound, but the bound is a safety net — never the primary control mechanism. The same principle governs the whole agentic loop: you terminate on stop_reason reaching end_turn, and iteration caps merely prevent runaway loops.
For retries specifically: stop because the structured error says isRetryable: false, or because progress has been made — not merely because you hit attempt #3. The cap exists so a transient-looking-but-permanent fault can't spin forever.
# Cap = backstop. The REAL stop signal is the error category.
for attempt in range(MAX_ATTEMPTS): # safety net only
res = call_tool(args)
if not res["isError"]:
return res
if not res["isRetryable"]: # primary, decision-driven stop
return escalate(res)
sleep(backoff(attempt))
return escalate(res) # exhausted -> escalate, never silentDeterministic Guards for Hard Limits
Some escalations protect against financial, legal, or safety consequences — for example, a refund above a policy threshold. Here, prompt guidance (~90% reliable) is not enough.
Use a hook for 100% deterministic enforcement. A PostToolUse or outgoing-call hook can block a policy-violating action before it ever executes, forcing escalation to a human. Prompts persuade; hooks guarantee.
# .claude hook: block refunds over $500 -> force escalation
def on_outgoing_call(call):
if call.tool == "process_refund" and call.args["amount"] > 500:
return {
"block": True,
"reason": "Refund exceeds $500 policy limit; escalate to human.",
}
return {"block": False}Putting It Together: The Decision Flow
For any failed step, walk this flow:
- 1. Empty, not failed? Return the valid empty result. Done.
- 2. Transient + retryable? Recover locally with bounded retries and backoff.
- 3. Recovered? Continue the workflow.
- 4. Non-recoverable (validation / business / permission), explicit human request, policy gap, threshold violation, or retries exhausted? Escalate with structured context and partial results.
Never silently suppress, never abort the whole workflow, and never escalate on sentiment or self-rated confidence.
Quick Check
A subagent's database tool returns isError: true, errorCategory: "permission", isRetryable: false, with the attempted query and empty partial results. What should the subagent do?
Recap: Recover Local, Escalate the Rest
Key takeaways:
- Classify first: transient (retryable) vs non-recoverable (validation/business/permission).
- Recover transient faults locally in the subagent with bounded retries; the cap is a safety net, the structured error is the real stop signal.
- Distinguish access failure from a valid empty result — 0 rows is a final answer, not a retry trigger.
- Escalate the non-recoverable with structured context: failure type, attempted query, partial results, alternatives.
- Escalate on objective triggers (explicit human request, policy gap, no progress, threshold violation) — never on sentiment or self-rated confidence.
- Enforce financial/legal/safety limits with hooks, not prompts. Never silently suppress; never abort the whole workflow on one failure.
Frequently asked questions
Is the “Local Recovery vs Escalation” lesson free?
Yes — the full text of “Local Recovery vs Escalation” is free to read here on the web, and the Claude Architect course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Claude Architect course, upgrade to CoddyKit PRO.
What will I learn in “Local Recovery vs Escalation”?
Retry transient faults; escalate the non-recoverable. You practise Claude Architect with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Claude Architect?
No prior experience is required. Claude Architect on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Local Recovery vs Escalation” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Claude Architect lesson?
Yes. Every Claude Architect lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Clear Escalation Triggers
- Anti-Pattern: Sentiment & Confidence Scores
- Structured Error Context
- Local Recovery vs Escalation