惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

小众软件
小众软件
N
News and Events Feed by Topic
A
About on SuperTechFans
aimingoo的专栏
aimingoo的专栏
The Cloudflare Blog
H
Heimdal Security Blog
Schneier on Security
Schneier on Security
Engineering at Meta
Engineering at Meta
Google Online Security Blog
Google Online Security Blog
宝玉的分享
宝玉的分享
AI
AI
The GitHub Blog
The GitHub Blog
MongoDB | Blog
MongoDB | Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
The Last Watchdog
The Last Watchdog
T
Troy Hunt's Blog
S
Security @ Cisco Blogs
H
Hacker News: Front Page
F
Fortinet All Blogs
博客园_首页
S
Secure Thoughts
N
News and Events Feed by Topic
P
Proofpoint News Feed
Microsoft Azure Blog
Microsoft Azure Blog
I
InfoQ
Spread Privacy
Spread Privacy
Hacker News - Newest:
Hacker News - Newest: "LLM"
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Check Point Blog
Hugging Face - Blog
Hugging Face - Blog
Hacker News: Ask HN
Hacker News: Ask HN
C
CXSECURITY Database RSS Feed - CXSecurity.com
酷 壳 – CoolShell
酷 壳 – CoolShell
Stack Overflow Blog
Stack Overflow Blog
L
LINUX DO - 最新话题
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Schneier on Security
Know Your Adversary
Know Your Adversary
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Scott Helme
Scott Helme
P
Privacy & Cybersecurity Law Blog
S
Securelist
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
O
OpenAI News
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
PCI Perspectives
PCI Perspectives
L
LangChain Blog
雷峰网
雷峰网
Security Archives - TechRepublic
Security Archives - TechRepublic
V2EX - 技术
V2EX - 技术

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
5 AI Agent Error Handling Patterns That Keep Your Agent Running at 3 AM
Nebula · 2026-04-30 · via DEV Community

Last year, a deployment went sideways. An AI agent was running a data enrichment pipeline: pull records from an API, map fields into a schema, write to a database. Every API call returned 200 OK. The agent's dashboard showed green across the board. The agent reported success on every step.

Six hours later, a downstream team flagged the data. Half the field mappings were hallucinated. The agent had confidently mapped company_revenue to employee_count, invented values for missing fields, and written duplicates for records it had already processed. Hundreds of bad rows, all marked as verified.

Nobody noticed because nothing "failed."

This is the fundamental problem with AI agents in production: the most dangerous failures look exactly like success. Traditional error handling — try/catch blocks, HTTP status code checks, crash monitoring — was built for deterministic software. AI agents are probabilistic systems. They don't crash when they're wrong; they confidently produce garbage with a 200 status code.

After living through that incident and building dozens of production agents since, I've distilled five error handling patterns that actually work. Each pattern handles a failure mode the previous one can't catch. Together, they form a defense-in-depth strategy that keeps agents running, prevents silent data corruption, and gives you sleep on weeknight deployments.

Pattern 1: Circuit Breakers for LLM Quality Failures (Not Just HTTP Errors)

The classic circuit breaker pattern — closed, open, half-open — is standard infrastructure engineering. But when it comes to AI agents, the traditional version is incomplete. It tracks HTTP failures. It misses quality failures.

The Problem

After a model provider degradation, an agent started returning malformed JSON. Every API call succeeded. The HTTP status was 200. We burned 40 minutes of compute before anyone noticed because nothing in the error handling checked output quality — only transport status.

The Solution: Quality-Aware Circuit BREAKER

A circuit breaker for agents needs to track quality failures: outputs that violate schema, fail semantic invariants, or produce unsafe actions — even when the API itself succeeds.

import time
from enum import Enum
from dataclasses import dataclass, field

class CircuitState(Enum):
    CLOSED = "closed"
    OPEN = "open"
    HALF_OPEN = "half_open"

