Agent Step Budget and Stop Conditions: What Actually Holds in Production
Unbounded agent loops are financial incidents waiting to be billed. How to set a step ceiling against a task class, detect stagnation and bound cost separately from steps.
An agent loop with no ceiling is a financial incident that has not been billed yet. That is the whole argument. An agent step budget and stop conditions are the least interesting code in the system and also the only code standing between a confused planner and an invoice nobody can explain, and most agent projects still ship without a bound that actually holds.
This is not a hypothetical. A July 2026 study scanned 6,549 LLM agent repositories, flagged 74 candidates and manually confirmed 68 infinite-loop defects across 47 separate projects at 91.9 percent precision. Every one of the 68 confirmed cases shared a single property: the repeated path was not covered by a strong bound. Sometimes no bound existed. More often one did, and it was weak, disabled, misconfigured or sitting outside the feedback path it was meant to constrain. Model-controlled termination was a root cause in 38.2 percent of cases and tool-controlled retry in 41.2 percent, which is to say the agent's own judgement was left doing the work a hard ceiling should have done.
The failure does not present as a crash. That is what makes it expensive. The agent keeps emitting well-formed tool calls, keeps narrating progress, keeps looking busy. Nothing in the trace says stop. Models carry no representation of "I have already tried this", so a history full of failed attempts reads to the planner as evidence that it should try harder, not that the approach is wrong. The loop is perfectly healthy from the inside. It is only from the billing dashboard that it looks like a fire.
The framework default is either too small to finish or too large to notice
Framework defaults are chosen so tutorials terminate, not so your production run stays affordable, and they move. LangGraph's Python package raised its default recursion limit from 25 supersteps to 1,000 starting in version 1.0.6. The JavaScript package still defaults to 25. The two now disagree by a factor of forty for the same graph. LangChain's classic `AgentExecutor` defaults `max_iterations` to 15 and `max_execution_time` to `None`, so there is a step cap and no wall-clock cap. OpenAI's Agents SDK defaults `max_turns` to a module constant (`DEFAULT_MAX_TURNS`, ten at the time of writing) and raises `MaxTurnsExceeded` when it is passed, though `max_turns=None` removes the cap entirely. The Claude Agent SDK documents its default for both `max_turns` and `max_budget_usd` as no limit.
Do the arithmetic at published list rates. Claude Opus 5 is $5 per million input tokens and $25 per million output tokens. Take a coding agent whose context has grown to 60,000 tokens and which emits 1,500 output tokens per model call. That is $0.30 of input and about $0.04 of output, roughly $0.34 per step with no prompt caching. Twenty-five steps costs $8.44. One thousand steps costs $337.50. Prompt caching changes the shape, because cache reads bill at roughly a tenth of the input rate, so a 90 percent cache hit brings the step to about $0.10 and the thousand-step run to about $95.
Both numbers are survivable once. Neither is survivable across ten concurrent sessions on a Friday night. The point is not the absolute figure. The point is that a version bump moved the blast radius of a single stuck run by forty times and nothing in your code changed.

