What a loop budget is
A loop budget is a set of hard limits your own code checks on every turn of an agent loop (a maximum number of steps, a maximum spend and a limit on steps without progress) so the loop always stops, and records why it stopped.
It sits outside the model. The model can decide what to do next; the budget decides whether there is a next.
Who runs into this
Anyone running a model in a loop: a coding agent that fixes code until the tests pass, a research agent that searches until it’s satisfied, a support bot that calls tools until a ticket is resolved, or a “self-correcting” step that asks the model to review and redo its own output.
The problem
An agent loop runs until the model says it’s done. Anthropic’s docs describe the basic client-side loop in exactly those terms: while the model asks for a tool, run the tool and call the model again. Nothing in that shape stops a model that never says it’s done, because it keeps retrying a fix that doesn’t work or flips between two answers.
Each turn also tends to cost more than the one before. The Messages API is stateless, so every request carries the whole conversation so far, tool results included, and the input grows with every step. A loop left running unattended can spend far more than anyone planned before a person notices.
How it works
Wrap the loop in a function that owns three limits and checks them after every step:
- Steps. A hard ceiling on iterations. It always ends the loop, even if every other check is wrong.
- Spend. Add up the cost of each call from the token usage the API returns, and stop when the total reaches the limit.
- Progress. Decide what “closer to done” means for this task, such as the set of failing tests, and turn it into a key. If the loop keeps landing on states it has already seen, it’s going in circles, so stop it.
Every stop returns a reason (done, max_steps, max_cost or no_progress), so you can tell a finished task from one that was cut off and show the user the right thing.
export type StopReason = 'done' | 'max_steps' | 'max_cost' | 'no_progress'
export interface Budget {
maxSteps: number
maxCostUsd: number
/** Stop after this many steps in a row that reach a state seen before. */
maxStepsWithoutProgress: number
}
export interface StepResult<S> {
state: S
done: boolean
costUsd: number // what this step spent, from the API's token usage
}
export interface LoopOutcome<S> {
state: S
steps: number
costUsd: number
stopReason: StopReason
}
export async function runWithBudget<S>(
initial: S,
step: (state: S) => Promise<StepResult<S>>,
// What counts as progress, e.g. the set of failing tests, as a string.
progressKey: (state: S) => string,
budget: Budget,
): Promise<LoopOutcome<S>> {
let state = initial
let costUsd = 0
let stalled = 0
const seen = new Set([progressKey(initial)])
for (let steps = 1; steps <= budget.maxSteps; steps++) {
const result = await step(state)
state = result.state
costUsd += result.costUsd
const stop = (stopReason: StopReason) => ({ state, steps, costUsd, stopReason })
if (result.done) return stop('done')
// Checked after the step: the call that crosses the line is already paid.
if (costUsd >= budget.maxCostUsd) return stop('max_cost')
const key = progressKey(state)
stalled = seen.has(key) ? stalled + 1 : 0
seen.add(key)
if (stalled >= budget.maxStepsWithoutProgress) return stop('no_progress')
}
return { state, steps: budget.maxSteps, costUsd, stopReason: 'max_steps' }
}The spend check runs after each call, so the call that crosses the limit has already been paid for. The Claude Agent SDK behaves the same way: when its budget stops a run, the response that crossed the line is still counted in the reported cost. Set your limit with that last call in mind, or estimate the next call’s cost and check before making it.
Why it matters for AI-written code
Ask a coding agent for “an agent that keeps fixing the code until the tests pass” and you get what you asked for: a loop whose only exit is success. How much a task may cost and how many attempts it deserves are product decisions, so they rarely appear in generated code unless you state them.
Framework defaults don’t settle it either. They differ between libraries and can be switched off: the OpenAI Agents SDK, for example, lets you pass max_turns=None to disable its turn limit.
Alternatives and how they compare
- Framework turn limits. The OpenAI Agents SDK raises
MaxTurnsExceededwhen a run passesmax_turns. LangGraph raisesGraphRecursionErrorwhen a graph passes its recursion limit. The Claude Agent SDK hasmaxTurnsandmaxBudgetUsdoptions and reports which one ended the run. Use them. They don’t check progress, and a cost computed in the client isn’t your bill: Anthropic’s docs call the SDK’s cost fields client-side estimates. - Provider spend limits. OpenAI lets you set hard spend limits on an organization or project, after which requests fail with a 429. The Claude Console lets you give a workspace its own spend limit below the organization’s. These are account-level backstops for all traffic, not limits for one task, and OpenAI notes enforcement isn’t instant, so recorded spend can go slightly over.
- Anthropic task budgets. A beta API feature that tells the model how many tokens it has for the whole loop, so it can pace itself. The docs call it a soft hint, not a hard cap, so keep your own limits as well.
max_tokens. Caps the output of a single request, not the number of requests, so it doesn’t stop a loop.- A timeout. A wall-clock limit ends the loop eventually, but cost follows calls, not minutes, so a fast loop can spend a lot before the timer fires.
How to use it in your app
- Find every loop that calls a model: agent runs, retry-until-valid steps, review-and-fix cycles.
- Choose limits per task from what you’ve measured: how many steps and how much money successful runs take, plus some headroom.
- Define progress for each loop as a key you can compare, such as the failing tests or the open questions.
- Return the stop reason to the caller and log it with the run’s ID.
- Alert on
no_progressandmax_cost: they mean a prompt, a tool or an input needs attention.
What to tell your coding agent. Paste this into Claude Code, Codex or Antigravity:
Find every loop in this project that calls a model or an agent more than once. Wrap each one so it stops at whichever comes first: the task is done, a maximum number of steps, a maximum spend in US dollars added up from the token usage each API response returns, or N steps in a row that come back to a state seen before (define that state for each loop, for example the set of failing tests). Return the stop reason with the result, log it with the run ID, and make the limits configurable. Add a test for each stop reason.
To make it a standing rule instead of a one-off request, put the same text in the instructions file your agent reads at the start of a session: CLAUDE.md for Claude Code, AGENTS.md for Codex, and AGENTS.md, GEMINI.md or a file in .agents/rules/ for Antigravity. ArcLab doesn’t connect to any of these tools; you paste the text yourself.
Learn it in depth
The Convergence, Divergence & Loop Termination module opens the Loop Engineering track with an example incident, a self-correcting review agent told to loop until the code was clean. It covers convergence metrics, hard iteration bounds, divergence detection and oscillation detection, and ends with a termination design you write for one of your own loops.
Sources
The official documentation this guide was checked against:
- How tool use works, Claude API docs, Anthropic: the client-side agentic loop
- Using the Messages API, Claude API docs, Anthropic: the API is stateless; full history on every request
- Running agents, OpenAI Agents SDK docs: max_turns, MaxTurnsExceeded, max_turns=None
- Graph API: recursion limit, LangGraph docs, LangChain: GraphRecursionError
- Agent SDK reference: TypeScript, Claude Code docs, Anthropic: maxTurns and maxBudgetUsd
- Track cost and usage, Claude Code docs, Anthropic: cost fields are client-side estimates; the response that crossed the budget
- Task budgets, Claude API docs, Anthropic: beta; advisory, not enforced; max_tokens is per request
- Spend limits, OpenAI API docs: hard limits return 429; enforcement isn’t instantaneous
- Creating and managing workspaces in the Claude Console, Claude Help Center: workspace spend limits
- How Claude remembers your project, Claude Code docs, Anthropic: CLAUDE.md is read at the start of every session
- AGENTS.md, Codex docs, OpenAI: Codex reads AGENTS.md before doing any work
- Rules, Google Antigravity docs: workspace rules in AGENTS.md, GEMINI.md or .agents/rules/
Last checked 7 October 2026 against the sources above.
Found a mistake? Tell us.