An AI agent can look busy while it repeats the same tool, returns to the same subagent, or receives the same error on every turn. The problem is not only the API bill. The loop also grows context, delays the response, and makes it hard to know whether the task finished or merely ran out of budget.
The fix is to make termination part of the runtime. Combine a completion condition, a step limit, a time or token budget, and a progress signal. When one of those barriers ends the run, record a terminal reason that another execution can understand. The model may suggest that it is done, but code must check the condition.
This article shows the policy in TypeScript without calling a real provider. The snippet is illustrative and uses abstract adapters. A production implementation must connect the limits to your SDK, tools, and storage.

Short answer
- Stop on valid completion, no progress, an exhausted budget, or an error that cannot be recovered.
- A step limit prevents an infinite loop, but it does not identify repetition or prove that the answer is correct.
- Count run steps and calls per tool. Share the same deadline and budget across retries.
- Record
completed,budget_exceeded,no_progress, orfailedas different states.
What counts as an infinite loop in an agent?
In 2026, the paper "When Agents Do Not Stop" analyzed 6,549 agent repositories and manually confirmed 68 infinite-loop failures across 47 projects, with 91.9% precision for the reported findings (arXiv, "When Agents Do Not Stop: Uncovering Infinite Agentic Loops in LLM Agents", 2026). The result does not say that every long run is defective. It shows why the return path also needs a verifiable bound.
A long execution may be making progress. An agent that checks new pages, waits for an operation, or repairs a test may need several turns. The loop becomes suspicious when state does not change, the result adds no evidence, or the same transition happens again without a new decision.
Watch for these signals together:
- The tool gets the same name and arguments, or different arguments that lead to the same state.
- The history grows, but the task has no verifiable completion condition.
- The runtime repeats an operation after an error without changing its context, policy, or resource.
A repetition check should not block every polling operation. Checking a job
status can be correct while the external state remains running. The question
is whether each check can change the next decision. If it cannot, the run needs
to stop or ask for intervention.
Why is a step limit not enough?
In 2026, the OpenAI Agents SDK documents maxTurns with a default of 10 and raises MaxTurnsExceededError when the limit is reached (OpenAI Agents SDK, "Running Agents", retrieved 2026-08-28). In the same period, the AI SDK documents stopWhen and stepCountIs, with 20 steps as the ToolLoopAgent default (Vercel AI SDK, "Agents: Loop Control", retrieved 2026-08-28). Those values are library defaults, not universal recommendations.
A hard limit is the last fence. It protects against a completion condition that never becomes true, but it can cut off a task that was still making progress. It also does not stop an expensive tool from running repeatedly inside a turn, or distinguish real progress from another response with the same content.
Separate at least these budgets:
- Run steps: how many agent decisions may happen.
- Calls per tool: how often one tool may run in the same task.
- Time remaining: how much of the shared deadline is left.
- Tokens or cost: how much may be spent before another call.
The budget must be consumed by the whole execution. If every retry creates a
new local counter, the agent can respect each local limit and still exceed the
global one. The limit result must also be explicit. failed differs from
budget_exceeded, which differs from completed.
Which conditions should end the loop?
In 2026, Google Cloud's agent architecture guide describes two exit shapes for a loop pattern: a maximum iteration count or custom state. The same guide warns that a badly defined completion condition can leave the loop running forever (Google Cloud, "Choose a design pattern for your agentic AI system", retrieved 2026-08-28). Use both layers together.
A small policy can evaluate conditions in this order:
- Completion: does the output contain the expected result and pass the application's validation?
- Safety: is the next call allowed for this phase and resource?
- Progress: did state, evidence, or the decision change since the last turn?
- Budget: are steps, time, tokens, and tool calls still available?
If completion is valid, finish successfully. If state does not advance, record
no_progress and keep the last evidence. If the budget ends, record
budget_exceeded without turning a partial result into a final answer. If the
next action violates policy, end with blocked.
The model may return a word such as DONE, but that is an input signal, not
proof. Validate the format, required fields, and the effect the task should
have produced. For an architecture with more explicit phases and transitions,
see when to use a state machine for an AI agent.
How can you detect no progress without breaking polling?
In 2026, Microsoft Agent Framework documents a continuation predicate that can return continue, stop, or feedback for the next iteration, while keeping max_iterations as an independent limit (Microsoft Learn, "Agent looping", retrieved 2026-08-28). The separation is useful: the predicate observes progress, while the limit protects the process when that observation fails.
Start with a small fingerprint of the state controlled by the runtime. Include the tool name, normalized arguments, resource identifier, state version, and a short result signature. Do not include secrets or an entire response just to create a hash.
type StopReason =
| "completed"
| "no_progress"
| "budget_exceeded"
| "blocked"
| "failed";
type RunState = {
step: number;
lastFingerprint?: string;
repeatedSteps: number;
reason?: StopReason;
};
function shouldStop(
state: RunState,
nextFingerprint: string,
maxSteps: number,
): StopReason | undefined {
if (state.step + 1 >= maxSteps) return "budget_exceeded";
if (state.lastFingerprint === nextFingerprint && state.repeatedSteps >= 1) {
return "no_progress";
}
return undefined;
}
This snippet is illustrative. It does not know the meaning of an external
resource and cannot prove that two semantically equal responses are useless. In
production, combine the fingerprint with a rule per tool. A pollJob may allow
repetition while the status changes. A sendMessage should stop after the first
accepted intent, even if confirmation is slow.
Do not use a language judge as the only condition. An evaluator may approve the same response twice or request refinement forever. Use it to assess quality inside a limit controlled by the runtime.
How should completion, retry, and stopping be separated?
In 2026, the Claude Code SDK describes its loop as a repetition between the model response and tool execution until a response arrives without new tool calls (Anthropic, "How the agent loop works", retrieved 2026-08-28). That rule ends the cycle, but it does not replace application policy for incomplete output, tool errors, or no progress.
Model the decision outside the prompt. An adapter can return one of these outcomes:
type Decision =
| { kind: "continue"; fingerprint: string }
| { kind: "complete"; output: string }
| { kind: "stop"; reason: StopReason; detail: string };
async function runBoundedAgent(input: string): Promise<Decision> {
const state: RunState = { step: 0, repeatedSteps: 0 };
while (true) {
const attempt = await modelAndTools(input, state);
const decision = classifyAttempt(attempt, state);
if (decision.kind !== "continue") return decision;
const reason = shouldStop(state, decision.fingerprint, 8);
if (reason) {
return { kind: "stop", reason, detail: "termination policy reached" };
}
state.repeatedSteps = state.lastFingerprint === decision.fingerprint
? state.repeatedSteps + 1
: 0;
state.lastFingerprint = decision.fingerprint;
state.step += 1;
}
}
The example is illustrative and was not run against a provider. The
classifyAttempt function must distinguish a valid final response, a tool call
that may continue, a retryable failure, and a failure that requires stopping.
The value 8 is a local policy example, not a recommended limit.
Retry is appropriate when another attempt can still produce information or a
result without repeating an external effect. Stopping is appropriate when the
state is ambiguous, the error is permanent, the tool capability is unavailable,
or the task has no budget left. The policy is safer when the agent receives the
reason instead of a generic try again message.
How should you record and recover a terminal state?
In 2026, the OpenAI Agents SDK documents carrying a RunState and handling a maxTurns failure with a specific handler, without automatically replaying tool effects (OpenAI Agents SDK, "Running Agents", retrieved 2026-08-28). The principle is to separate partial output from permission to resume.
Record at least:
runIdandparentRunIdwhen a handoff exists;- step count and calls per tool;
- remaining budget and deadline;
- the last known state and its version;
- terminal reason and the evidence that produced it;
- references to external effects that need reconciliation.
A run stopped for no_progress may resume after its context changes. A
blocked run may need approval. A budget_exceeded run may need a smaller
task. None should automatically return to the first step without carrying the
previous reason.
To keep context cost under control, treat summaries and persisted state as different things. A summary helps the next decision. A terminal state explains why the decision stopped. Durable execution for AI agents goes deeper into checkpoints and resumption; here, the focus is the termination policy.
When a loop crosses several sessions, I use RemoteCode as my tool for keeping agent context and operational decisions visible. That is a description of my use, not a measured result from this article.
How do you test that the agent really stops?
In 2026, the OpenAI Agents SDK documents deterministic test doubles for exercising multi-turn workflows without making model or sandbox-provider calls (OpenAI Agents SDK, "Testing", retrieved 2026-08-28). Even without that library, use fakes to prove the policy before connecting an agent to external services.
Test at least these paths:
| Scenario | Expected result |
|---|---|
| The model returns a valid final response | completed, with no new tool call |
| The same tool returns the same state twice | no_progress, or a tool-specific polling rule |
| The global counter reaches its limit | budget_exceeded, with the last state preserved |
| A tool requests permission outside the current phase | blocked, with no effect executed |
| The model returns a retryable error within the deadline | another attempt within budget |
| A tool starts an effect and its response disappears | ambiguous state, reconcile before retrying |
Also verify what must not happen. The agent must not turn
budget_exceeded into completed, reset the counter after a provider change,
drop history during a handoff, or confuse a partial result with the final answer.
Testing an AI agent's trajectory helps verify the full sequence. This article adds the stop question: what evidence made the runtime end, and can the next run understand that reason?
Frequently asked questions
What is a safe iteration limit for an AI agent?
There is no universal value. The OpenAI Agents SDK uses maxTurns: 10 by
default, while the AI SDK documents 20 steps for its ToolLoopAgent, but those
values belong to different libraries (OpenAI Agents SDK, "Running Agents"; Vercel AI SDK, "Agents: Loop Control", retrieved 2026-08-28). Measure the task and keep a global limit.
Does detecting the same tool call prevent every infinite loop?
No. A tool can receive different arguments and produce the same state, or two tools can alternate. Combine a fingerprint with progress, time, cost, and a completion condition. For legitimate polling, allow repetition while external state changes and set an independent deadline.
What should happen when the limit ends before the answer?
Record budget_exceeded with the last known state, reason, and available
evidence. Do not present partial output as success. If the task can continue,
create a new run with summarized context. If an external effect is ambiguous,
check its state before repeating it.
Conclusion
An agent loop becomes safer when stopping is a system decision, not a hidden request in the prompt. Use validated completion, a step limit, a shared budget, and no-progress detection. These barriers do different jobs. Together, they stop an unsolved task from looking like autonomy.
The agent may still fail. That is better than continuing without knowing why. Record the terminal reason, preserve state, and make the next step a retry, reconciliation, approval, or smaller run.
Production note
Samuel Fajreldines is the editorial owner of this article. The research used public documentation from the OpenAI Agents SDK, AI SDK, Microsoft Agent Framework, Claude Code, LangChain, Google Cloud, and a technical arXiv paper, along with public practitioner discussions. AI assistance supported discovery, comparison, first drafting, translation, and review. The code is illustrative; there was no real-provider benchmark, client test, or original metric.
Sources consulted
- OpenAI Agents SDK, "Running Agents", retrieved 2026-08-28.
- OpenAI Agents SDK, "Testing", retrieved 2026-08-28.
- Vercel AI SDK, "Agents: Loop Control", retrieved 2026-08-28.
- Microsoft Learn, "Agent looping", retrieved 2026-08-28.
- Anthropic, "How the agent loop works", retrieved 2026-08-28.
- Google Cloud, "Choose a design pattern for your agentic AI system", retrieved 2026-08-28.
- Xinyi Hou et al., "When Agents Do Not Stop: Uncovering Infinite Agentic Loops in LLM Agents", retrieved 2026-08-28.