A generous budget does not prevent bad planning. It launders it.
An agent step budget and stop conditions are two different controls
People conflate these and then wonder why one of them keeps failing. A budget is a scalar resource cap that counts down and is completely indifferent to what the agent is doing. A stop condition is a predicate evaluated against the trajectory: the goal is satisfied, the same action just repeated, two consecutive tool calls failed, a guardrail tripped. The budget is the last line of defence. The stop conditions are the ones you actually want to fire.
Before you set either, find out what your framework counts. LangGraph counts supersteps, and nodes that run in parallel belong to the same superstep. The Claude Agent SDK counts tool-use round trips only, so the final text-only turn is not counted. The OpenAI Agents SDK counts turns, where one turn is a model call plus the tool executions it triggers. A ceiling of 50 therefore means three different things across those three runtimes, and one superstep fanning out to five parallel model calls is one step against your counter and five line items on your bill. If your cap is denominated in a unit that does not track spend, it is not a cost control.
AutoGen gets the composition right by making conditions first-class objects that combine. `MaxMessageTermination(max_messages=10)` and `TokenUsageTermination(max_total_token=...)` can be joined with `&` and `|` alongside a text-mention condition. That is the correct mental model even if you are not using AutoGen: a set of independent predicates, evaluated every iteration, any one of which can end the run.
Set the ceiling per task class, not as one global constant
A single global step limit is a guess applied uniformly to tasks with wildly different shapes. A single-record extraction converges in a handful of steps, a retrieval-and-answer task in under ten, a multi-file code edit with a test loop in a few dozen and a browser workflow across an unfamiliar site in a hundred or more. Those are shapes rather than measurements, so treat them as a starting hypothesis and replace them with your own percentiles the week you have trace data. One constant that accommodates the browser task gives the extraction task ninety-seven steps of rope.
Derive the ceiling from your own traces instead. Log the step count of every successful run, bucketed by task class. Take the 99th percentile of successes in that class and add headroom, typically a further 30 to 50 percent. The common folk rule of three to five times the expected step count is a reasonable cold start when you have no trace data, but it is a placeholder and should be replaced the week you have a hundred completed runs. Recompute quarterly, because a prompt change or a model upgrade shifts the distribution.
Route the ceiling with the task, not with the deployment. In practice that means the classifier or router that picks the plan also picks the budget, and the budget travels in the run context. LangGraph exposes the current step counter at `config["metadata"]["langgraph_step"]` and ships a `RemainingSteps` managed value precisely so a node can branch to a degraded fallback before the exception fires. Use it. An agent that knows it has four steps left should be writing its best partial answer, not opening a new line of enquiry.
Stagnation shows up long before the ceiling does
The ceiling is a blunt instrument that fires at the end. Stagnation detection fires in the middle, which is where the money is. Three detectors cover most of what I have seen go wrong, and all three are cheap.
First, repeated tool calls. Hash the tuple of tool name plus canonicalised arguments, sorted keys, normalised whitespace, and keep the last twenty hashes. Two identical calls returning identical results is a warning. Three is a stop. MIRAGE-Bench, which analyses agent trajectories for repetition and hallucination, treats a third repetition of the same action as already past the point where a person would have revised the plan, which is a useful calibration point for the threshold.
Second, absence of state change. The agent is supposed to be changing something: a file tree, a row count, a ticket status, a cart. Fingerprint that thing, hash it, and compare across steps. N consecutive steps with no fingerprint delta means the agent is reading and thinking and spending, and not acting. This detector is the one that catches the expensive case where every tool call is superficially novel but nothing in the world moves.
Third, oscillation. Keep the action sequence and look for short cycles: A, B, A, B is a two-cycle; A, B, C, A, B, C is a three-cycle. Multi-agent setups are especially prone to this, and the taxonomy from the infinite-loop study bears it out. Retry feedback without a bound accounted for 25.0 percent of confirmed defects, unbounded tool-call iteration 23.5 percent and multi-agent chat with no turn bound 20.6 percent. Two agents handing work back and forth is a canonical way to burn a five-figure budget while both of them behave exactly as designed.
Cost ceilings and step ceilings catch different failures
Steps are a poor proxy for cost because the cost per step is not constant. Context accumulates, so step forty is more expensive than step four. Reasoning effort varies by turn. Parallel tool calls multiply model invocations under a single counter. Subagents spawn whole trajectories that the parent's step counter never sees. You need a bound denominated in money as well as one denominated in iterations, and they are not substitutes.
Anthropic's stack is a clean illustration of the three distinct layers. `max_tokens` is a hard per-response ceiling the model is never told about. A `task_budget` is advisory and token-denominated, minimum 20,000 tokens, and the server injects a countdown the model can see so it paces itself and wraps up rather than being cut mid-thought. A Managed Agents session budget is the hard one: a dollar cap expressed in minor units as an integer string, so `{"type": "limit", "max_list_cost": {"amount": "2500", "currency": "USD"}}` is $25.00.
Two operational details in that session budget are worth copying into whatever you build. Enforcement is a pre-request gate, so the request in flight when the cap is crossed still completes and the final figure can exceed the cap by at most one model request per running thread. Treat a cost ceiling as a bound on new work, never as an exact stop. And the reported cost is rounded to the nearest cent while enforcement compares exact amounts, so the stop reason is the signal that the cap was reached, not the number on the dashboard.
Know what your cost ceiling does not cover. Anthropic's list cost prices model tokens, web searches at $10 per thousand and session runtime at $0.08 per hour. It does not price what your own tools spend downstream: the database queries, the paid third-party APIs, the storage. An agent looping on a metered external service is unbounded by every control discussed so far. Organisation and workspace monthly spend limits sit underneath all of it as a backstop, and workspace limits must be set below the organisation limit. That backstop catches the case where each run is properly bounded and something launched four hundred of them.
When the budget is hit, fail loudly and hand back the trace
This is where most implementations quietly betray you. LangChain's `AgentExecutor` defaults `early_stopping_method` to `"force"`, and the `"force"` path returns an `AgentFinish` whose output is the constant string `"Agent stopped due to iteration limit or time limit."` That is a successful return. Not an exception, not an error flag, a normal finish carrying a plausible-looking text field. If that string lands in a summary column, a customer-facing reply or a downstream ticket, the failure has been laundered into data and will be discovered weeks later by somebody reading a report.
The alternatives are better behaved and worth copying. LangGraph raises GraphRecursionError. The OpenAI Agents SDK raises MaxTurnsExceeded. The Claude Agent SDK does both: it yields a ResultMessage whose subtype is error_max_turns or error_max_budget_usd, and a single-shot query then raises with the failure text, while a streaming-input session stays alive. Critically the result field is absent on every error subtype, which forces the caller to branch instead of reading a field that happens to be populated. All subtypes still carry `total_cost_usd`, `usage`, `num_turns` and `session_id`, so the failure arrives with its own forensics attached.
Never silently truncate. The handoff to a human should carry the task class, which bound fired, which detector tripped if any, the last five to ten actions with their arguments, the accumulated cost and a resume handle. Resume matters more than people expect: restarting a truncated run pays for the entire trajectory a second time.
The pause semantics in Managed Agents are the pattern to imitate. A session at its budget goes idle with `stop_reason: budget_reached`. It is not terminated. History and sandbox survive, only settle events that resolve work already in progress are accepted, and raising or removing the cap resumes the paused work automatically. Pausing with state intact and asking a human whether to extend is a genuinely different product decision from killing the process, and it is almost always the right one.
The strongest objection is that tight budgets kill runs that would have finished
This objection is correct and I have caused it. A stagnation detector tuned aggressively will stop a legitimately exploratory run at exactly the moment exploration looks most repetitive, because searching a space properly involves trying similar things. Early-exit research documents a related hazard from the other direction: confidence and stability signals are unreliable stopping cues precisely on the hard problems where stopping matters, because a model converging on a good answer and a model stuck repeating an early guess can look much the same from outside. Cite the specific paper you are leaning on in SOURCES rather than gesturing at a body of work.
There is a subtler version. Work on budget-aware tool use found that simply raising the tool-call budget does not improve agent performance, because agents have no awareness of the budget. Agents terminate early believing they have found a sufficient answer or concluding they are stuck, unaware that resources remain unspent. Giving the model a visible budget tracker changed the scaling curve where raising the ceiling alone did not. So a step budget that is invisible to the agent buys you cost protection and nothing else.
Four things make the tradeoff manageable. Make the budget visible to the model so it paces and produces a graceful partial answer rather than being severed. Require multiple independent signals before a stagnation detector fires, not one, since a single repeated call is normal and a repeated call plus an unchanged state fingerprint is not. Resume rather than restart, so an over-tight ceiling costs one operator decision instead of a full re-run. And log every budget stop as an incident with its task class, so the ceiling gets tuned from evidence rather than raised reflexively the first time somebody complains.
A generous budget hides a broken plan
Here is the failure mode nobody puts in the postmortem. Set the ceiling at 1,000 when your median task finishes in six steps, and the 400-step runs stop being failures. They become slow successes. Nothing alerts. The run completes, the output is plausible, the cost is absorbed into a monthly aggregate and the broken plan that caused it survives to the next release. A generous budget does not prevent bad planning. It launders it.
The instrument that catches this is not the ceiling itself but the ratio. Track steps used divided by ceiling, per task class, at the 50th and 95th percentiles, weekly. That ratio moves before the success rate does. A p95 climbing from 0.3 to 0.6 over three weeks is a planning regression that is still producing correct answers, and it is the earliest warning you are going to get. Success rate will not tell you, because success rate is measured on the runs that finished. Success rate will not tell you that, because success rate is measured on the runs that finished.
Which is the actual reason to keep the ceiling tight. A step budget is not there to stop the agent. It is there to tell you the agent should have stopped, and a ceiling generous enough never to fire is a sensor you disconnected on purpose. An agent that routinely finishes at 80 percent of its budget is not efficient. It is one prompt change away from an incident, and the budget is the only instrument you own that reports it before the invoice does.
Tools referenced
LangGraph, reviewed here: LangGraph review.
OpenAI Agents SDK, reviewed here: OpenAI Agents SDK review.
Langfuse, reviewed here: Langfuse review.
Arize Phoenix, reviewed here: Arize Phoenix review.
Temporal, reviewed here: Temporal review.
Braintrust, reviewed here: Braintrust review.
Sources
LangGraph graph API: recursion limit, supersteps and RemainingSteps (LangChain docs): https://docs.langchain.com/oss/python/langgraph/graph-api
Running agents: max_turns and MaxTurnsExceeded (OpenAI Agents SDK docs): https://openai.github.io/openai-agents-python/running_agents/
How the agent loop works: turns, max_turns, max_budget_usd and result subtypes (Claude Agent SDK docs): https://code.claude.com/docs/en/agent-sdk/agent-loop
Task budgets: advisory token budgets for agentic loops (Claude Platform docs): https://platform.claude.com/docs/en/build-with-claude/task-budgets
Session budgets: hard dollar caps, pre-request gating and budget_reached (Claude Platform docs): https://platform.claude.com/docs/en/managed-agents/budgets
Hou, Wang, Zhao and Wang, 'When Agents Do Not Stop: Uncovering Infinite Agentic Loops in LLM Agents', arXiv:2607.01641: https://arxiv.org/abs/2607.01641
Liu et al., 'Budget-Aware Tool Use Enables Effective Agent Scaling', arXiv:2511.17006: https://arxiv.org/abs/2511.17006
LangChain AgentExecutor source: max_iterations, early_stopping_method and return_stopped_response: https://github.com/langchain-ai/langchain/blob/master/libs/langchain/langchain_classic/agents/agent.py
Zhang et al., 'MIRAGE-Bench: LLM Agent is Hallucinating and Where to Find Them', arXiv:2507.21017: https://arxiv.org/abs/2507.21017
Frequently Asked Questions
What is a good default step budget for an AI agent?
There is no single good default, because the right ceiling depends on the task class. Derive it from your own traces: take the 99th percentile step count of successful runs in that class and add 30 to 50 percent headroom. With no trace data yet, three to five times the expected step count is a reasonable cold start. Extraction tasks converge in a handful of steps, retrieval-and-answer in under ten, multi-file code edits with a test loop in a few dozen, and browser workflows can legitimately need a hundred or more. Those are shapes to start from, not measurements. One global constant that accommodates the browser task gives the extraction task ninety-seven steps of rope.
How do I detect that an AI agent is stuck in a loop?
Use three cheap detectors together rather than one. Hash each tool call as tool name plus canonicalised arguments and stop after three identical calls returning identical results. Fingerprint the state the agent is supposed to be changing, such as a file tree, row count or ticket status, and flag N consecutive steps with no change to that fingerprint. Watch the action sequence for short cycles, since A, B, A, B oscillation is common in multi-agent handoffs. Requiring two independent signals before stopping cuts false kills on genuinely exploratory runs.
Should I use a step limit or a token and cost limit for my agent?
Both, because they catch different failures. Cost per step is not constant: context accumulates, reasoning effort varies, parallel tool calls multiply model invocations under one counter and subagents spawn trajectories the parent counter never sees. A step ceiling bounds iterations. A dollar ceiling bounds spend. Neither substitutes for the other, and organisation or workspace monthly spend limits should sit underneath both as a backstop for the case where every individual run is bounded but something launched four hundred of them.
What should happen when an agent hits its step budget?
Fail loudly and hand back the trace. Never return a plausible-looking string as a successful result, because that laundering turns a failure into data that reaches a downstream system. LangChain's AgentExecutor does exactly this by default: early_stopping_method is force, which returns a normal AgentFinish carrying the text 'Agent stopped due to iteration limit or time limit.' Better patterns raise an exception or return a typed error result with no output field, and carry the cost, turn count and a session id so the run can be resumed rather than restarted.
Why did my LangGraph agent hit the recursion limit?
LangGraph counts supersteps, where nodes running in parallel belong to the same superstep, and raises GraphRecursionError when the cap is reached. The Python package raised its default from 25 supersteps to 1,000 starting in version 1.0.6, while the JavaScript package still defaults to 25, so the same graph can behave very differently across the two. Set it explicitly with recursion_limit as a standalone config key, not inside configurable. If it fires well past the expected step count, the cause is usually a cycle with no verified exit rather than a genuinely long task.
Does a bigger step budget make an AI agent better?
Not on its own. Research on budget-aware tool use found that raising the tool-call budget alone does not improve performance, because agents have no awareness of how much budget remains. They stop early believing they are finished or stuck while resources sit unspent. Making the remaining budget visible to the model changed the scaling curve where raising the ceiling did not. A budget the agent cannot see buys cost protection and nothing else, and a ceiling generous enough never to fire is a sensor you disconnected on purpose.
Read next
China's University Major Cuts Are AI Policy, and Nigeria Should Read the Fine Print
The latest analysis essay.
Working on something in this space?
If this analysis is close to a problem you're thinking about, say so. I read every message personally.
Start a conversation