How-To

Build an AI Agent Token Budget Circuit Breaker

Set hard token, step, tool-call, and error limits so autonomous agents stop predictably before a bad run becomes expensive.

  • #ai-agents
  • #token-budget
  • #circuit-breaker
  • #cost-control
  • #howto

If an autonomous agent can retry, call tools, or hand work to another agent, give it a hard per-run budget before giving it a larger context window. The practical control is an AI agent token budget circuit breaker: reserve capacity before every model call, record actual usage afterward, and stop the run when tokens, errors, steps, or tool calls cross a defined threshold.

This is separate from a provider account spending cap. Account caps are useful backstops, but a per-run breaker can identify the failing task, save partial state, and stop one loop without taking down unrelated work.

Why agents overspend

Runaway cost usually comes from multiplication rather than one unusually long answer:

  • Reasoning loops: The agent repeatedly observes the same state, proposes another action, and never reaches its completion condition.
  • Automatic retries: A timeout or malformed tool result triggers retries. If the failed response is added to history, every retry may carry more context.
  • Growing context: Tool output, logs, retrieved documents, and previous model messages are resent on later turns. Input tokens can then dominate the run.
  • Fan-out: One planner starts several workers, each with its own model calls and tool calls. A budget stored only inside one worker does not cover the whole run.
  • Parallel calls: Multiple calls can pass a non-atomic budget check at the same time and collectively exceed the limit.

A maximum output-token setting only limits one response. It does not cap cumulative input, retries, tools, or child agents. Treat the run as the accounting boundary and use one shared, atomic ledger.

1. Set a per-run token budget

Choose a budget from observed successful runs, not from the model’s context-window size. Start by logging a representative batch, separate tasks by class, and set each class near a high percentile of successful usage. A small documentation edit and a repository migration should not share one allowance.

Count all billable token categories reported by the provider. At minimum, track input and output tokens. Keep cached input and reasoning tokens as separate fields when the API exposes them, because their billing or availability may differ. Store both the original provider usage object and your normalized total.

Reserve enough tokens for the next call before sending it. Post-call accounting alone can overshoot because the response has already been generated. For example, if 18,000 of a 20,000-token budget is used and the next request may consume 4,000, reject or downgrade that call before dispatch.

The numbers below are example policy values, not vendor recommendations:

ControlExamplePurpose
Total tokens per run100,000Primary cumulative ceiling
Reserved tokens per model callestimated input + maximum outputPrevent one-call overshoot
Steps30Stop reasoning loops
Tool calls40 total, 8 per toolContain broad and tool-specific loops
Consecutive errors3Stop a persistently broken dependency
Rolling error rate40% over the last 10 operationsCatch intermittent failure storms

2. Implement the circuit breaker

Keep breaker state outside the prompt. The model can be told that a budget exists, but application code must enforce it. This compact Python example is intentionally framework-neutral:

from collections import deque
from dataclasses import dataclass, field

class BudgetExceeded(RuntimeError):
    pass

@dataclass
class RunGuard:
    token_limit: int
    step_limit: int
    tool_limit: int
    used_tokens: int = 0
    steps: int = 0
    tool_calls: int = 0
    consecutive_errors: int = 0
    recent_results: deque = field(default_factory=lambda: deque(maxlen=10))

    def before_model(self, estimated_input: int, max_output: int):
        reserve = estimated_input + max_output
        if self.used_tokens + reserve > self.token_limit:
            raise BudgetExceeded("token budget would be exceeded")
        if self.steps >= self.step_limit:
            raise BudgetExceeded("step limit reached")

    def after_model(self, input_tokens: int, output_tokens: int):
        self.used_tokens += input_tokens + output_tokens
        self.steps += 1
        if self.used_tokens >= self.token_limit:
            raise BudgetExceeded("token budget reached")

    def before_tool(self):
        if self.tool_calls >= self.tool_limit:
            raise BudgetExceeded("tool-call limit reached")
        self.tool_calls += 1

    def record_result(self, ok: bool):
        self.recent_results.append(ok)
        self.consecutive_errors = 0 if ok else self.consecutive_errors + 1
        error_rate = 1 - (sum(self.recent_results) / len(self.recent_results))
        if self.consecutive_errors >= 3:
            raise BudgetExceeded("three consecutive errors")
        if len(self.recent_results) == 10 and error_rate >= 0.40:
            raise BudgetExceeded("rolling error threshold reached")

Call before_model immediately before provider dispatch and after_model as soon as usage metadata arrives. Call before_tool before execution, not after. Wrap these mutations in a lock or transactional store when workers run concurrently. All child agents should receive the same run ID and debit the same ledger.

When the breaker opens, cancel queued work, prevent new tool calls, save the latest checkpoint, and return a structured stop reason. Do not feed the limit error back into an unrestricted agent loop; that can turn the safety control into another retry trigger.

3. Add framework-native ceilings

Use framework limits as defense in depth. They do not replace token accounting because a turn or graph step can vary greatly in token cost.

As of 2026-07-20, the OpenAI Agents SDK runner documentation says max_turns raises MaxTurnsExceeded when the agent exceeds the configured turn count. It also supports an error handler that returns a controlled final result. Set a finite value; passing None disables that ceiling.

As of 2026-07-20, LangChain’s built-in middleware documentation includes ModelCallLimitMiddleware and ToolCallLimitMiddleware, with per-run and per-thread limits. For a hard stop, check the documented exit behavior: some modes block a tool call and let the model continue, while an error mode terminates immediately.

For graph-based agents, the LangGraph Graph API documentation describes recursion_limit, GraphRecursionError, and a RemainingSteps value for routing to a fallback before the limit is reached. That gives you a clean place to save partial output instead of treating every ceiling as an unhandled crash.

4. Log and alert on the stop

Emit one structured event for every model call, tool call, retry, and breaker transition. Include run_id, task class, model, step, tool name, input tokens, output tokens, cumulative tokens, latency, success, error category, and stop reason. Exclude prompts, secrets, and raw tool output unless your retention policy explicitly permits them.

Alert on conditions that need action, not on every stopped run. Page an operator for repeated breaker openings across several runs, a shared dependency failure, or budget consumption far above the task baseline. Send a lower-priority notification for an isolated run that stopped cleanly and preserved a checkpoint.

Finally, test the controls with deterministic failures: a tool that always errors, a model stub that always requests the same tool, parallel workers racing for the last token reservation, and a response that uses its full allowed output. The breaker is ready when each test ends with a recorded reason, no further dispatches, and recoverable partial state.

Deployment checklist

  • Assign one run ID and one shared budget ledger to the entire agent tree.
  • Reserve estimated input plus maximum output before each model call.
  • Reconcile the reservation with provider-reported usage afterward.
  • Enforce total and per-tool call ceilings before execution.
  • Stop on consecutive errors and a rolling error-rate threshold.
  • Add a framework-native turn or graph-step limit.
  • Persist a checkpoint and structured stop reason when the breaker opens.
  • Test loops, retries, oversized context, and concurrent reservations before production use.

A token budget controls cumulative consumption; step, tool, and error ceilings control the paths that create it. Put all four in application code, then use framework limits and provider account controls as additional backstops.