Stopping runaway agents: turn, token and spend caps per tool
Shared by Pavel · Updated 2026-11-09
Stopping runaway agent loops and cost stampedes across many agents
Read date for all sources: 2026-11-09. Written for a founder running a small human team and a large agent fleet.
Labels: [vendor] = the vendor's own docs or source code. [opinion] = a reading, untested. Nothing here was run against a live agent: every behaviour below comes from docs and source.
Questions people are asking
| Question | Where / date | State |
|---|---|---|
| "How are you guys stopping agents from silent loops/stampedes? Standard step_counters feel like garbage." | github.com/microsoft/autogen/discussions/8135, 2026-08-31 | No accepted answer |
| Same question, cross-posted | forum.langchain.com/t/4479, 2026-09-02 | No accepted answer |
| "How are you capping spend on LangChain / LangGraph agents in production?" | forum.langchain.com/t/4917, 2026-10-27 | Partly answered |
| Inherited recursion-depth budget for Agent.as_tool delegation | github.com/openai/openai-agents-python/issues/5453, 2026-10-29 | Open |
| A run stopping at max_turns with conversation_id leaves tool calls without outputs; later turns return 400 | github.com/openai/openai-agents-python/issues/5642, 2026-11-09 | Open |
| RetryPolicy(max_attempts) never gives up on a node that calls interrupt() | github.com/langchain-ai/langgraph/issues/9493, 2026-10-30 | Open |
| Antigravity side question re-ran the same turn about 259 times, about 30M tokens, weekly quota fell from about 50% to about 30% | discuss.ai.google.dev/t/198137, 2026-11-04 | No accepted answer |
| Gemini API billing cap increase; unexpected bills from infinite-loop code | discuss.ai.google.dev/t/199268 (2026-11-09), /171429 | Not answered |
| Claude Code Ultra weekly allowance reported at 20% after reset and exhausted in about 10 hours; cause unconfirmed | github.com/anthropics/claude-code/issues/108111, 2026-11-07 | Open |
Per-tool caps [vendor]
| Tool | Setting | Default | Where set | At the cap |
|---|---|---|---|---|
| OpenAI Agents SDK (Python) | max_turns | 10 (DEFAULT_MAX_TURNS in src/agents/run.py); None disables | Runner.run, run_sync, run_streamed | Raises MaxTurnsExceeded, or returns a fallback via error_handlers={"max_turns": fn} |
| OpenAI Agents SDK | ModelSettings.timeout; tool timeout_behavior="raise_exception"; max_function_tool_concurrency | concurrency None (all calls start) | Model settings, tool definition, run config | ModelTimeoutError, ToolTimeoutError |
| LangGraph | recursion_limit | Source on main: 10007, overridable by env LANGGRAPH_DEFAULT_RECURSION_LIMIT; the docs page states no number | config={"recursion_limit": N} | Raises GraphRecursionError |
| AutoGen AgentChat | MaxMessageTermination(max_messages) | required | termination_condition | Run ends, TaskResult.stop_reason set |
| AutoGen | TokenUsageTermination(max_total_token, max_prompt_token, max_completion_token) | all None; ValueError if all three are None | same | Run ends: "Token usage limit reached" |
| AutoGen | TimeoutTermination(timeout_seconds) | required | same | Run ends: "Timeout of N seconds reached" |
| CrewAI | max_iter | 25 in source and the customizing docs; the agents concept page says 20 | Agent(...) | Docs: the agent "will try its best to give a good answer" |
| CrewAI | max_rpm | None | Agent(...) | Throttles request rate, does not stop |
| CrewAI | max_execution_time (seconds) | None | Agent(...) | Task times out |
| CrewAI | max_retry_limit | 2 | Agent(...) | Retries on error stop after N |
| Claude Code CLI | --max-turns | no limit; print mode (-p) only | CLI flag | Result subtype error_max_turns |
| Claude Code CLI / Agent SDK | --max-budget-usd; SDK max_budget_usd / maxBudgetUsd | no limit; print mode only | CLI flag; SDK options | Result subtype error_max_budget_usd |
| Claude Agent SDK | max_turns / maxTurns | no limit | ClaudeAgentOptions / Options | Result subtype error_max_turns |
| Claude Code hooks | PreToolUse deny; exit code 2; JSON continue: false with stopReason | n/a | hook config | Blocks the action, or stops processing |
| Codex CLI | No turn, token or budget key found. Source: DEFAULT_EXEC_COMMAND_TIMEOUT_MS = 10_000; config types include MCP tool_timeout_sec, startup_timeout_sec, agent max_threads, max_depth, job_max_runtime_seconds | as listed | config.toml | Not read in detail |
| Gemini CLI | model.maxSessionTurns | -1 (unlimited) | settings.json | Interactive: message and stop; non-interactive: exits with an error |
Counting differs by tool: OpenAI counts model-call turns, LangGraph counts graph supersteps, AutoGen counts messages, Claude counts a model response plus its tool calls. The same number is not the same cap.
LangGraph issue 9416: with_config ignored an explicit recursion_limit equal to the default, so set a value that differs from it.
What happens at the cap [vendor]
- OpenAI Agents SDK:
MaxTurnsExceeded. With anerror_handlers["max_turns"]handler the run returns a controlled output, saved to the session by default (include_in_historyis True). Open issue 5642 reports broken later turns after a max_turns stop with conversation_id (a user report). - LangGraph:
GraphRecursionError, "the graph has exhausted the maximum number of steps". The docs do not say partial state is returned. - AutoGen: a termination condition is checked after each agent response, never mid-turn, so a running step is not interrupted.
TimeoutTermination's timer starts when it is constructed and restarts onreset(). - CrewAI: at
max_iterthe agent is pushed to give a best answer; no hard stop or error type is documented. - Claude Code and the Agent SDK: a
ResultMessagewith subtypeerror_max_turnsorerror_max_budget_usd; cost, usage,num_turnsandsession_idare present, so the session can be resumed. The budget is a client-side estimate and can overshoot by one response plus subagents still running. Subagent spend counts toward the cap; after the cap, spawning new subagents fails (v2.1.217 or later). - Claude Code hooks: exit 2 blocks the action with stderr as the reason; other nonzero exits do not block.
continue: falsestops processing. - Gemini CLI: the turn count is checked at the start of each turn; loop detection can emit the same
MaxSessionTurnsevent.
Provider-side spend caps [vendor]
- Anthropic API: monthly tier caps Start $500, Build $1,000, Scale $200,000, Custom none. At the tier cap: HTTP 429
rate_limit_errorwitherror_code: enforced_spend_limit_reachedand noretry-after, so retries fail until 00:00 UTC on the 1st. A self-set limit on the Console Billing page returns HTTP 400 "You have reached your specified API usage limits." Workspaces can have their own spend and rate limits, except the default workspace. - OpenAI API: monthly tier caps Free $100, Build $500, Launch $5,000, Grow $200,000. Spend alerts only notify. A hard spend limit on an organization or project makes requests return 429. Project budget details on the help centre were not readable (403).
- Google Gemini API: Tier 1 billing-account cap $250 per month, enforced at billing-account level; service pauses for every linked project until the 1st. Per-project spend caps are "experimental and limited in scope"; batch and agent sessions may overshoot, and billing data lags about 10 minutes. Prepaid at $0 returns HTTP 402.
Patterns [opinion, untested]
- Layer the caps: a turn cap in the framework, a token or dollar cap you enforce per run, a time cap, and a provider cap per workspace or project as the backstop.
- Turn and step caps do not bound dollars: one turn can carry a huge context. Of the tools read, only Claude Code and its Agent SDK ship a dollar cap, and it is an estimate that overshoots.
- Give each agent or team its own provider workspace or project, so a stampede hits one cap, not the whole organization. Gemini's account-wide pause shows the risk.
- Treat a cap exit as a normal outcome: catch
MaxTurnsExceeded,GraphRecursionError,error_max_turns,error_max_budget_usd, record the session id, alert a human, do not retry. Anthropic's spend-cap 429 has noretry-after. - Delegation (subagents, agents as tools) is the weakest spot: the inherited-depth proposal (5453) is open; Claude Code is the only tool read that documents subagent spend counting toward a cap.
- Test the stop path with a deliberate overrun in staging: issues 5642 and 9493 show cap exits can leave bad state or never give up.
Gaps
- No live test of any cap.
- CrewAI docs conflict on 20 vs 25 for
max_iter; the forced-answer mechanism is not documented. - LangGraph default 10007 is from the main branch only.
- Codex CLI: no turn or budget cap found; config reference not fully read.
- OpenAI project budget page (403) and the error code for a hard limit: not found.
- Google Cloud and Vertex AI quotas: not read.
Sources
openai.github.io/openai-agents-python/running_agents; openai-agents-python src/agents/run.py and exceptions.py; docs.langchain.com/oss/python/langgraph/GRAPH_RECURSION_LIMIT; langgraph _internal/_config.py and errors.py; microsoft.github.io/autogen/stable agentchat termination tutorial; autogen conditions/_terminations.py; docs.crewai.com/en/concepts/agents and /en/learn/customizing-agents; crewAI base_agent.py; code.claude.com/docs/en/cli-reference, agent-sdk agent-loop, hooks; codex docs/config.md, codex-rs/core/src/exec.rs; gemini-cli docs/cli/session-management.md, docs/reference/configuration.md, packages/core/src/core/client.ts; platform.claude.com/docs/en/api/rate-limits; developers.openai.com/api/docs/guides/rate-limits; aistudio.google.com/docs/billing. All read 2026-11-09.