Agent Governance ToolkitAgent Governance Toolkit 4.1.0 · Python 3.10+
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
47 small wins to finish your pathNext lesson →

Circuit breakers and an error budget

A circuit breaker wraps a tool call, counts its failures, and after a threshold stops calling a failing dependency for a while, so one broken service does not have the agent retry it on every message.

Last updated: 14 Oct, 2026 · Agent Governance Toolkit 4.1.0

The Sandbox policy, without running a sandbox lesson limited how an action runs. Reliability is the next concern: what happens when a tool keeps failing. The toolkit ships a circuit breaker in agent_os.circuit_breaker and an error budget in agent_sre, and both have a signature or two that catch you out, so every one here was checked against the install before it was written.

Describing when to trip

Syntax: CircuitBreakerConfig sets the number of failures that opens the breaker and the delay before it tries again. The recovery timeout is kept small here so the example can show the breaker healing without a long wait.

python
from agent_os.circuit_breaker import CircuitBreaker, CircuitBreakerConfig, CircuitOpenError

# open after 3 failures; retry after the recovery timeout (kept small for the demo)
config = CircuitBreakerConfig(failure_threshold=3, recovery_timeout_seconds=0.3)

The retry delay's name is not the obvious one

The first trap is the parameter name. The obvious recovery_timeout does not exist and the constructor rejects it; the real name is recovery_timeout_seconds. Guessing the short name raises a TypeError rather than being quietly ignored.

ExampleThe obvious keyword name raises
from agent_os.circuit_breaker import CircuitBreakerConfig

# the obvious name does not exist; the real one is recovery_timeout_seconds
CircuitBreakerConfig(failure_threshold=3, recovery_timeout=5)

With the right name the config builds and holds the two values, ready to hand to a breaker.

ExampleThe correct keyword builds the config
config = CircuitBreakerConfig(failure_threshold=3, recovery_timeout_seconds=0.3)
print("threshold      :", config.failure_threshold)
print("recovery seconds:", config.recovery_timeout_seconds)

A breaker around the shop agent's tool call

Wrap the tool in a breaker and call it through breaker.call. While a dependency is failing, each real failure counts; on the third the breaker opens, and from then on it refuses the call without touching the tool at all.

ExampleThe breaker opens after three failures and then short-circuits
breaker = CircuitBreaker("order_status", config=config)
calls = {"n": 0}

def order_status():                 # the real tool, currently failing
    calls["n"] += 1
    raise RuntimeError("downstream down")

for _ in range(3):                  # three real failures trip the breaker
    try: breaker.call(order_status)
    except RuntimeError: pass
print("state after 3 failures:", breaker.get_state().value)

# the breaker is open, so the next call is refused without calling the tool
try: breaker.call(order_status)
except CircuitOpenError: print("open: tool skipped, calls still", calls["n"])

Recovering after the timeout

Once the recovery timeout passes, the breaker lets one trial call through. If the dependency is healthy again, that call succeeds and the breaker closes; the agent is back to normal with no manual reset.

ExampleA healthy trial call closes the breaker
import time

time.sleep(0.35)                    # wait past the recovery timeout

def order_status():                 # the dependency is healthy again
    return "A-2291: out for delivery"

print("recovered:", breaker.call(order_status))
print("state:", breaker.get_state().value)

Tracking an error budget

An ErrorBudget counts how much failure a target can absorb before you must act. The trap is the method name: there is no .consume a reader might reach for. You record each event as good or bad with record_event, and read consumed, remaining and is_exhausted. A burn_rate method is there too, for how fast the budget is being spent.

ExampleRecording events against an error budget
from agent_sre import ErrorBudget

# there is no .consume; you record each event as good or bad
print("has consume:", hasattr(ErrorBudget, "consume"))

budget = ErrorBudget(total=20)              # 20 failures allowed in the window
for good in [True, True, False, False, False]:
    budget.record_event(good=good)

print("consumed :", budget.consumed)
print("remaining:", budget.remaining)
print("exhausted:", budget.is_exhausted)

An SLI is a class you subclass

The last trap is SLI, the service-level indicator that feeds a budget. It is abstract: it declares a collect method with no body, so calling SLI() raises rather than giving you an object. You subclass it with your own collect, or use one of the concrete indicators the toolkit ships.

ExampleThe abstract SLI cannot be instantiated
from agent_sre import SLI

# SLI declares an abstract collect(); it cannot be built directly
SLI()

What the breaker and the budget gave you

  • The breaker opened on the third failure and then refused the call without touching the tool, so a dead dependency is not hammered on every message.
  • It healed itself after the recovery timeout, letting one trial call through and closing on success.
  • The budget counts, it does not block. record_event tallies good and bad calls so you can alert before the budget is spent; enforcing a limit is still the gate's job.

The three names that trip you up

You might writeWhat happensUse instead
CircuitBreakerConfig(recovery_timeout=5)TypeErrorrecovery_timeout_seconds=5
budget.consume(...)AttributeError, no such methodbudget.record_event(good=...)
SLI(...)TypeError, it is abstractA subclass with a collect method

Where you use a circuit breaker

  • A tool that calls a flaky external service, so a run of failures opens the breaker instead of retrying forever.
  • An agent in a loop, where one broken tool should not stall every turn while it times out.
  • A reliability target, where the error budget tells you how much failure is left before the target is missed.
Watch out. The circuit breaker and the error budget live in different packages, agent_os.circuit_breaker and agent_sre, and their signatures were the three traps above. Check each against the install before you write it, because a wrong keyword raises but a wrong assumption about SLI or .consume fails only when you reach the line.
Try it yourself
  • Lower failure_threshold to 2 and confirm the breaker opens one failure sooner.
  • Call the breaker while it is open and read the CircuitOpenError message in full.
  • Build an ErrorBudget(total=2), record three bad events, and watch is_exhausted turn true.

This is what real progress feels like.