@dataclass
class QualityCircuitBreaker:
    failure_threshold: int = 3
    reset_timeout: float = 60.0
    state: CircuitState = CircuitState.CLOSED
    failures: int = 0
    last_failure_time: float = field(default_factory=time.time)

    def record_quality_failure(self) -> None:
        """Called when LLM output fails validation (not HTTP error)."""
        self.failures += 1
        self.last_failure_time = time.time()
        if self.failures >= self.failure_threshold:
            self.state = CircuitState.OPEN

    def record_success(self) -> None:
        if self.state == CircuitState.HALF_OPEN:
            self.state = CircuitState.CLOSED
            self.failures = 0

    def allow_request(self) -> bool:
        if self.state == CircuitState.CLOSED:
            return True

        if self.state == CircuitState.OPEN:
            elapsed = time.time() - self.last_failure_time
            if elapsed >= self.reset_timeout:
                self.state = CircuitState.HALF_OPEN
                return True  # One probe request
            return False  # Circuit is open, reject

        # Half-open: allow exactly one probe
        return True

    def should_block(self) -> bool:
        return not self.allow_request()

Enter fullscreen mode Exit fullscreen mode

Usage in an agent loop:

breaker = QualityCircuitBreaker(failure_threshold=3, reset_timeout=30.0)

while agent_running:
    if breaker.should_block():
        logger.warning("Circuit breaker OPEN — skipping LLM call")
        sleep(5)
        continue

    response = call_llm(prompt, system=system)
    validated = validate_output(response)

    if not validated:
        breaker.record_quality_failure()
        logger.error(
            f"Quality failure #{breaker.failures}"
            f"state: {breaker.state.value}"
        )
    else:
        breaker.record_success()
        process_response(response)

Enter fullscreen mode Exit fullscreen mode

Key insight: When the circuit opens, stop. Don't burn tokens on a model producing garbage. Wait for the cooldown, send one probe request, and if it passes schema validation, close the circuit.

A natural extension is a model fallback chain: when the circuit opens, switch to a cheaper model with tighter constraints (lower temperature, stricter schema, fewer allowed tools). Circuit breakers tell you when to stop trusting a model; fallback chains tell you where to go next.

Pattern 2: Validation Gates Before Tool Execution

An agent mapped a delete_all_records action to what it interpreted as "cleanup." The API accepted it. 47 records gone before the next human review. The agent was confident. The action was syntactically valid. The intent was completely wrong.

The Rule

Never let an agent's output directly trigger a side effect. Always validate before execution.

from typing import Any
from dataclasses import dataclass
import json

@dataclass
class ToolCallValidation:
    tool_name: str
    parameters: dict[str, Any]

ALLOWED_TOOLS = {"query_database", "send_notification", "update_record"}
DESTRUCTIVE_TOOLS = {"delete_record", "archive_project"}
MAX_DELETE_COUNT = 10

class ValidationGate:
    def validate(self, call: ToolCallValidation) -> tuple[bool, str]:
        """Returns (is_valid, reason)"""

        # 1. Schema: is this a known tool?
        if call.tool_name not in ALLOWED_TOOLS | DESTRUCTIVE_TOOLS:
            return False, f"Unknown tool: {call.tool_name}"

        # 2. Sanity: does the action make sense?
        if call.tool_name == "delete_record":
            count = call.parameters.get("count", 1)
            if count > MAX_DELETE_COUNT:
                return False, (
                    f"Delete count {count} exceeds limit of {MAX_DELETE_COUNT}"
                )

        # 3. Boundary: is the agent in its allowed scope?
        if call.tool_name == "query_database":
            table = call.parameters.get("table")
            if table == "production_billing":
                return False, "Agent cannot access production_billing"

        return True, "OK"

Enter fullscreen mode Exit fullscreen mode

Three layers of validation, each catching a different failure class:

  1. Schema validation — Is the output structurally correct? Missing required field? Wrong type? Malformed JSON?
  2. Sanity checks — Does the action make sense? Deleting 10,000 records? Probably not.
  3. Boundary enforcement — Is the agent operating within its allowed scope? Cross-tenant access? Targeting a production table from a staging workflow?

