A Taxonomy of Stopping Conditions
Stopping the Loop Is Part of the Design
Section titled “Stopping the Loop Is Part of the Design”When you are focused on making a loop run, it is easy to neglect how to make it stop. But an agentic loop without stopping conditions either runs indefinitely, incurs unbounded costs, or devolves into a doom loop — repeating the same mistake forever. One of the reasons AutoGPT and BabyAGI went viral in early 2023 and then faded quickly was that loops with absent or poorly specified stopping conditions spun out of control.
Stopping conditions fall into four types. Understanding this taxonomy from the start ensures no gap is left in your design.
┌──────────────────────────────────────────────────────────────┐│ Stopping Condition Taxonomy │├─────────────────┬────────────────────────────────────────────┤│ Type │ Description and Examples │├─────────────────┼────────────────────────────────────────────┤│ ① Semantic │ Model declares task complete ││ Completion │ (stop_reason == "end_turn"). The correct ││ │ success exit. │├─────────────────┼────────────────────────────────────────────┤│ ② Iteration │ Maximum iteration count reached. ││ Limit │ Hard safety net. E.g. max_iterations = 30 │├─────────────────┼────────────────────────────────────────────┤│ ③ Resource │ Token count, cost (USD), or wall-clock ││ Cap │ time exceeded. E.g. max_tokens = 200_000 │├─────────────────┼────────────────────────────────────────────┤│ ④ No-Progress │ N consecutive outputs are identical or ││ Detection │ show no measurable progress toward goal. ││ │ Implemented via output hash comparison. │└─────────────────┴────────────────────────────────────────────┘Each missing condition creates a corresponding failure mode. Without ①, the loop can never finish normally. Without ②, the model’s misguided belief that there is “more to do” can make it loop forever. Without ③, costs explode. Without ④, the same mistake repeats indefinitely — the doom loop.
Type ①: Semantic Completion (end_turn)
Section titled “Type ①: Semantic Completion (end_turn)”When the model determines the task is done and returns stop_reason == "end_turn" (or finish_reason == "stop"), the loop should exit. This is the normal successful exit path.
# Pseudocode for conceptual illustration; actual API may differ.
while True: response = model.generate(messages, tools=tools)
# Semantic completion: model declares "done" if response.stop_reason == "end_turn": return {"status": "success", "result": response.content}
# Work remains: execute tools and continue for call in response.tool_calls: result = run_tool(call) messages.append(tool_result(result))Trusting only this path is dangerous. A model can declare completion even when the task has not actually been finished. Premature success is a failure mode that Anthropic’s long-running agent guide explicitly calls out. Semantic completion should be re-verified by an external verifier (see Chapters 6-2, 6-3, 6-4).
Type ②: Iteration Limit (max_iterations)
Section titled “Type ②: Iteration Limit (max_iterations)”This is the hard safety net — a guarantee that the loop stops regardless of any other condition.
# Pseudocode for conceptual illustration; actual API may differ.
MAX_ITERATIONS = 30
for iteration in range(MAX_ITERATIONS): response = model.generate(messages, tools=tools)
if response.stop_reason == "end_turn": return {"status": "success", "result": response.content}
for call in response.tool_calls: result = run_tool(call) messages.append(tool_result(result))
# Maximum iterations reachedreturn {"status": "max_iterations_exceeded", "partial": get_current_state()}The right value for max_iterations depends on task complexity. A simple QA task may need only five; a complex coding agent may need 50–100. Setting it too low interrupts legitimate work; setting it too high defeats the safety net’s purpose.
Type ③: Resource Caps
Section titled “Type ③: Resource Caps”| Resource Type | Cap Example | Behavior When Exceeded |
|---|---|---|
| Input token count | 200,000 tokens | Trigger compaction or force stop |
| Cumulative cost | $5.00 USD | Halt task + notify user |
| Wall-clock time | 30 minutes | Timeout exit |
| Output tokens per call | 10,000 tokens | Handle truncation |
Resource caps are especially critical for open-ended loops where the number of iterations cannot be predicted. A debugging agent that encounters an unfixable bug may call tools indefinitely. Without a time or cost cap, significant expense can accumulate before anyone notices.
Type ④: No-Progress Detection
Section titled “Type ④: No-Progress Detection”This is the subtlest stopping condition. It detects when the loop is running but making no meaningful progress.
The simplest implementation is output hash comparison. If the model outputs (or the codebase state) are identical across two consecutive iterations, the loop is spinning in place.
# Pseudocode for conceptual illustration; actual API may differ.
import hashlib
def detect_no_progress(response_history: list, window: int = 3) -> bool: """ Return True if the N most recent responses all have the same hash. """ if len(response_history) < window: return False
recent = response_history[-window:] hashes = [ hashlib.md5(str(r).encode()).hexdigest() for r in recent ] return len(set(hashes)) == 1 # All identical = stuck
# Usage inside the loopresponse_history = []
for iteration in range(MAX_ITERATIONS): response = model.generate(messages, tools=tools) response_history.append(response.content)
if detect_no_progress(response_history, window=3): return {"status": "no_progress", "last_state": get_current_state()}
if response.stop_reason == "end_turn": return {"status": "success", "result": response.content} # ...Beyond hash comparison, more sophisticated approaches are possible: tracking whether the list of completed sub-tasks grows, whether the number of modified files increases, or whether the test pass rate improves. Domain-specific progress metrics give finer-grained stuck detection.
A Loop That Includes All Four Conditions
Section titled “A Loop That Includes All Four Conditions”# Pseudocode for conceptual illustration; actual API may differ.
def run_safe_loop(task: str, tools: list, max_iters: int = 30, max_tokens: int = 200_000, max_cost_usd: float = 5.0, timeout_sec: int = 1800) -> dict:
messages = [{"role": "user", "content": task}] response_history = [] start_time = now() total_tokens = 0 total_cost = 0.0
for i in range(max_iters): # ③ Resource caps if total_tokens >= max_tokens: return {"status": "token_limit_exceeded"} if total_cost >= max_cost_usd: return {"status": "cost_limit_exceeded"} if elapsed(start_time) >= timeout_sec: return {"status": "timeout"}
response = model.generate(messages, tools=tools) total_tokens += response.usage.total_tokens total_cost += response.usage.cost_usd
response_history.append(response.content)
# ① Semantic completion if response.stop_reason == "end_turn": return {"status": "success", "result": response.content}
# ④ No-progress detection if detect_no_progress(response_history, window=3): return {"status": "no_progress"}
for call in response.tool_calls: result = run_tool(call) messages.append(tool_result(result))
# ② Iteration limit (loop exhausted) return {"status": "max_iterations_exceeded"}Simon Willison put it well: “Stopping is better than nothing.” When a loop declares failure, you can restart it or ask the user for help. A loop that never stops forecloses all options. Stopping conditions are both the safety valve and the resource shield of any agentic loop.
References
- Anthropic — Building Effective AI Agents — accessed 2026-06-30
- Simon Willison — Designing Agentic Loops — accessed 2026-06-30
- Anthropic — Effective harnesses for long-running agents — accessed 2026-06-30