- Classify errors before retrying.
- Design retries with backoff, jitter, and limits.
- Escalate to human review without losing state.
Classify Before Acting
- Transient: 429, 503, timeout, broken connection. Retry with backoff.
- LLM-recoverable: invalid JSON format, missing field. Retry with explicit correction.
- User-recoverable: missing data, permission, or approval. Ask or pause.
- Permanent: invalid credential, resource does not exist, policy blocked. Stop and report.
type AgentError =
| { kind: "transient"; code: 429 | 503 | "timeout"; retryAfterMs?: number }
| { kind: "model_format"; message: string }
| { kind: "needs_human"; reason: string }
| { kind: "fatal"; reason: string };
const retryPolicy = {
maxAttempts: 4,
initialDelayMs: 1000,
backoff: 2,
jitter: true,
};Minimum State to Avoid a Spiral
{
"task_id": "research-2026-07-05-001",
"step": "fetch_sources",
"attempts": 2,
"last_error": "503 from search API",
"next_retry_at": "2026-07-05T10:31:00Z",
"fallback": "use cached sources",
"human_review": false
}Recommended Recovery Pattern
- Execute the tool with a timeout.
- If it fails, classify the error in code, not in free text.
- If transient, wait with backoff and log the attempt.
- If attempts are exhausted, switch to fallback or escalate to a human.
- Summarize the state, not the full history, to continue.
Official Sources
- LangGraph persistence
- LangGraph: Thinking in LangGraph
- LangChain/LangGraph human-in-the-loop