This gates every tool call before it executes. No separate validation function to remember to call — it's in the critical path.

gate = ValidationGate()

def execute_tool_call(raw_llm_output: str):
    call = parse_tool_call(raw_llm_output)
    is_valid, reason = gate.validate(call)

    if not is_valid:
        logger.warning(f"Tool call blocked: {reason}")
        return f"Action blocked: {reason}. Please revise."

    # Only reach here after validation passes
    return run_actual_tool(call)

Enter fullscreen mode Exit fullscreen mode

Design principle: Constrain what the agent can do, and you prevent most errors at the source. This pairs directly with the insight from tool design work — splitting one monolithic tool into eight focused tools eliminates most validation failures before they can occur.

Pattern 3: Idempotent Sagas for Multi-Step Workflows

A three-step agent workflow failed on step 2. Step 1 had already created a customer record. The retry created a duplicate. Two hundred orphaned records found a week later, each triggering duplicate billing notifications.

The math is uncomfortable: an agent that succeeds 95% of the time on each step has only a 60% chance of completing a 10-step workflow cleanly (0.95^10). At 90% per step, a 10-step workflow succeeds just 35% of the time. Every step compounds the risk, and without idempotency, every retry doubles the side effects.

The Solution: Checkpoint-Then-Execute with Compensation Actions

Borrowing from the saga pattern in distributed systems, each step records its completion before execution and defines a compensation action for rollback.

from dataclasses import dataclass
from enum import Enum
from typing import Callable, Optional
import sqlite3

class StepStatus(Enum):
    PENDING = "pending"
    COMPLETED = "completed"
    COMPENSATED = "compensated"

@dataclass
class SagaStep:
    name: str
    execute: Callable
    compensate: Optional[Callable]  # None means read-only or irreversible
    status: StepStatus = StepStatus.PENDING

class SagaExecutor:
    def __init__(self, db_path: str = ":memory:"):
        self.conn = sqlite3.connect(db_path)
        self._init_checkpoint_table()

    def _init_checkpoint_table(self):
        self.conn.execute("""
            CREATE TABLE IF NOT EXISTS checkpoints (
                step_name TEXT PRIMARY KEY,
                status TEXT,
                result TEXT
            )
        """)
        self.conn.commit()

    def _is_completed(self, step_name: str) -> bool:
        row = self.conn.execute(
            "SELECT status FROM checkpoints WHERE step_name = ?",
            (step_name,)
        ).fetchone()
        return row is not None and row[0] == "completed"

    def _record_checkpoint(self, step_name: str, result: str):
        self.conn.execute(
            "INSERT OR REPLACE INTO checkpoints (step_name, status, result) "
            "VALUES (?, 'completed', ?)",
            (step_name, result)
        )
        self.conn.commit()

    def execute(self, steps: list[SagaStep]) -> dict[str, str]:
        completed_steps = []

        for step in steps:
            if self._is_completed(step.name):
                # Idempotent: skip already-completed steps on retry
                continue

            try:
                result = step.execute()
                self._record_checkpoint(step.name, str(result))
                completed_steps.append(step)
            except Exception as e:
                # Rollback: execute compensation in reverse order
                logger.error(
                    f"Step '{step.name}' failed: {e}. "
                    f"Rolling back {len(completed_steps)} steps."
                )
                for completed in reversed(completed_steps):
                    if completed.compensate:
                        try:
                            completed.compensate()
                        except Exception as comp_err:
                            logger.critical(
                                f"Compensation for '{completed.name}' failed: "
                                f"{comp_err}"
                            )
                raise

        return {s.name: "completed" for s in steps}

Enter fullscreen mode Exit fullscreen mode

Usage:

def fetch_data(): return api.get_records()
def transform(data): return [process(r) for r in data]
def write_to_db(data):
    db.insert_batch(data)
    return {"id": db.last_insert_id}
