CCAR-F · Study Guide

← Domain 1: Agentic Architecture & Orchestration

1.1 · Lesson 1 of 7

Agentic Loops

What you need to know

An agentic loop is the cycle that runs a Claude agent. You write it in code: call the Messages API, read the response, run tools when asked, and call again with the new history. It is control flow. A sentence in the prompt that says to keep going is not this loop. A retry wrapper is not this loop. One chat reply is not this loop. Most of Domain 1 depends on this cycle. If the cycle is wrong, the agent stops halfway through a real task.

The loop, one cycle at a time

Repeat these four steps until the model is finished.

  1. 1Send a Messages API request that includes the whole conversation: the system prompt, earlier turns, and any tool results from the previous pass. The API stores nothing between calls.
  2. 2Read stop_reason. That field decides the next step. On the exam, two values matter:
    • tool_use — Claude wants one or more tools. Continue.
    • end_turn — Claude is finished. Stop.
  3. 3When stop_reason is tool_use, run the requested tools, append those results to the conversation as a new message, and send the updated history back.
  4. 4When stop_reason is end_turn, the agent is done. Show the final response to the user.

Step 3 is where loops break. The tool output has to land in the conversation. If it does not, the next request has no new facts, and Claude cannot act on a result it never received.

Beyond the two exam values

The v1.0 exam guide branches on tool_use and end_turn. A live Messages API response can also return values a production loop has to handle:

  • pause_turn — a long server-tool turn paused. Continue that turn.
  • max_tokens — the response hit the output cap.
  • stop_sequence — a configured stop sequence fired.
  • refusal — the model declined, including on an otherwise normal 200.
  • model_context_window_exceeded — the response filled the context window. Handle it the same way you handle max_tokens truncation.

Any value other than end_turn means the turn is not finished. Check why. Do not assume the only other case is tool_use.

const MAX_ITERATIONS = 20;

for (let i = 0; i < MAX_ITERATIONS; i++) {
  const response = await client.messages.create({ model, max_tokens, tools, messages });

  if (response.stop_reason === "end_turn") {
    return textFrom(response);
  }

  if (response.stop_reason === "tool_use") {
    messages.push({ role: "assistant", content: response.content });
    const results = response.content
      .filter((block) => block.type === "tool_use")
      .map((block) => ({
        type: "tool_result",
        tool_use_id: block.id,
        content: String(runTool(block.name, block.input)),
      }));
    messages.push({ role: "user", content: results });
    continue;
  }

  throw new Error(`Unhandled stop_reason: ${response.stop_reason}`);
}

console.warn("Safety cap hit before end_turn");

Model-driven tool choice

Inside the loop, Claude picks the tool from the current context. It reads the task, weighs the tools you registered, and chooses one. A pre-built decision tree or a fixed tool sequence does the opposite: your code names the next tool.

The exam favors the model-driven version because Claude can handle a case you never mapped and can chain tools in an order you did not write down. The exception is a rule that must hold every time — a payment, a security check, a regulatory step. There, code enforces the step. Task statement 1.4 covers that handoff.

Three anti-patterns

These three show up whenever a question is about how the loop stops.

  1. Parsing a sentence. Ending because the reply contains "I'm done" or "task complete". The model can say it finished the first file and still intend to open the next one. stop_reason exists so you do not have to interpret the prose.
  2. A count as the real stop. "Quit after 10 loops" either cuts a task that needed 12 steps or keeps running after the work ended on step 3. The model already reports completion. A cap is acceptable as a ceiling so a runaway agent cannot loop forever. It is not how you detect that the task is done.
  3. Text content as a done flag. response.content[0].type == "text" does not mean the loop is finished. Claude often writes a line ("I'll look up the order history") and then a tool call in the same response. A text block does not tell you the agent stopped.

Worked example: the early-exit bug

A customer-support agent answers short questions and then drops complex ones in the middle. The loop decides it is finished with if (response.content[0].type == "text").

Claude returns the sentence "Let me look up your order" and, in the same response, a tool_use block for lookup_order. The code sees text at index 0, treats the agent as finished, and returns that sentence. The order is never looked up.

Replace the content-type check with a stop_reason check. Continue while it is tool_use. Return when it is end_turn. The mix of blocks in content no longer matters.

Exam traps

  • response.content[0].type == "text" means the loop is finished

    The same response can hold a sentence and a tool_use block. Text at index 0 does not mean the agent stopped. stop_reason does.

  • Stop after 10 loops, and use that count as the main exit

    A fixed count cuts work that needed more turns, or keeps going after the task already ended. A cap is only a ceiling. Completion is stop_reason.

  • End the loop when the text says "I'm done" or "task complete"

    Prose is ambiguous. The model can describe finishing one step while the turn is still a tool call. stop_reason is a fixed field.

  • Set tool_choice to any so the agent cannot return text

    any requires a tool call even after the work is finished, so the loop never reaches a natural end. Let the model finish, and read stop_reason.

Practice scenario

A support agent's loop sometimes quits early when Claude returns text in the same response as a tool call. The code treats the agent as finished when response.content[0].type == "text". Users get incomplete answers on harder questions. What should change?

Choose one answer

Build exercise

Build a multi-tool agent loop

What you will learn

  • How the loop runs against the Messages API
  • Why stop_reason is the control signal
  • How to handle tool_use and end_turn
  • How to append tool results so the next turn can use them
  • When an iteration cap is a safety ceiling, and when it is the wrong stop condition
  1. Step 1

    Register two tools on the client

    Add a calculator that takes an expression and returns a result, and a web-search stub that takes a query and returns canned results.

    Why: Two tools force the model to choose. That choice, made from the current context, is the model-driven decision the exam expects.

    You should see: Both tools are objects with name, description, and a JSON Schema input_schema. Calculator requires expression. Search requires query.

  2. Step 2

    Loop on the Messages API and read stop_reason

    Send the conversation, then inspect stop_reason on every response before you do anything else.

    Why: The exam checks that you use this field. Content-type checks and phrase scans are the unreliable alternatives.

    You should see: A loop calls client.messages.create() and reads response.stop_reason on each pass.

  3. Step 3

    On tool_use, run the tool and write the result back

    Execute each requested tool, then append the assistant turn and a user turn that carries the tool_result.

    Why: This handoff is the step the exam tests: extract the call, run it, and return the result in the message shape the API expects.

    You should see: You pull the tool_use block, call the matching function, append the assistant response, and append a user message whose content is a tool_result.

  4. Step 4

    On end_turn, return the final text

    Leave the loop and hand the user the text from that last response.

    Why: end_turn is the model's completion signal. Returning that text is how the loop closes.

    You should see: The function returns when stop_reason is end_turn, using the text blocks from that response.

  5. Step 5

    Prove the loop survives two tool calls in a row

    Use a prompt that must search for a value and then calculate with it.

    Why: One tool call can hide a broken history append. A second call only works if the first result was actually sent back.

    You should see: You get at least two tool_use turns, then end_turn. Search runs first, the calculator uses that number, and the final answer combines both.

  6. Step 6

    Add a safety cap of 20

    Bound the loop at 20 iterations and log a warning if that bound hits. Keep stop_reason as the real exit.

    Why: The exam allows a cap as a fallback against a runaway agent. Using the cap as the way you decide the task is done is the anti-pattern.

    You should see: A MAX_ITERATIONS constant, a counter, and a warning if the cap trips. Ordinary prompts still exit on end_turn well before 20.

Sources