CCAF logo

Domain 1 · Task 1.1

Agentic Loops

Design and implement agentic loops for autonomous task execution.

An agentic loop is the control flow that lets Claude act autonomously: you send a request, the model either asks to use a tool or declares it is finished, you execute any requested tools, feed the results back, and repeat. The entire pattern hinges on one field on the API response — stop_reason — and getting its handling right is the single most-tested idea in this domain.

Key concept

The loop's only source of truth is stop_reason. Continue while it is tool_use; terminate when it is end_turn. Never infer completion from the assistant's text or from an iteration counter.

What you need to know

The agentic-loop lifecycle

Every turn of the loop is the same four steps:

  • Send the conversation (plus tool definitions) to the model.
  • Inspect stop_reason: tool_use means the model wants a tool run; end_turn means it considers the task done.
  • Execute the requested tool calls and append each result to the conversation history as a tool-result block.
  • Iterate — send the updated history back so the model can reason about the next action.

Tool results are appended to history so the model always reasons over the full record of what has happened so far. You keep looping until the response comes back with end_turn.

python
while True:
    resp = claude.create(messages=history, tools=tools)
    if resp.stop_reason == "tool_use":
        for call in resp.tool_calls:
            result = execute(call)
            history.append(tool_result_block(call.id, result))
        history.append(resp.assistant_message)  # keep the model's tool_use turn
        continue
    if resp.stop_reason == "end_turn":
        return resp.text                         # done

Model-driven, not pre-scripted

A correct loop is model-driven: Claude decides which tool to call next by reasoning over the accumulated context, and you simply honour stop_reason. This is different from a pre-configured decision tree or a fixed tool sequence, where the orchestration logic — not the model — chooses the next step. The exam repeatedly rewards the model-driven answer: the loop's job is to run tools and relay results, not to hard-code the order in which tools fire.

The three anti-patterns

Three wrong ways to decide when to stop show up again and again:

  • Parsing natural-language signals — e.g. if "done" in resp.text: break. The model mentions completion in its reasoning long before it is finished, so this fires early and truncates the task.
  • **An iteration cap as the *primary* stop** — e.g. for _ in range(5). A cap is a fine *safety net* against runaway loops, but it must not be the thing that decides success; real work gets cut off or the loop spins after work is done.
  • Treating any assistant text as a completion indicator — the presence of text says nothing about whether the model wants another tool call. Only stop_reason does.

Exam traps

The trapThe reality
Stop the loop when the assistant's message contains a phrase like "task complete".Text parsing produces false positives the moment the model *writes about* completing — the exact bug the scenario describes. Use stop_reason == "end_turn".
A finish_task() tool the loop watches for is a clean way to signal completion.That puts termination in the model's hands as an *action*. stop_reason already signals it naturally, so the extra tool is an anti-pattern.
Raising the iteration cap and adding better regex for completion phrases fixes premature stops.Both fixes double down on the wrong signal. Switch the termination criterion to stop_reason; keep a cap only as a runaway guard.
The Task tool has built-in completion detection you can rely on.There is no such built-in. Loop control is always your handling of stop_reason.

Practice scenario

Real questions from the bank that test this topic — the correct answer is highlighted.

You've built an autonomous data-cleaning agent using the Claude Agent SDK. It reads CSVs, identifies quality issues, calls cleaning tools, and verifies results. Your loop runs while an iteration counter is below 50 AND the assistant's last message doesn't contain the phrase

"task complete." In production you see two failure modes: (1) the agent terminates after writing "I should now run task complete diagnostics" mid-reasoning, and (2) it sometimes runs to the iteration cap when work was actually finished cleanly.

What is the correct termination criterion for the agentic loop?

AIncrease the iteration cap and add more sophisticated regex patterns to detect completion phrases
BTerminate when stop_reason == "end_turn" ; continue while stop_reason == "tool_use"Correct
CHave the agent explicitly call a finish_task() tool that the loop watches for
DWrap the agent in a parent subagent and use the Task tool's built-in completion detection

Why: The agentic loop's termination signal is the stop_reason field on the API response, not text in the assistant message. tool_use means execute the requested tool and loop back with the result; end_turn means the model considers itself done. Parsing assistant text (A) creates false positives whenever the model writes about completion in its reasoning — exactly the failure you're seeing. A finish_task() tool (C) is an anti-pattern because it puts termination in the model's hands as an action; the stop_reason already signals this naturally. There is no built-in completion detection (D). This is one of the most-tested agentic-loop anti-patterns.

A customer support agent built directly on the Messages API forgets prior turns intermittently. The orchestration team confirms the full messages array is sent every request. Investigation reveals: the issue happens specifically when the prior assistant message included a tool_use block, and the next user message arrives without the corresponding tool_result — the orchestration layer was injecting the user's new text but not the tool result.

What's the underlying cause of the broken behavior?

AThe messages array is malformed: every assistant tool_use block must be followed by a corresponding tool_result in the next user message; without it, the conversation is invalidCorrect
BClaude has a built-in turn limit that drops the oldest tool_result blocks first when context fills up
CThe orchestration is correct in principle, but the API silently drops tool_result blocks older than the most recent turn
DTool results expire after 30 minutes server-side and must be re-fetched from the tool to be valid

Why: A valid Messages API conversation requires that every assistant tool_use block be followed by a corresponding tool_result in the next user message. If the orchestration layer injects the user's new text without first inserting the tool_result , the conversation is malformed. The API does not silently drop content (C), there is no server-side expiration of tool results (D), and there is no automatic turn-limit dropping (B) — the API is stateless and processes faithfully whatever you send. The fix is to ensure the orchestration layer constructs valid tool_result blocks before appending the next user input.

Build exercise

Build a minimal tool-using agent loop

~45 min
  1. 1
    Define two simple tools (e.g. a calculator and a get_time) and a system prompt that tells Claude to use them to answer a multi-part question.

    Why: You need at least two tools so the model must make several tool_use turns before finishing.

  2. 2
    Write a while True loop that sends the history, and branch strictly on resp.stop_reason.

    You should see: On the first responses stop_reason is tool_use; after the model has what it needs it flips to end_turn.

  3. 3
    On tool_use, execute each requested call and append a tool-result block (plus the assistant's tool_use turn) to the history before looping.

    Why: Skipping the tool-result append leaves the model blind to what the tool returned.

  4. 4
    Deliberately add a broken variant that breaks when the text contains "done", and prompt the model with a task whose reasoning says "once this is done…".

    You should see: The broken variant terminates early — a live demonstration of the text-parsing anti-pattern.

  5. 5
    Add an iteration cap of, say, 25 as a pure safety net that raises/logs if hit, and confirm the healthy loop never reaches it.

    Why: This is the correct role of a cap: a guard, not the termination criterion.

Sources

Drill Agentic Architecture & Orchestration

Practice only this domain’s questions, untimed, with instant explanations.