def rollback_write(ctx):
    db.delete_batch(ctx["id"])

def send_notification():
    smtp.send("Pipeline complete")
def send_correction():
    smtp.send("Correction: pipeline was rolled back")

saga = SagaExecutor()

steps = [
    SagaStep("fetch_data", execute=fetch_data, compensate=None),  # Read-only
    SagaStep("transform", execute=transform, compensate=None),    # Pure function
    SagaStep("write_to_db", execute=write_to_db, compensate=rollback_write),
    SagaStep("send_notification", execute=send_notification, compensate=send_correction),
]

saga.execute(steps)

Enter fullscreen mode Exit fullscreen mode

Classify every step:

  • Read-only — safe to retry freely (fetches, queries)
  • Pure function — safe to retry (transforms, computations)
  • Reversible — can undo (delete what you created)
  • Compensatable — can't undo but can correct (send a follow-up notification)
  • Irreversible — can't undo at all (payment processed). These need the most validation before execution.

Pattern 4: Budget Guardrails for Runaway Loops

Agents can enter reasoning loops. The model keeps calling tools, generating responses, re-evaluating — never reaching a terminal state. The tokens pile up. The bill climbs. Nothing crashes, which is the problem.

Token Budget

A hard cap on tokens per agent session. When the budget is exhausted, the agent must stop and either return results so far or escalate.

@dataclass
class TokenBudget:
    max_tokens: int = 50_000  # Set based on your cost tolerance
    used_tokens: int = 0
    cost_per_1k: float = 0.01  # Adjust for your model

    def remaining(self) -> int:
        return max(0, self.max_tokens - self.used_tokens)

    def is_exhausted(self) -> bool:
        return self.used_tokens >= self.max_tokens

    def estimate_cost(self) -> float:
        return (self.used_tokens / 1_000) * self.cost_per_1k

    def track(self, response_text: str):
        # Rough estimate: ~1 token per 4 chars for English
        self.used_tokens += len(response_text) // 4

Enter fullscreen mode Exit fullscreen mode

Cycle Budget

Beyond tokens, limit the number of reasoning cycles (turns) the agent can take. This prevents infinite tool-call loops even if each individual call is cheap.

@dataclass
class CycleBudget:
    max_cycles: int = 15
    current_cycle: int = 0

    def increment(self) -> bool:
        """Returns True if cycles remain, False if exhausted."""
        self.current_cycle += 1
        return self.current_cycle <= self.max_cycles

    def remaining(self) -> int:
        return max(0, self.max_cycles - self.current_cycle)

Enter fullscreen mode Exit fullscreen mode

Integrated into an agent loop:

token_budget = TokenBudget(max_tokens=30_000)
cycle_budget = CycleBudget(max_cycles=12)

while agent_running:
    if token_budget.is_exhausted():
        logger.warning(
            f"Token budget exhausted ({token_budget.used_tokens} used). "
            f"Estimated cost: ${token_budget.estimate_cost():.2f}"
        )
        # Return partial results or escalate
        break

    if not cycle_budget.increment():
        logger.warning(
            f"Cycle budget exhausted after {cycle_budget.max_cycles} turns. "
            f"Agent may be in a reasoning loop."
        )
        break

    # ... execute agent step ...
    token_budget.track(llm_response_text)

    if cycle_budget.remaining() <= 3:
        logger.info(f"Agent in danger zone: {cycle_budget.remaining()} cycles left")

Enter fullscreen mode Exit fullscreen mode

These budgets protect against the failure mode where an agent "works" perfectly — no exceptions, no crashes — while slowly burning through your API budget.

Pattern 5: Human Escalation for High-Risk Decisions

Some actions are too risky for an agent to make autonomously. Deleting production data. Sending customer-facing communications. Changing infrastructure configuration.

Confidence-Based Escalation

Ask the model for its confidence level alongside its reasoning. Below a threshold, route to a human instead of executing.

