What you’ll be able to do
- Name the three kinds of termination condition a production loop needs
- Explain why a structurally verifiable done-signal beats a text heuristic
- Set a max-iteration cap that fails safe instead of failing silent
- Decide when a bounded loop is the right call instead of an open-ended agent
What you need to know
An agent loop is a liability until you’ve bounded it
Mechanically, an agent loop is nothing exotic: call the model, execute whatever tool it asked for, feed the result back, call the model again, repeat until it stops asking for tools. That mechanism has no opinion about when to stop. Left alone, it will keep going as long as the model keeps producing tool_use blocks — which a confused model can do indefinitely.
At developer scale this is a bug. At architect scale it's a class of incident: a loop that doesn't terminate burns tokens, burns wall-clock time, and if any tool call has a side effect, burns real-world state on every extra pass. The job of an architect is to design the boundary before the loop ships, not to discover it in a bill.
Three termination conditions, not one
A production loop needs all three of these, because each one catches a different failure:
- A success condition — a specific, checkable signal that the task is actually done. Not "the response looks complete," but a structural marker: a particular tool was called (
submit_answer()), a particular field was populated, a schema validated. - A ceiling — a hard cap on iterations, tokens, or wall-clock time, independent of whether the model thinks it's done. This is the fallback for when the success condition never fires.
- A cost/side-effect guard — a limit on how many times a side-effecting tool (one that sends an email, writes a row, calls a paid API) can fire within one loop, separate from the general iteration cap.
Shipping only the first is the most common gap. The success condition handles the happy path; the ceiling is what protects you when the model gets stuck in a retry-and-fail cycle it can't see its own way out of.
The done-signal has to be structural, not textual
A text heuristic breaks the moment the model phrases things slightly differently, and it can be satisfied by a model that just says it's done without having actually produced a valid result. A structural signal — a specific tool call, a schema that validates — can't be talked past.
When a bounded loop beats an open-ended agent
Not every task should get an open-ended agent loop at all. If the shape of the work is fixed and enumerable — the same five steps every time, in the same order — a bounded loop (or a plain workflow) is more reliable and cheaper to reason about than letting the model decide the path on every run.
Reach for an open-ended loop when the number of steps genuinely can't be known in advance — the model has to explore, and how far it goes depends on what it finds. Reach for a bounded structure when you already know the steps and just need the model to fill in judgment at each one. Architecturally, this is a cost and reliability decision as much as a capability one: an unnecessary open-ended loop pays for flexibility you never use.
Key concept
A loop with no hard stop isn’t flexible — it’s unbounded liability. Design the termination condition before the loop, and make the done-signal something a schema can check, not something a human has to read.
When a scenario describes a runaway cost or an agent that "just kept going," the fix is almost never a smarter prompt — it's a missing ceiling.
Practice scenario
Work it through, then open this
The loop is missing a ceiling, not a smarter stop-prompt. “Enough sources” is a judgment call the model can defer indefinitely if nothing forces a decision. Add a hard iteration cap that forces a “submit with what you have” tool call once reached, and make the actual success condition a structural one — a specific submit_findings tool call with a minimum source count — rather than the model’s own sense of sufficiency.
Build exercise — Add a real termination boundary to a loop
Intermediate · 25 min
What you’ll learn
- Separating a success condition from a ceiling
- Making a done-signal structurally checkable
- Designing a safe-fail path for when the ceiling is hit
-
Take an existing or hypothetical agent loop and write down its current stop condition in one sentence.
- Why: Most loops have an implicit stop condition nobody wrote down, which is why it’s easy to miss that it’s just “the model decided.”
- You should see: Either a structural condition already in place, or a gap where the loop relies on the model’s own judgment with no fallback.
-
Add a hard iteration ceiling and a defined behavior for what happens when it’s hit — not just “stop,” but “stop and do X” (flag for review, return partial results, alert).
- Why: A ceiling that just halts execution silently is only half a fix; someone or something needs to know it fired.
- You should see: A named fallback path, not a bare stop.
-
Replace any text-based completion check with a structural one — a specific tool call or a schema validation.
- Why: Text heuristics are the most common source of loops that never terminate cleanly.
- You should see: A condition that a unit test could assert on, not one that requires reading model output.
Exam traps
Trusting the model to decide when it’s done with no enforced fallback
Works until the one run where it doesn’t, and there’s nothing else in the design to catch it.
Treating “the text looks finished” as a termination signal
Text heuristics can be satisfied by a model that says it’s done without having done it. Use a structural signal instead.
Setting max_iterations low enough to silently truncate real work
A ceiling that fires on legitimate tasks looks like a bug in the model when it’s actually a bug in the cap.
Building an open-ended agent for a task with a fixed, enumerable shape
If you already know the steps, a bounded structure is cheaper and more reliable than paying for exploration you don’t need.
Logging a runaway loop instead of stopping it
Observability tells you it happened after the cost is spent. The ceiling is what prevents the cost in the first place.