Most agents fail silently on the first API error. Chapter 9 gives each failure a specific recovery path — and only gives up after exhausting every option.
The Big Picture
Chapter 9 handles failures in the LLM API itself — rate limits, auth errors, timeouts, context overflow.
Solution: a 3-Step Retry Chain. When one step fails, the error moves outward to the next recovery step.
STEP 1: Auth Rotation
[primary-key] → [backup-key] → [emergency-key]
key hits error → cooldown → try next key
│
STEP 2: Overflow Recovery
context too long?
→ truncate tool results → compact history
│
STEP 3: Tool-Use Loop
standard while True + stop_reason (same as Chapter 1–8)
│
success → return
failure → classify → route to the appropriate recovery step
Three Key Concepts
1. classify_failure() — Route Exceptions to the Right Layer
type FailureReason int
const (
Unknown FailureReason = iota
RateLimit
Auth
Timeout
Billing
Overflow
)
func classifyFailure(err error) FailureReason {
// LLM APIs don't always return typed errors — parsing the message string is the most reliable approach across providers
msg := strings.ToLower(err.Error())
switch {
case strings.Contains(msg, "rate") || strings.Contains(msg, "429"): return RateLimit
case strings.Contains(msg, "auth") || strings.Contains(msg, "401"): return Auth
case strings.Contains(msg, "timeout"): return Timeout
case strings.Contains(msg, "billing") || strings.Contains(msg, "402"): return Billing
case strings.Contains(msg, "context") || strings.Contains(msg, "token"): return Overflow
default: return Unknown
}
}
| Reason | Action |
|---|---|
| rate_limit | Cooldown 5 min, try next key |
| auth | Cooldown, try next key |
| timeout | Cooldown 60s, try next key |
| overflow | Step 2 — compact history |
| billing | Cooldown, try next key |
2. ProfileManager — Key Rotation with Cooldown
func selectProfile(profiles []*Profile) *Profile {
now := time.Now()
for _, p := range profiles {
if !now.Before(p.CooldownUntil) { // now >= CooldownUntil → key is available
return p // first available key
}
}
return nil // all on cooldown → exhausted → fallback models
}
3. Retry Limits — Not Infinite
maxRetries := min(max(24+8*nProfiles, 32), 160)
// scales with number of API keys — more keys = more retries worth attempting before giving up
There’s a hard cap. If all retries are exhausted → raise the error up. Never loop forever.
Multi-Provider Tiered Fallback
For maximum resilience, configure fallback across providers:
Tier 1: Anthropic Claude → primary, best quality
↓ rate limit / balance exhausted
Tier 2: Gemini / OpenRouter → cheaper, still cloud
↓ all cloud APIs down
Tier 3: Ollama (local) → free, always available
Quality drops per tier. Responses from smaller or local models may be less accurate or miss nuance — they keep the agent running, but with reduced capability. For agents requiring strong reasoning, keep the primary provider as the preferred choice. Local models are a last resort, not a replacement.
Context Overflow Recovery (in depth)
Chapter 3 (ContextGuard) and Chapter 9 handle overflow at different points in time:
Attempt 1: normal call
↓ overflow error
Attempt 2: truncate oversized tool results
↓ still failing
Attempt 3: compact — LLM summarizes 50% of history
↓ still failing
→ route to Step 1 (try a different key/model)
Chapter 3’s ContextGuard tries to prevent overflow before it happens. Chapter 9’s recovery step handles the case where the API already returned an overflow error — it’s the reactive fallback.
Takeaway
- 3-Step Retry Chain: auth rotation → overflow recovery → tool-use loop
classify_failure()— router — different errors handled differently- Key rotation + cooldown — graceful degradation when one key has issues
- Always have a retry limit — no infinite loops
- Multi-provider fallback — last line of defense when all keys are exhausted
Cross-References
- Chapter 3 (Context Management) — ContextGuard proactive overflow prevention
- Chapter 8 (Delivery Guarantees) — write-ahead queue that pairs with resilience on restarts
- Chapter 1 (Tool-Use Loop) — the inner loop that Step 3 wraps