Error Recovery and Self-Correction in Agents
Every agent in this series has assumed tool calls succeed. Part 9 flagged this as genuinely unrealistic for real external tools — this part covers the actual, practical handling every prior part deferred.
The real, core technique: let the agent SEE the error
def execute_tool_with_error_visibility(tool_name: str, tool_input: dict) -> str:
try:
result = tool_functions[tool_name](**tool_input)
return str(result)
except requests.exceptions.Timeout:
return "ERROR: the request timed out. Consider retrying or trying an alternative approach."
except requests.exceptions.HTTPError as e:
return f"ERROR: the real API returned {e.response.status_code}. This tool call cannot be retried as-is."This real, error string gets sent back into the loop exactly like any other real tool result — the model sees the actual, real failure and can genuinely REASON about it, per part 1's loop, rather than the agent silently crashing.
A real, concrete recovery example
Real trace:
Iteration 1: agent calls get_customer_from_crm("4471")
→ real result: "ERROR: the request timed out."
Iteration 2: agent reasons "this might be transient" → calls
get_customer_from_crm("4471") again
→ real result: successful, real customer data returned
Iteration 3: agent proceeds normally with the real, retrieved dataThis is genuinely different, and more flexible, than the tenacity-based automatic retry from part 9 — that handles TRANSIENT network issues automatically, before the model ever sees them. This part's technique handles a genuinely different case: an error the model itself needs to reason about and decide how to handle, since not every real failure has an obvious, automatic fix.
Distinguishing genuinely retryable from genuinely fatal errors
RETRYABLE_ERRORS = {"timeout", "rate_limit", "temporary_unavailable"}
FATAL_ERRORS = {"invalid_credentials", "resource_not_found", "permission_denied"}
def classify_error(error: Exception) -> str:
if isinstance(error, requests.exceptions.Timeout):
return "timeout"
if hasattr(error, "response") and error.response.status_code == 404:
return "resource_not_found"
# ...additional real classification logicA real, explicit distinction matters directly — an agent that keeps retrying a genuinely fatal error (invalid credentials will never succeed on retry) wastes real API cost and time; one that gives up immediately on a genuinely transient error (a timeout) misses an easy, real recovery. This classification is what lets the system prompt correctly instruct the model on which category warrants retrying.
A real system prompt addition for error-aware reasoning
"If a tool call returns an ERROR, read it carefully. For a timeout
or rate limit error, you may retry once. For any other real error
type, do not retry — instead, explain what went wrong and what
information you were unable to obtain."This directly extends part 6's system prompt pattern with explicit, real guidance for the failure case specifically — without it, the model has no clear, real basis for deciding whether to retry, try something different, or give up and report the real gap honestly.
The real, honest limit: some failures genuinely need a human
def run_agent_with_escalation(request: str) -> dict:
result = run_agent_loop(request)
if "ERROR" in result and "unable to obtain" in result:
return {"status": "needs_human_review", "partial_result": result}
return {"status": "completed", "result": result}This real, explicit escalation path directly extends the same honest principle from part 6's iteration-limit fallback — a genuine, real failure the agent can't resolve on its own should surface clearly for real, human attention, not fail silently or produce a fabricated, plausible-sounding success message covering up an actual, unresolved gap.
Building an agent that either crashes completely on any real error, or — genuinely worse — silently continues and generates a confident-sounding final answer despite a real, unresolved failure somewhere in its actual execution trace. Both are real, honest problems this part's explicit error-visibility and escalation pattern directly solves.
Next: observability and debugging AI agents — real, practical logging and tracing for understanding what an agent actually did, and why.