Study guides / CCAR-F / Domain 1

Agentic Architecture & Orchestration · Lesson 1 of 7

1.1 - Agentic Loops

Design and implement agentic loops for autonomous task execution using the Messages API, stop_reason field, and tool result handling.

An agentic loop is the core execution cycle behind every Claude-based agent. It's deterministic control flow, defined in code. Not a prompt trick, not a retry loop, not a chatbot turn. Get this lifecycle right and most of Domain 1 falls into place; get it wrong and your agent stops halfway through a task in production.

The Agentic Loop Lifecycle

The loop follows four steps, repeated until completion:

  1. Send a request to Claude via the Messages API. This includes the conversation history (system prompt, prior messages, and any tool results from the previous iteration).

  2. Inspect the stop_reason field in the response. This field is the authoritative signal for what happens next. It has two values relevant to agentic loops:

    • "tool_use" - Claude wants to call one or more tools. The loop continues.
    • "end_turn" - Claude has finished its work. The loop terminates.
  3. If stop_reason is "tool_use": execute the requested tool(s), append the tool results to the conversation history as a new message, and send the updated conversation back to Claude.

  4. If stop_reason is "end_turn": the agent has finished. Present the final response to the user.

Step 3 is where loops break. Tool results must be appended to conversation history. Miss that, and Claude can't reason about the new information on the next iteration - the model never sees what the tool returned, so it has nothing new to act on.

Key Concept

The stop_reason field is the only reliable signal for loop control. It is deterministic and unambiguous. Never use natural language parsing, text content checks, or arbitrary iteration caps as your primary stopping mechanism.

Current state: beyond the two exam values

