AI Agent in an Infinite Loop: How to Prevent It
Why an AI agent gets stuck in an infinite loop and the defenses that teach the loop when to stop: iteration cap, stopping condition, no-progress detection, and feedback signals.
Contributors: Ivan Garcia Villar
An AI agent is, at its core, an agentic loop: the model looks at the context, decides on an action, calls a tool, reads the result, and starts over. That cycle is what turns a chat into something that can work through tasks on its own. The problem shows up when the loop doesn’t know when to stop. So it spins, and spins, and keeps on spinning. Every iteration is a model call, and every model call burns tokens.
An AI agent stuck in an infinite loop rarely crashes with a red error. It hangs while burning through your budget, all while appearing to be working. This post explains why it happens and how to close each gap, starting from one simple idea: a loop that doesn’t end is a poorly designed loop.
Why an Agent Gets Stuck in a Loop
An agent enters an infinite loop when nothing in its design tells it, unambiguously, that it’s done or that it should give up. The loop only closes if one of two things happens at some point: the model declares the task is finished, or your code cuts it off. If neither is well-defined, there’s no way out.
The mechanics are straightforward. On each iteration, the model returns either a final response or a request to use a tool. If it requests a tool, your code runs it, feeds the result back, and the loop continues. In the Anthropic API this is visible directly: the model responds with stop_reason: "tool_use" when it wants to call a function, and your program iterates until it responds with "end_turn". The whole system depends on the model eventually deciding it’s done. And that “I’m done” is exactly what breaks.
These are the causes I’ve run into over and over again.
No clear stopping condition. If your prompt never defines what “done” means, the model will always find one more thing to check. You asked it to fix a test and, while it was at it, it reviews the imports, and while it’s there, the formatting, and runs the test again just to be sure. Without a semantic boundary, initiative turns into a loop.
Feedback that doesn’t distinguish success from failure. This is the most insidious one. An agent that was supposed to rename files kept calling list_files in a loop, because the tool returned the file list whether the rename had worked or not. The model had no way to know it had already succeeded. So it kept trying again.
Blind retries on a deterministic failure. A tool that always fails the same way (an expired credential, a 404) will fail exactly the same on the second attempt. Retrying without changing anything is, literally, the definition of a loop.
No progress: the same state over and over. The agent alternates between the same two or three actions. Each individual step is “valid,” but the whole thing doesn’t move an inch. It reads a file, decides to edit it, reads the file again, decides to edit it again. It never actually writes.
All four causes boil down to the same thing: the loop has no way of knowing whether it’s going anywhere. The defenses below attack that gap, from the bluntest angle to the most precise.
Defense 1: Set a Hard Cap
The hard cap is the simplest defense and the one that should never be missing: a maximum number of iterations and a token budget that cut execution no matter what. It doesn’t make the agent smarter. It limits the damage when something goes wrong.
// El bucle del agente con dos topes duros: número de vueltas y tokens gastados
async function runAgent(task: string) {
const MAX_TURNS = 20; // nunca más de 20 vueltas
const TOKEN_BUDGET = 100_000; // corta si se pasa de presupuesto
let tokensUsed = 0;
for (let turn = 0; turn < MAX_TURNS; turn++) {
const step = await model.next(task); // el modelo decide la acción
tokensUsed += step.usage.input_tokens + step.usage.output_tokens;
if (step.done) return step.result; // condición de parada explícita
if (tokensUsed > TOKEN_BUDGET) break; // el tope gana si el agente no ha terminado
await runTool(step.toolCall); // ejecuta y realimenta el bucle
}
throw new Error("El agente no terminó dentro del presupuesto");
}
Think of the cap as the circuit breaker on your electrical panel. It doesn’t fix the short circuit, but it keeps the house from burning down. This bounded for loop is the first thing I write, always, before worrying about whether the agent is good.
That said, if your normal tasks frequently hit MAX_TURNS, the cap isn’t the solution — it’s the symptom. The agent isn’t converging, and raising the number just kicks the problem down the road.
Defense 2: Define What “Done” Means Before You Start
The stopping condition is the answer to a question many people don’t ask before launching an agent: how will I know, without any ambiguity, that the task is complete? If you leave that decision entirely to the model’s judgment, you’re depending on it to “feel” like it’s done. Sometimes it feels done quickly; sometimes never.
Give it an explicit, verifiable definition. You have two ways to do this. One is a tool the model must call to declare completion — a submit_result that also forces it to deliver the result in the format you expect. The other is a check your code runs: the tests pass, the file exists, the JSON validates against a schema. When the condition is something your program can verify, “done” stops being the model’s opinion and becomes an actual fact.
Defining the exit condition isn’t something you bolt on at the end. It’s part of designing the loop from the start, and it’s the essence of loop engineering: building with AI is less about writing code and more about designing the context → action → verification → stop cycle that the agent runs through.
Defense 3: Detect No-Progress
If the agent’s state doesn’t change between one iteration and the next, cut it off: it’s spinning, not advancing. The iteration cap would eventually catch it too, but twenty iterations later and twenty expensive model calls deeper. No-progress detection catches it on the second repetition.
The idea is to store a fingerprint of each step (the tool and its arguments, or a hash of the state) and compare it against the previous one. If it repeats, don’t retry the same thing. Escalate or cut.
// Detecta no-progreso: misma acción con los mismos argumentos dos vueltas seguidas
let lastFingerprint = "";
for (let turn = 0; turn < MAX_TURNS; turn++) {
const step = await model.next(task);
if (step.done) return step.result; // si terminó, sal antes de comparar
// huella orden-dependiente: estable en la práctica, no garantizada por la spec
const fingerprint = JSON.stringify(step.toolCall ?? null); // huella de la acción
if (fingerprint === lastFingerprint) {
// el estado no cambia: cambia de estrategia o entrega el control a un humano
return escalate("El agente está girando sobre la misma acción");
}
lastFingerprint = fingerprint;
await runTool(step.toolCall);
}
The fingerprint doesn’t have to be exact: you can tolerate one repetition before cutting, or compare by similarity instead of strict equality, because sometimes a legitimate retry changes one small parameter. What matters is that the loop has memory of its recent past. An agent that doesn’t remember what it did in the previous iteration is doomed to repeat it.
Defense 4: Give It a Signal That Distinguishes Progress from Spinning
The first three defenses cut the loop. This one makes it so you often don’t need to cut it, because the agent knows on its own whether things are going well. The result each tool returns to the model must tell it, without ambiguity, whether the step worked or failed.
Go back to the agent that was renaming files. The fix wasn’t giving it a lower cap or more retries. It was changing what the tool returned: instead of the same file list every time, a { ok: true, renamed: "..." } or a { ok: false, error: "..." }. With that signal, the model no longer needs to guess. It knows whether it moved forward.
A loop that measures converges. One that doesn’t, spins. That signal that distinguishes progress from spinning is, at its core, a feedback loop; the guide on how to evaluate AI agents in production goes into detail on how to build that success-or-failure signal, and experiment-driven optimization covers how to leverage those measurements so the agent keeps improving between runs.
Guardrails: The Safety Net, Not the Plan
Guardrails are the last line of defense: hard rules that stop the agent when everything else fails. An absolute cost cap, a timeout, an allowlist of permitted actions, mandatory human review before dangerous operations. They don’t depend on the model reasoning correctly, because they apply regardless.
They’re essential, but they’re the safety net under the trapeze artist, not the act itself. If your only protection against an infinite loop is a cost guardrail, the agent will keep spinning until it hits that limit every time, burning as much as you allow before stopping. How to set them up, step by step, is in the guide on guardrails for AI agents. Don’t confuse the net with the design.
Common Mistakes
Raising the Retry Limit Instead of Fixing the Signal
This is the instinctive reaction and almost always the wrong one. The agent fails, so you give it more attempts. But if the feedback doesn’t distinguish success from failure, more retries just mean more identical iterations and a higher bill. Before raising that number, ask yourself why the agent doesn’t know it failed.
The try/except That Retries the Same Thing
You wrap a tool call in a try/except and, in the except, call it again exactly the same way. If the failure is deterministic, the second attempt fails identically to the first, and the third, and the fourth. A retry only makes sense if something changes between attempts: a backoff, a different parameter, an alternative tool.
Not Logging the State of Each Iteration
Without a per-iteration log, you’re debugging blind. When an agent hangs, the only way to understand why is to see the sequence of actions that led there. Log the chosen action, its arguments, and the result fingerprint on every iteration. That log is the difference between “the agent got stuck” and “the agent repeated search_docs fourteen times because it always returned zero results.”
Letting Only the Model Decide When to Stop
Trusting the model’s judgment alone to end the task, with no external check, works until it doesn’t. The model can declare itself satisfied with a half-finished job, or never feel satisfied at all. A stopping condition your code can verify turns a gut feeling into a guarantee.
Implementation Checklist
- The loop has a hard iteration cap and a token budget that cut execution no matter what
- There is a definition of “task completed” that your code can verify, not just the model’s judgment
- Each iteration compares its state against the previous one and cuts or escalates if there’s no progress
- The result of each tool distinguishes success from failure unambiguously
- A deterministic failure (expired credential, 404) is not retried blindly without changing anything
- Each iteration is logged with the action, its arguments, and the result for audit purposes
- Cost and time guardrails are active as a final safety net, not as the only defense
Frequently Asked Questions
Why does my AI agent repeat the same action over and over?
Almost always because the result of that action doesn’t tell it whether it worked. If the tool returns the same thing whether it succeeded or failed, the model has no way to know it already accomplished the task, so it tries again. Fix the feedback signal before touching anything else: make every tool return a clear success or failure state.
What iteration limit should I set to avoid an infinite loop?
It depends on how many steps your task needs in its longest realistic version. Set it a bit above that, not as a barrier you hit often. If your normal tasks regularly hit the limit, the number isn’t the problem: the agent isn’t converging, and what you need to revisit is the stopping condition and the feedback signal, not raise the cap.
Isn’t a timeout enough?
No. A timeout prevents an agent from hanging for an entire night, but it cuts by time, not by meaning: it doesn’t distinguish an agent that’s working slowly from one that’s spinning in place. Use it as a final safety net, alongside the iteration cap and no-progress detection, never instead of them.
Can an infinite loop happen even when using a framework like LangChain?
Yes. Frameworks usually come with a default iteration cap, but that cap only limits the damage; it doesn’t make the agent know when it’s done. If you define the stopping condition poorly or the feedback is ambiguous, the agent will spin until it hits the framework’s limit just as it would in code you wrote yourself. The underlying defense is the same with or without a framework.