ESCALATION_THRESHOLD = 0.7
HIGH_RISK_ACTIONS = {"delete_production_data", "send_customer_email",
                     "modify_infrastructure", "approve_payment"}

def should_escalate(tool_name: str, confidence: float,
                    validation_result: str) -> bool:
    reasons = []

    if tool_name in HIGH_RISK_ACTIONS:
        reasons.append(f"high-risk action: {tool_name}")

    if confidence < ESCALATION_THRESHOLD:
        reasons.append(f"low confidence: {confidence:.2f}")

    if "unsure" in validation_result.lower():
        reasons.append("model expressed uncertainty")

    if len(reasons) >= 2:
        logger.warning(f"Escalating: {', '.join(reasons)}")
        return True

    return False

# In the agent loop:
if should_escalate(call.tool_name, parsed.confidence, validation_reason):
    escalation_queue.put({
        "tool": call.tool_name,
        "params": call.parameters,
        "model_confidence": parsed.confidence,
        "model_reasoning": parsed.reasoning,
        "suggested_action": call.parameters,
    })
    response = "Action queued for human review."
else:
    response = execute_tool_call(call)

Enter fullscreen mode Exit fullscreen mode

The Escalation Queue

Persisted escalation means it survives crashes, restarts, and redeployments. A simple file-backed queue works:

import json
from pathlib import Path

class EscalationQueue:
    def __init__(self, path: str = "escalations.json"):
        self.path = Path(path)
        self._ensure_file()

    def _ensure_file(self):
        if not self.path.exists():
            self.path.write_text("[]")

    def enqueue(self, item: dict):
        items = json.loads(self.path.read_text())
        items.append({**item, "enqueued_at": time.time()})
        self.path.write_text(json.dumps(items, indent=2))

    def pending(self) -> list[dict]:
        return json.loads(self.path.read_text())

    def resolve(self, index: int, decision: str):
        items = json.loads(self.path.read_text())
        items[index]["decision"] = decision
        items[index]["resolved_at"] = time.time()
        self.path.write_text(json.dumps(items, indent=2))

Enter fullscreen mode Exit fullscreen mode

Principle: The agent should stop deciding and start asking. Define clear escalation criteria upfront — action type, confidence threshold, validation failure patterns — and honor them. Nothing erodes trust in an agent faster than an autonomous mistake that a human would have caught in three seconds.

Putting It All Together

These five patterns form layers:

┌─────────────────────────────────────────┐
│ 5. Human Escalation                     │ ← When to stop deciding
│ 4. Budget Guardrails (tokens + cycles) │ ← When to stop spending
│ 3. Idempotent Sagas                     │ ← How to recover from partial failure
│ 2. Validation Gates                     │ ← What is allowed to execute
│ 1. Circuit Breakers (quality-aware)     │ ← When the model itself is unreliable
└─────────────────────────────────────────┘

Enter fullscreen mode Exit fullscreen mode

Layer 1 catches model degradation before it burns tokens. Layer 2 blocks dangerous actions before they execute. Layer 3 contains partial failures when they inevitably occur. Layer 4 limits blast radius when the agent spirals. Layer 5 ensures humans are in the loop for irreversible decisions.

Together, they transform an agent from a probabilistic liability into a system you can deploy at 5 PM on a Friday and sleep through the night.

Actionable Takeaways

  1. Start with circuit breakers on output quality, not just HTTP status. Validate schema conformance on every LLM response. Three consecutive failures → open circuit → switch to fallback model.
  2. Gate every tool call. Schema, sanity, and boundary checks in the critical path — not as an afterthought.
  3. Idempotence is non-negotiable for multi-step workflows. Checkpoint before execution, compensate on failure, skip completed steps on retry.
  4. Set hard token and cycle budgets from day one. Cost surprises come from "working" agents in loops, not from crashed ones.
  5. Define escalation criteria before writing agent code. If you can't state when a human should review an action, the agent isn't ready for production.

The production agent journey doesn't start with making the agent smarter. It starts with making the agent reliable. Do that, and the smartness will follow.