Curriculum › Agentic Architecture & Orchestration · 27% of the exam

Agentic loops and termination conditions

What you’ll be able to do

  • Implement loop control that continues on stop_reason “tool_use” and terminates on “end_turn”
  • Append tool results to the conversation so the next iteration can reason over them
  • Explain why model-driven tool selection is the design, not a risk to engineer around
  • Place an iteration cap correctly: a secondary guard against runaway cost, not the primary stop

What you need to know

The loop lifecycle in one paragraph

An agentic loop is a short, mechanical cycle: send the conversation to Claude, inspect the response's stop_reason, and branch on it. If stop_reason is "tool_use", the model is asking for one or more tools — execute them, append the results to the conversation as tool_result blocks, and call the model again. If stop_reason is "end_turn", the model has finished its turn and the loop is done.

That's the whole control flow. The API already tells you, in a structured field, whether the model wants to keep working. An architect's job is to honor that signal faithfully — not to second-guess it with heuristics layered on top.

Tool results have to go back into the conversation

The model can only reason about what's in its context. Each iteration, the tool output has to be appended to the message history — the assistant's tool_use turn, followed by a user turn carrying the matching tool_result — before the next request is sent. Skip that step, or send only a summary of it, and the model is choosing its next action blind: it may repeat the same call, or invent what the result probably was.

before — termination guessed from text, cap does the real stopping
for i in range(5): response = client.messages.create(messages=messages, tools=tools, ...) if response.content[0].type == "text" or "done" in response_text.lower(): break run_tools(response) # results never appended to messages
after — stop_reason drives the loop, cap is only a backstop
while True: response = client.messages.create(messages=messages, tools=tools, ...) messages.append({"role": "assistant", "content": response.content}) if response.stop_reason == "end_turn": break if response.stop_reason == "tool_use": results = run_tools(response.content) # one tool_result per tool_use messages.append({"role": "user", "content": results}) iterations += 1 if iterations >= SAFETY_LIMIT: # runaway/bug guard only flag_for_review(messages) break

The three anti-patterns the exam tests

  • Parsing natural-language "done" signals. Searching the response text for "task complete" or "I'm finished" is fragile — the phrasing varies, and the model can mention being done while still emitting a tool_use block. The structured stop_reason is the signal; prose is not.
  • Treating assistant text as completion. A response can contain both a text block ("Let me check the order history…") and a tool_use block. Seeing text first doesn't mean the model is finished; only "end_turn" means that.
  • Using an arbitrary iteration cap as the primary stop. A loop that ends because it hit iteration 5 ends whether or not the work is done — it silently truncates legitimate multi-step tasks. A cap is reasonable as a secondary safety guard against a bug or runaway cost, sized generously and paired with a visible fallback (flag, alert, partial result). It is not the mechanism that decides normal completion.

Model-driven decisions are the point

In an agentic loop, Claude reasons from the current context about which tool to call next — or whether to call none and finish. That's a deliberate contrast with a pre-configured decision tree or a fixed tool sequence, where the code decides the path in advance. Model-driven selection is what lets the agent adapt to what each tool result reveals.

That doesn't mean every task needs an agent. If the steps are genuinely fixed and enumerable, a plain workflow is simpler. And where a business rule requires a specific ordering (verify identity before a refund), enforce it programmatically around the loop (covered in 1.4). But inside an agentic loop, letting the model choose the next action isn't a risk to be designed out — it's the design.

Key concept

Continue while stop_reason is “tool_use”, appending every tool result before the next call; stop when it’s “end_turn”. Text content, “done” phrases, and iteration counts are not the completion signal — a cap is only a backstop.

When a scenario describes an agent that stops too early, loops on the same call, or ignores what a tool returned, look first at the loop control: is it branching on stop_reason, and are tool results actually being appended?

Practice scenario

ScenarioA research agent sometimes returns a half-finished answer. Inspecting the loop, you find it stops on the first response that contains a text block, and also stops unconditionally after 3 iterations "to keep costs predictable." Complex questions routinely need 5–8 tool calls.
Work it through, then open this

Both exits are anti-patterns. The model often emits a short text block alongside a tool_use request, so “contains text” fires while the model is still working. And a 3-iteration cap is acting as the primary stopping mechanism, cutting off tasks that legitimately need more steps. Rewrite the control flow to branch on stop_reason: execute tools and append their results while it is "tool_use", and finish when it is "end_turn". If cost control is a concern, keep a much higher safety limit that flags the run for review when hit — a guard against bugs, not the normal way the loop ends.

Build exercise — Rebuild a loop around stop_reason

Intermediate · 25 min

What you’ll learn

  • Branching loop control on stop_reason instead of on text
  • Appending tool_use and tool_result turns correctly between iterations
  • Positioning a safety limit as a backstop with a visible fallback
  1. Take an existing or hypothetical agent loop and write down, in one sentence, what currently makes it stop.

    • Why: Many loops end on a text check or a small fixed count that nobody consciously chose as the completion signal.
    • You should see: Either a stop_reason check already in place, or an exit based on text content, a phrase match, or an iteration count.
  2. Rewrite the control flow so it continues on "tool_use" and terminates on "end_turn", appending the assistant turn and the matching tool_result blocks on every iteration.

    • Why: The model can only choose a sensible next step if each tool’s output is in its context.
    • You should see: A message history that grows by an assistant turn plus a tool-result turn per iteration, and a loop that exits only when the model ends its turn.
  3. Add a generous safety limit as a secondary guard, with a named fallback (flag for review, alert, return partial results) when it fires.

    • Why: The limit protects against bugs and runaway cost; it shouldn’t decide normal completion, and when it fires someone needs to know.
    • You should see: Normal runs ending on "end_turn" well before the limit, and the limit firing only on a deliberately broken test case.

Exam traps

Parsing natural-language “done” signals to decide when the loop ends

Phrasing varies and can appear while the model is still requesting tools. The structured stop_reason field is the signal.

Treating the presence of assistant text as a completion indicator

A response can carry a text block and a tool_use block together. Only "end_turn" means the model has finished.

Setting an arbitrary iteration cap as the primary stopping mechanism

It truncates legitimate work whenever a task needs more steps than the cap allows. Keep a cap only as a secondary safety guard.

Forgetting to append tool results before the next request

Without the tool_result in context, the model can’t incorporate what it learned — it repeats calls or guesses.

Replacing model-driven tool selection with a hard-coded tool sequence

Letting Claude reason about the next tool from context is the core of an agentic loop. Enforce only the specific orderings a business rule requires, and do it programmatically.

Sources

Quick check

In a Claude agentic loop, which signal should the loop treat as meaning the model has finished its work?