The exam guide (v1.0) keys tool_use and end_turn, the two values a basic loop branches on. The live Messages API returns others that a production loop must handle: pause_turn (continue a long-running server-tool turn), max_tokens, stop_sequence, refusal (current models such as Fable 5 can decline on an otherwise-normal 200 response), and model_context_window_exceeded (the response filled the model's context window; handle it like max_tokens truncation). Treat any value other than end_turn as "not finished, check why" rather than assuming tool_use. (Verified against the Messages API docs, July 2026.)

Model-Driven Decision-Making

In an agentic loop, Claude decides which tool to call from the current context. That's model-driven decision-making - the model reads the task, weighs the available tools, and picks one. Compare that to pre-configured decision trees or fixed tool sequences, where the developer hard-codes which tool runs when.

The exam favours model-driven approaches because they flex. Claude adapts to situations the developer never mapped out, handles edge cases, and chains tools in orders nobody planned. There's one exception worth memorising: when business logic demands deterministic compliance - financial operations, security checks, regulatory requirements - programmatic enforcement overrides that flexibility. Task Statement 1.4 covers this in detail.

The Three Anti-Patterns

Three anti-patterns show up again and again for loop termination. Learn to spot all three.

Anti-Pattern 1: Parsing natural language signals. Checking if Claude said "I'm done" or "task complete" to determine whether the loop should end. This is wrong because natural language is inherently ambiguous. Claude might say "I've finished analysing the first file" while intending to continue with more files. The stop_reason field exists precisely to eliminate this ambiguity.

Anti-Pattern 2: Arbitrary iteration caps as the primary stopping mechanism. Setting "stop after 10 loops" as the main way to terminate the agent. This is wrong because it either cuts off useful work (if the task genuinely needs 12 iterations) or runs unnecessary iterations (if the task finishes in 3). The model signals completion via stop_reason - use that signal. Iteration caps are acceptable as a safety net (a maximum bound to prevent runaway agents), but never as the primary control mechanism.

Anti-Pattern 3: Checking for assistant text content as a completion indicator. Using response.content[0].type == "text" to decide the loop is finished. This is wrong because Claude can return text alongside tool_use blocks. A response might contain explanatory text ("I'll now search for the customer's order history") immediately followed by a tool call. Checking for text presence does not tell you whether the agent is finished.

Common Exam Distractor

The exam frequently presents iteration caps as a plausible fix for premature termination. Reject these answers. Caps address runaway loops, not premature exits. The fix for premature termination is always to check stop_reason correctly.

Practical Example: The Premature Termination Bug

A developer builds a customer support agent. It works for simple queries but sometimes stops mid-task on complex requests. The code checks if response.content[0].type == "text" to determine completion.

The bug: Claude returns a text explanation ("Let me look up your order") alongside a tool_use block requesting the lookup_order tool. The code sees text in position [0], concludes the agent is finished, and returns the incomplete response to the user.

The fix: replace the content-type check with a stop_reason check. Continue the loop when stop_reason == "tool_use", terminate when stop_reason == "end_turn". This works regardless of what content types appear in the response.

Exam traps

Practice question

A developer's agent sometimes terminates prematurely when Claude returns text alongside a tool call. Their loop checks response.content[0].type == 'text' to determine if the agent is finished. Users report incomplete responses on complex queries. What should the developer change?

  • A Add an iteration cap of 15 loops to ensure the agent runs long enough for complex queries

    Arbitrary caps do not address the root cause. The agent exits because it misidentifies the response type, not because it loops insufficient times. A cap of 15 would still terminate prematurely if the text-check bug triggers on iteration 2.

  • B Set tool_choice to any so Claude always calls a tool instead of returning text

    This forces tool use even when the agent is genuinely finished, creating an infinite loop. The issue is not that Claude returns text - the issue is that the code misinterprets text presence as a completion signal.

  • C Parse the assistant text for completion phrases like I have finished before terminating the loop

    Natural language parsing is ambiguous and unreliable. Claude might say it has finished one step while intending to continue with the next. The stop_reason field already provides an unambiguous signal.

  • D Check the stop_reason field instead of content type - continue when stop_reason is tool_use, terminate when end_turn Correct

    The stop_reason field is the deterministic, authoritative signal for loop control. It correctly distinguishes between responses where Claude wants to call more tools (tool_use) and responses where Claude has finished (end_turn), regardless of whether text content appears alongside tool calls.

Build exercise: Build a Multi-Tool Agent Loop

Intermediate · 45 minutes

You'll practice:

  1. Set up a Claude API client with two tools: a calculator tool (accepts expression, returns result) and a web search stub (accepts query, returns mock results)

    Multi-tool setups expose model-driven decision-making - Claude must select the right tool based on context, which is core to agentic architecture.

    You should see: Two tool definitions registered with proper JSON Schema input_schema, each with name, description, and parameters.

    Hints
    1. Think about what the Anthropic SDK requires for a tool definition - name, description, and what kind of schema format?
    2. Each tool needs a name (string), description (string), and input_schema (JSON Schema object with type, properties, and required). The calculator takes an expression string; the web search takes a query string.
    3. const tools = [
        {
          name: "calculator",
          description: "Evaluates a mathematical expression",
          input_schema: {
            type: "object",
            properties: { expression: { type: "string" } },
            required: ["expression"]
          }
        }
      ];
  2. Implement the agentic loop that sends requests to Claude and inspects stop_reason after each response

    The agentic loop is the core execution pattern - the exam tests whether you use stop_reason (deterministic) versus content-type checks or natural language parsing (unreliable).

    You should see: A while loop that calls client.messages.create() and checks response.stop_reason after each iteration.

    Hints
    1. What field in the API response tells you definitively whether Claude wants to keep going or is done?
    2. Use a while(true) loop. After each messages.create() call, check response.stop_reason. If it is tool_use, continue. If it is end_turn, break. Never check content[0].type.
    3. let messages = [{ role: "user", content: userPrompt }];
      while (true) {
        const response = await client.messages.create({
          model: "claude-sonnet-5",
          max_tokens: 1024,
          tools,
          messages
        });
        if (response.stop_reason === "end_turn") break;
        // Handle tool_use next
      }
  3. Handle the tool_use stop_reason by executing the requested tool, creating a tool result message, and appending it to conversation history

    This is the critical handoff in the loop - the exam specifically tests whether you correctly extract tool calls, execute them, and return results in the right message format.

    You should see: When Claude requests a tool, your code extracts the tool_use block, runs the corresponding function, and appends both the assistant response and a user message with tool_result to the conversation.

    Hints
    1. Claude response contains content blocks. Which block type tells you what tool to call and with what input?
    2. Find content blocks where type === tool_use. Each has an id, name, and input. Execute the matching function, then create a user message with a tool_result content block containing the tool_use_id and the result string.
    3. const toolUse = response.content.find(b => b.type === "tool_use");
      const result = executeTool(toolUse.name, toolUse.input);
      messages.push({ role: "assistant", content: response.content });
      messages.push({
        role: "user",
        content: [{ type: "tool_result", tool_use_id: toolUse.id, content: result }]
      });
  4. Handle the end_turn stop_reason by extracting and returning the final response

    end_turn is Claude signal that it has completed the task - extracting the final text response correctly closes the loop and returns the result to the user.

    You should see: When stop_reason is end_turn, your loop exits and returns the text content from the final response.

    Hints
    1. Where in the response object is the final text that Claude wants to show the user?
    2. Filter response.content for blocks where type === text. The text property of those blocks contains Claude final answer.
    3. if (response.stop_reason === "end_turn") {
        const textBlock = response.content.find(b => b.type === "text");
        return textBlock?.text ?? "";
      }
  5. Test with a prompt that requires multiple sequential tool calls (e.g., search for a value then calculate something with it) and verify the loop continues correctly through all iterations

    Sequential tool calls test the full loop lifecycle - the agent must complete one tool call, receive the result, reason about it, and decide to call another tool before finally returning.

    You should see: At least two tool call iterations before end_turn. The agent searches first, uses the search result in a calculation, then returns the combined answer.

    Hints
    1. Design a prompt where the answer to the first tool call is needed as input for the second. What kind of query would force this chain?
    2. Try a prompt like: Search for the population of France and calculate what 15% of that number is. This forces a search call followed by a calculator call using the search result.
    3. const result = await runAgentLoop(
        "Search for the current price of Bitcoin and calculate what 3.5 coins would cost"
      );
      console.log("Iterations:", iterationCount);
      console.log("Result:", result);
  6. Add a safety iteration cap of 20 as a maximum bound (not the primary stopping mechanism) and log a warning if it triggers

    The exam distinguishes safety caps (acceptable as a fallback) from using caps as the primary stopping mechanism (an anti-pattern). Your cap should never trigger in normal operation.

    You should see: A MAX_ITERATIONS constant, a counter that increments each loop, and a warning log if the cap is hit. Normal queries should terminate via stop_reason well before reaching 20.

    Hints
    1. Where in your loop should you check the counter? What should happen if it triggers - error or warning?
    2. Add a counter variable before the loop. Increment at the start of each iteration. If counter >= MAX_ITERATIONS, log a warning and break. This is a safety net, not the primary control.
    3. const MAX_ITERATIONS = 20;
      let iterations = 0;
      while (true) {
        if (iterations >= MAX_ITERATIONS) {
          console.warn("Safety cap reached");
          break;
        }
        iterations++;
        // ... rest of loop
      }

Sources