spb/ultra-sharp-agent-skills Public
Ultra-Sharp Agent Skills — a research-first skill-authoring system + 72 production-ready skills for AI agents.
Python 100%
1<!--2Author: Simon-Pierre Boucher3Contact: contact@spboucher.ai4-->56# Patterns — Handling Errors78## Contents9- Error taxonomy skeleton (Python)10- Boundary handler → problem+json11- Retry with exponential backoff + full jitter12- Circuit breaker13- Fail-fast startup14- Gotchas1516## Error taxonomy skeleton (Python)1718```python19class AppError(Exception):20 status = 50021 title = "Internal error"22 def __init__(self, detail="", **ctx):23 super().__init__(detail)24 self.detail, self.ctx = detail, ctx2526class ValidationError(AppError): status, title = 400, "Invalid request"27class AuthError(AppError): status, title = 401, "Authentication required"28class Forbidden(AppError): status, title = 403, "Not allowed"29class NotFound(AppError): status, title = 404, "Resource not found"30class Conflict(AppError): status, title = 409, "Conflict"31class RateLimited(AppError): status, title = 429, "Too many requests"32```3334## Boundary handler → problem+json3536One handler at the HTTP edge; nothing below it formats responses.3738```python39@app.exception_handler(AppError)40def app_error(request, exc):41 # Exactly one log line per failure, with context, at the boundary.42 logger.warning("request_failed", status=exc.status,43 error=type(exc).__name__, **exc.ctx)44 return JSONResponse(status_code=exc.status, media_type="application/problem+json",45 content={"type": f"https://api.example.com/errors/{type(exc).__name__}",46 "title": exc.title, "status": exc.status,47 "detail": exc.detail, "instance": str(request.url.path)})4849@app.exception_handler(Exception)50def unexpected(request, exc):51 logger.exception("unhandled_error") # full trace to logs only52 return JSONResponse(status_code=500, media_type="application/problem+json",53 content={"title": "Internal error", "status": 500,54 "detail": "Unexpected error. Retry or contact support."})55```5657## Retry with exponential backoff + full jitter5859```python60import random, time6162# base 0.2s, factor 2, cap 30s, 5 attempts: worst-case wait ~ <60s total.63def retry(fn, retryable=(TimeoutError, ConnectionError),64 attempts=5, base=0.2, cap=30.0):65 for n in range(attempts):66 try:67 return fn()68 except retryable:69 if n == attempts - 1:70 raise71 time.sleep(random.uniform(0, min(cap, base * 2 ** n))) # full jitter72```7374Honor server hints: if the response carries `Retry-After: N`, sleep `N` seconds instead of the computed backoff.7576## Circuit breaker7778```python79class Breaker:80 # 5 consecutive failures opens; probe after 30s (half-open).81 def __init__(self, threshold=5, reset_after=30.0):82 self.fail, self.threshold, self.reset_after = 0, threshold, reset_after83 self.opened_at = None84 def call(self, fn):85 if self.opened_at is not None:86 if time.monotonic() - self.opened_at < self.reset_after:87 raise DependencyDown("circuit open")88 self.opened_at = None # half-open: allow one probe89 try:90 out = fn()91 except Exception:92 self.fail += 193 if self.fail >= self.threshold:94 self.opened_at = time.monotonic()95 raise96 self.fail = 097 return out98```99100## Fail-fast startup101102```python103def main():104 cfg = load_config() # raises with the missing key named105 db.ping(cfg.database_url) # unreachable DB -> crash now, not at first request106 run_pending_migration_check(cfg)107 serve(cfg)108```109110## Gotchas111- `except Exception: pass` hides bugs for months — if a failure is truly ignorable, log it at DEBUG with a reason string.112- Re-raising with `raise NewError(...) from e` preserves the chain; bare `raise NewError(...)` destroys the original traceback.113- Retrying a POST without an idempotency key can double-charge/double-create — timeouts are *unknown outcome*, not failure.114- Breakers per dependency, not global — one dead cache must not open the DB breaker.115- problem+json `type` URLs should be stable identifiers; they don't have to resolve, but never reuse one for a different meaning.116- 500 bodies must be static — rendering them from the exception risks leaking internals and can itself fail.117