Skip to content

Error Recovery

Overview

Production systems fail. The question isn't "if" but "when" and "how quickly can you recover?"


Error Classification

Error Categories

class ErrorClassification:
 """Categorize errors by recovery approach"""

 def classify(self, error) -> str:
 """Determine error type"""

 if isinstance(error, TransientError):
 # Temporary failure (network blip, rate limit)
 return 'transient'

 elif isinstance(error, PermanentError):
 # Won't succeed with retry (invalid input, not found)
 return 'permanent'

 elif isinstance(error, TimeoutError):
 # Took too long
 return 'timeout'

 elif isinstance(error, ResourceError):
 # Out of resources (OOM, quota exceeded)
 return 'resource'

 elif isinstance(error, SystemError):
 # Internal system error (bug in code)
 return 'system'

 else:
 return 'unknown'

 def recovery_strategy(self, error_type):
 """Different strategy for each error type"""

 strategies = {
 'transient': 'retry_with_backoff',
 'permanent': 'fallback_or_escalate',
 'timeout': 'circuit_break',
 'resource': 'queue_or_reject',
 'system': 'escalate_to_humans'
 }

 return strategies[error_type]

Recovery Strategies

Strategy 1: Retry with Exponential Backoff

class RetryWithBackoff:
 """Retry transient errors with increasing delays"""

 def execute_with_retry(self, func, max_retries=3):
 """Retry with exponential backoff"""

 for attempt in range(max_retries):
 try:
 return func()

 except TransientError as e:
 if attempt < max_retries - 1:
 # Exponential backoff: 1s, 2s, 4s, 8s...
 wait_time = min(2 ** attempt, 60)

 # Add jitter to prevent thundering herd
 jitter = random.uniform(0, 0.1 * wait_time)
 time.sleep(wait_time + jitter)
 else:
 raise

-

Strategy 2: Fallback

class FallbackRecovery:
 """Use alternative when primary fails"""

 def execute_with_fallback(self, request):
 """Try primary, fallback on failure"""

 try:
 # Try expensive/complex path
 return self.expensive_solution(request)

 except Exception as e:
 # Fall back to cheaper/simpler solution
 return self.cheap_solution(request)

 def expensive_solution(self, request):
 """Complex reasoning, expensive"""
 return self.complex_agent.solve(request)

 def cheap_solution(self, request):
 """Simple heuristic, cheap"""
 return self.heuristic_solver.solve(request)

Strategy 3: Circuit Breaker

class CircuitBreakerRecovery:
 """Stop calling failing service"""

 def __init__(self, failure_threshold=5, timeout=60):
 self.state = 'closed' # Normal
 self.failure_count = 0
 self.last_failure = None
 self.threshold = failure_threshold
 self.timeout = timeout

 def call(self, func):
 """Call with circuit breaker protection"""

 if self.state == 'open':
 # Circuit is open, don't call
 if time.time() - self.last_failure > self.timeout:
 # Try again (half-open state)
 self.state = 'half-open'
 else:
 raise CircuitBreakerOpen("Service unavailable")

 try:
 result = func()

 # Success - close circuit
 self.state = 'closed'
 self.failure_count = 0
 return result

 except Exception as e:
 # Failure - increment counter
 self.failure_count += 1
 self.last_failure = time.time()

 if self.failure_count >= self.threshold:
 self.state = 'open' # Open circuit

 raise

Graceful Degradation

Degradation Strategies

class GracefulDegradation:
 """Reduce quality rather than fail completely"""

 def handle_request(self, request):
 """Try progressively simpler strategies"""

 try:
 # Attempt 1: Full capability
 return self.full_solution(request)

 except Exception:
 try:
 # Attempt 2: Simplified solution
 return self.simplified_solution(request)

 except Exception:
 try:
 # Attempt 3: Cached result
 return self.cached_result(request)

 except Exception:
 # Attempt 4: Default response
 return self.default_response(request)

 def full_solution(self, request):
 """Best quality, most expensive"""
 return self.complex_agent.solve(request)

 def simplified_solution(self, request):
 """Medium quality, cheaper"""
 return self.fast_agent.solve(request)

 def cached_result(self, request):
 """Old result, free"""
 return self.cache.get(request.id)

 def default_response(self, request):
 """Basic response, always works"""
 return {'status': 'queued', 'estimate': '1 hour'}

Health Checks & Healing

Proactive Health Monitoring

class HealthCheckHealing:
 """Monitor health, heal before failure"""

 def run_health_check(self):
 """Periodically check system health"""

 health = {
 'llm_available': self.check_llm(),
 'tools_available': self.check_tools(),
 'memory_available': self.check_memory(),
 'disk_available': self.check_disk(),
 'database_responsive': self.check_database()
 }

 # Identify unhealthy components
 unhealthy = [k for k, v in health.items() if not v]

 if unhealthy:
 # Heal proactively
 self.heal(unhealthy)

 # Alert ops
 self.notify_ops(unhealthy)

 return health

 def heal(self, components):
 """Auto-healing when possible"""

 for component in components:
 if component == 'memory_available':
 # Clear caches
 self.clear_caches()

 elif component == 'database_responsive':
 # Restart database connection
 self.restart_database_connection()

 elif component == 'tools_available':
 # Restart tools service
 self.restart_tools_service()

3 Warnings

Warning 1: Infinite Retry Loops

# WRONG
while True:
 try:
 result = call_api()
 break
 except:
 continue # Retry forever!

# If service is down, loops infinitely
# Consumes resources

# RIGHT
max_retries = 3
for attempt in range(max_retries):
 try:
 result = call_api()
 break
 except TransientError:
 if attempt < max_retries - 1:
 time.sleep(2 ** attempt)
 else:
 raise

# Bounded retries

Warning 2: Hiding Real Errors

# WRONG
try:
 critical_operation()
except:
 pass # Silently ignore!

# Error disappears
# Data corruption possible

# RIGHT
try:
 critical_operation()
except PermanentError:
 raise # Don't hide permanent errors
except TransientError:
 retry_later() # Only retry on transient

Warning 3: Cascading Failures

# WRONG
# No circuit breaker
for request in requests:
 result = call_failing_service()
 # Service down, all requests fail
 # Overwhelms service with retries

# RIGHT
# Circuit breaker prevents cascade
breaker = CircuitBreaker()
for request in requests:
 try:
 result = breaker.call(call_service)
 except CircuitBreakerOpen:
 # Service down, skip gracefully
 queue_for_later(request)

# Protects service from overload

-

Last Updated: August 9, 2026