{"slug": "circuit-breakers-for-agentic-ai-workflows-controlling-blast-radius-in-autonomous", "title": "Circuit Breakers for Agentic AI Workflows: Controlling Blast Radius in Autonomous Code Review", "summary": "A developer has proposed applying circuit breaker patterns to agentic AI code review workflows to limit the blast radius of autonomous agents that can interpret, execute, and commit code. The approach uses a finite state machine to gate agent actions, tracking failure metrics such as token budget exhaustion, duplicate actions, and security guardrail violations, and falls back to human review when thresholds are breached.", "body_md": "*Originally published on [tamiz.pro](https://tamiz.pro/insights/circuit-breakers-agentic-ai-workflows-code-review).*\n\nThe rapid adoption of agentic AI in software engineering has introduced a new class of operational risks: autonomous agents that can interpret, execute, and commit code. While powerful, these systems lack the inherent safety rails of traditional deterministic software. When an LLM agent makes a cascading error in a code review pipeline, the \"blast radius\" can expand from a single file to a corrupted build, a failing deployment, or even a security vulnerability. This article dissects the architectural necessity of applying **Circuit Breaker patterns** to agentic workflows, specifically focusing on AI-driven code review. We will move beyond simple retry logic to explore state-machine-based fail-safes that isolate failures, prevent resource exhaustion, and enforce human-in-the-loop gating when confidence levels drop below operational thresholds.\n\nTraditional code review tools are deterministic: regular expressions, static analyzers (like ESLint or SonarQube), and linters produce predictable outputs for predictable inputs. However, agentic workflows powered by Large Language Models (LLMs) are probabilistic. An agent might:\n\n`git commit` or trigger CI/CD pipelines that have cascading financial or security implications.\nIn a standard microservices architecture, a circuit breaker opens when a service fails N times in a rolling window, stopping traffic to that service to allow it to recover. In an agentic AI workflow, the \"service\" is the agent's decision-making capability. The \"traffic\" is the sequence of actions it takes. If we don't break the circuit, a buggy prompt engineering update or a specific edge-case PR can trigger an autonomous agent to continuously generate bad code, exhaust API tokens, or introduce subtle security flaws into the main branch.\n\nIn traditional SRE, blast radius refers to the scope of impact when a component fails. In AI engineering, it is multidimensional:\n\nWe define our circuit breaker not as a simple flag, but as a finite state machine (FSM) that gates agent actions. The states are:\n\nWhat constitutes a \"failure\" in an agentic review? We must move beyond HTTP 500 errors. Key metrics for opening the circuit include:\n\n`eval()`, hardcoded secrets).\nRather than hardcoding checks into every agent prompt, we wrap the agent's execution loop in a `CircuitBreakerMiddleware`. This middleware intercepts every `action` the agent requests to perform.\n\n``` python\nclass AgenticCircuitBreaker:\n    def __init__(self, failure_threshold=5, recovery_timeout=300, \n                 token_budget=10000, security_guardrail=None):\n        self.failure_threshold = failure_threshold\n        self.recovery_timeout = recovery_timeout\n        self.token_budget = token_budget\n        self.security_guardrail = security_guardrail\n\n        self.state = \"CLOSED\"\n        self.failure_count = 0\n        self.last_failure_time = None\n        self.total_tokens_used = 0\n        self.consecutive_duplicate_actions = 0\n\n    async def execute_action(self, agent, action: AgentAction, context: ReviewContext):\n        \"\"\"\n        Guts the agent's action. If circuit is OPEN, blocks and triggers fallback.\n        \"\"\"\n        if self.state == \"OPEN\":\n            if self._should_attempt_half_open():\n                self.state = \"HALF-OPEN\"\n                logger.info(\"Circuit Breaker: Transitioning to HALF-OPEN for test\")\n            else:\n                raise CircuitOpenError(\n                    f\"Circuit is OPEN. Last failure: {self.last_failure_time}. \"\n                    f\"Falling back to human review queue.\"\n                )\n\n        try:\n            # 1. Pre-Flight Check: Security Guardrail\n            if self.security_guardrail:\n                risk_score = await self.security_guardrail.evaluate(action)\n                if risk_score > 0.8: # High risk\n                    self._record_failure(\"Security Guardrail Flag\")\n                    raise SecurityViolationError(action)\n\n            # 2. Token Budget Check\n            estimated_tokens = self._estimate_tokens(action)\n            if self.total_tokens_used + estimated_tokens > self.token_budget:\n                self._record_failure(\"Token Budget Exhausted\")\n                raise TokenExhaustionError()\n\n            # 3. Execute the Action (e.g., LLM call, git commit, test run)\n            result = await agent.execute(action)\n\n            # 4. Post-Flight Check: Detect Infinite Loops\n            if result.is_duplicate_of_previous:\n                self.consecutive_duplicate_actions += 1\n                if self.consecutive_duplicate_actions >= 2:\n                    self._record_failure(\"Infinite Loop Detected\")\n                    raise InfiniteLoopError()\n            else:\n                self.consecutive_duplicate_actions = 0\n                self._record_success()\n\n            return result\n\n        except Exception as e:\n            self._record_failure(str(e))\n            raise e\n\n    def _record_failure(self, reason):\n        self.failure_count += 1\n        self.last_failure_time = time.time()\n        if self.failure_count >= self.failure_threshold:\n            self._open_circuit()\n        logger.warning(f\"Circuit Breaker Failure: {reason}. Count: {self.failure_count}\")\n\n    def _record_success(self):\n        self.failure_count = 0\n        self.state = \"CLOSED\"\n\n    def _should_attempt_half_open(self):\n        if self.state == \"OPEN\":\n            if time.time() - self.last_failure_time > self.recovery_timeout:\n                return True\n        return False\n\n    def _open_circuit(self):\n        self.state = \"OPEN\"\n        logger.error(f\"Circuit Breaker OPENED after {self.failure_count} failures.\")\n```\n\nA major cause of \"cognitive blast radius\" is context pollution. If an agent reads a 5,000-line file, it may hallucinate dependencies. To mitigate this, the circuit breaker should also enforce **Context Sanitization**.\n\nBefore the agent begins its review, a preprocessing step chunks the codebase. The breaker tracks which chunks have been\n\ninjected into the agent's window and which chunks have triggered context overflow or semantic drift alerts. If the drift metric exceeds a predefined threshold—indicating the agent is losing focus on the actual code structure—the breaker trips the **Context Limiter**, truncating the prompt history and re-injecting a high-fidelity summary of the relevant architectural layers. This prevents the \"lost in the middle\" phenomenon where critical logic buried thousands of tokens away is ignored during the review.\n\nStatic thresholds are insufficient for agentic systems. A monolithic review of a core banking service requires different safety margins than a pull request touching a static documentation generator. We implement an adaptive controller that adjusts breaker parameters based on the repository's historical volatility and the agent's confidence metrics.\n\nThe core logic resides in a lightweight state machine. Here is a Python implementation using `dataclasses` and standard library components, designed to be drop-in compatible with LangChain or LlamaIndex agent frameworks.\n\n``` python\nimport time\nfrom enum import Enum\nfrom dataclasses import dataclass, field\nfrom typing import Optional, List, Callable\nimport threading\n\nclass BreakerState(Enum):\n    CLOSED = \"closed\"\n    OPEN = \"open\"\n    HALF_OPEN = \"half_open\"\n\n@dataclass\nclass BreakerConfig:\n    failure_threshold: int = 5\n    recovery_timeout: float = 60.0\n    success_threshold: int = 3\n    context_drift_limit: float = 0.85  # 85% drift triggers trip\n    confidence_floor: float = 0.60     # Below this, count as failure\n\nclass AgenticCircuitBreaker:\n    def __init__(self, config: BreakerConfig = None):\n        self.config = config or BreakerConfig()\n        self.state = BreakerState.CLOSED\n        self.failure_count = 0\n        self.last_failure_time = 0.0\n        self.half_open_calls = 0\n        self._lock = threading.RLock()\n        self.history: List[dict] = []\n\n    def _trip(self, reason: str):\n        \"\"\"Transitions breaker to OPEN state.\"\"\"\n        with self._lock:\n            self.state = BreakerState.OPEN\n            self.last_failure_time = time.time()\n            self.failure_count = 0\n            self.history.append({\n                \"timestamp\": self.last_failure_time,\n                \"state\": \"OPEN\",\n                \"reason\": reason\n            })\n            # In a production system, emit a telemetry event here\n\n    def _attempt_reset(self):\n        \"\"\"Transitions from OPEN to HALF_OPEN if timeout has elapsed.\"\"\"\n        with self._lock:\n            if self.state == BreakerState.OPEN:\n                if time.time() - self.last_failure_time >= self.config.recovery_timeout:\n                    self.state = BreakerState.HALF_OPEN\n                    self.half_open_calls = 0\n                    self.history.append({\n                        \"timestamp\": time.time(),\n                        \"state\": \"HALF_OPEN\",\n                        \"reason\": \"Recovery attempt initiated\"\n                    })\n                    return True\n            return False\n\n    def execute(self, agent_call: Callable, *args, **kwargs):\n        \"\"\"\n        Wraps the agent's execution step.\n\n        Args:\n            agent_call: The function representing the agent's next step.\n            *args, **kwargs: Arguments passed to the agent.\n\n        Returns:\n            The result of the agent_call if successful.\n            Raises CircuitBreakerOpenError if the breaker is tripped.\n        \"\"\"\n        # Check if in OPEN state and eligible for HALF_OPEN\n        if self._attempt_reset() and self.state == BreakerState.HALF_OPEN:\n            # In HALF_OPEN, we allow limited traffic\n            if self.half_open_calls >= self.config.success_threshold:\n                self.state = BreakerState.CLOSED\n                self.half_open_calls = 0\n            else:\n                self.half_open_calls += 1\n\n        if self.state == BreakerState.OPEN:\n            raise CircuitBreakerOpenError(\n                f\"Breaker is OPEN. Last trip: {time.ctime(self.last_failure_time)}\"\n            )\n\n        try:\n            # Execute the agent step\n            result, metrics = agent_call(*args, **kwargs)\n\n            # Evaluate metrics for soft failures\n            if metrics.get(\"confidence\", 1.0) < self.config.confidence_floor:\n                self._handle_soft_failure(\"Low confidence score\")\n            elif metrics.get(\"context_drift\", 0.0) > self.config.context_drift_limit:\n                self._handle_soft_failure(\"High context drift detected\")\n            else:\n                self._handle_success()\n\n            return result\n\n        except Exception as e:\n            self._handle_hard_failure(str(e))\n            raise\n\n    def _handle_soft_failure(self, reason: str):\n        with self._lock:\n            self.failure_count += 1\n            self.history.append({\n                \"timestamp\": time.time(),\n                \"state\": self.state.value,\n                \"reason\": reason,\n                \"failure_count\": self.failure_count\n            })\n            if self.failure_count >= self.config.failure_threshold:\n                self._trip(reason)\n\n    def _handle_hard_failure(self, reason: str):\n        with self._lock:\n            # Hard failures (exceptions) trip the breaker immediately \n            # or contribute more heavily to the count depending on strategy.\n            # Here we use a 1-strike policy for critical errors.\n            self.failure_count += 1\n            self.history.append({\n                \"timestamp\": time.time(),\n                \"state\": self.state.value,\n                \"reason\": f\"Hard Failure: {reason}\",\n                \"failure_count\": self.failure_count\n            })\n            if self.failure_count >= 1: # Aggressive for autonomous agents\n                self._trip(reason)\n\n    def _handle_success(self):\n        with self._lock:\n            self.failure_count = 0\n            if self.state == BreakerState.HALF_OPEN:\n                # Successful call in half-open state progresses recovery\n                pass # Logic handled in execute() for state transition check\n\nclass CircuitBreakerOpenError(Exception):\n    pass\n```\n\nWhen the breaker trips, the workflow cannot simply halt; it must degrade gracefully. In an agentic code review pipeline, degradation involves shifting from autonomous generation to **Supervised Verification**.\n\n`OPEN`, the orchestrator pauses the autonomous loop. It packages the last N interactions, the specific code diff, and the reason for tripping (e.g., \"High context drift\") into a structured handoff payload. This payload is sent to a human reviewer's queue. The agent is effectively \"parked.\"`HALF_OPEN` state, the orchestrator does not retry the original complex task. Instead, it slices the review into smaller, non-overlapping chunks. For example, instead of reviewing an entire `UserSession` class, it reviews only the `validate_token` method. This reduces the context load and allows the agent to \"warm up\" its performance metrics before the breaker potentially re-closes.\nA circuit breaker without telemetry is a black box. Every state transition must be logged to a time-series database. Key metrics to track include:\n\n`recovery_timeout`.` context_drift` scores against token count. This often reveals a non-linear relationship where drift spikes exponentially past 40% of the model's context window, allowing you to set hard cap limits on chunk size.\nAgentic AI systems are powerful, but they are inherently stochastic. They do not fail gracefully; they fail confusingly. By wrapping these autonomous loops in circuit breakers, we shift the safety model from \"preventing errors\" (which is impossible with LLMs) to \"containing errors.\"\n\nThe key insight is that the breaker is not just a failure detector; it is a **context governor**. It enforces hygiene by forcing context sanitization and preventing the accumulation of hallucinated state. As we build more complex multi-agent systems, where one agent reviews another's code, these breakers become the immune system of the workflow. They allow the system to be aggressive in its exploration while remaining robust in its failure modes.\n\nStart small. Implement the breaker around your most critical, least-automated agent. Monitor the trip reasons. You will likely discover that the \"failures\" are not actually code errors, but context management issues that can be solved by better chunking strategies, not better prompts. The breaker will teach you where the agent's cognitive limits lie, allowing you to design workflows that respect those boundaries rather than fighting them.", "url": "https://wpnews.pro/news/circuit-breakers-for-agentic-ai-workflows-controlling-blast-radius-in-autonomous", "canonical_source": "https://dev.to/tamizuddin/circuit-breakers-for-agentic-ai-workflows-controlling-blast-radius-in-autonomous-code-review-431i", "published_at": "2026-09-12 00:02:12+00:00", "updated_at": "2026-09-12 00:22:12.747458+00:00", "lang": "en", "topics": ["ai-agents", "ai-safety", "ai-tools", "developer-tools", "mlops"], "entities": ["tamiz.pro", "ESLint", "SonarQube"], "alternates": {"html": "https://wpnews.pro/news/circuit-breakers-for-agentic-ai-workflows-controlling-blast-radius-in-autonomous", "markdown": "https://wpnews.pro/news/circuit-breakers-for-agentic-ai-workflows-controlling-blast-radius-in-autonomous.md", "text": "https://wpnews.pro/news/circuit-breakers-for-agentic-ai-workflows-controlling-blast-radius-in-autonomous.txt", "jsonld": "https://wpnews.pro/news/circuit-breakers-for-agentic-ai-workflows-controlling-blast-radius-in-autonomous.jsonld"}}