CLAUDE API · AGENTIC LOOP • 12 min read •

Claude’s agentic loop: blocks, stop_reason and the next tool

A Claude response is one assistant message whose content field is a list of blocks. Text next to a tool_use block in the same response is an ordinary tool call. Your loop reads stop_reason, runs the tools and sends the results back. The model picks the next tool, process_refund or escalate_to_human. It decides from what it can see in context.

Aerial night photo of a circular highway interchange with light trails

Claude exam candidates arrive here with two questions. The first is about a response that holds both text and a tool call. That is a single assistant message with several content blocks of different types. The second is about the step after lookup_order when the purchase was 45 days ago. The model decides: it compares the date with the policy it can see in context. If the rule must hold every time, code checks it.

One message, several blocks

The Messages API returns a response with a content field. It is an array of blocks, and each block has its own type. The Messages API reference lists response blocks that include text, thinking, redacted_thinking, tool_use and server_tool_use. Result blocks from server tools come on top of those. When Claude wants to use one of your tools, it may write a short sentence first and then add a tool_use block. Both blocks sit in one message:

example: a response with two blocks
{
  "role": "assistant",
  "stop_reason": "tool_use",
  "content": [
    { "type": "text", "text": "Let me check that order." },
    {
      "type": "tool_use",
      "id": "toolu_01A",
      "name": "lookup_order",
      "input": { "order_id": "A-1042" }
    }
  ]
}

So the answer to the question about this structure is: a single assistant message with multiple content blocks of different types. There are not two messages here, and there is no separate tool role. The Claude API puts tools into the ordinary structure of user and assistant messages. The tool_use block belongs to the assistant message. The tool_result block comes back in the next user message.

On Claude Opus 5.5 a thinking block can also come before the text. This model always uses adaptive thinking. The block then has an empty thinking field and an encrypted signature field. You do not show it to the user, but you send it back in the history unchanged.

Two Foundations exams test these mechanics. The Architect Foundations guide covers the agentic loop in task statement 1.1. The Developer Foundations guide lists custom agent loops, hooks and Claude API mechanics. If you are torn between the two, read a side-by-side of which exam fits integration work and which fits design.

What stop_reason tells your code

Every successful response carries a stop_reason field. The documentation lists seven values. The agentic loop keeps going while tool_use comes back. With server tools, pause_turn also keeps it going. Any other value ends the loop, but not every one of them means a finished answer.

Value When it arrives What your code does
end_turn Claude finished its response. Shows the result.
tool_use Claude asks for a tool call. Runs the tools, returns the results and asks again.
max_tokens The response hit your max_tokens limit. Raises the limit or continues the response. A cut-off tool call is retried with a higher limit.
stop_sequence One of your stop sequences appeared. Reads the stop_sequence field.
pause_turn A server tool loop reached its iteration limit. Sends the response back unchanged so Claude can finish.
refusal Claude declined to respond. Reads stop_details and may retry on another model.
model_context_window_exceeded The response filled the context window. Treats the result as truncated.

Two values are easy to miss. refusal arrives as an ordinary HTTP 200 response, so a try block will not catch it. pause_turn applies only to server tools such as web_search, and the default limit is 10 iterations per request. When Claude stops to call one of your tools, stop_reason is tool_use, never pause_turn. Each value is described on the page about handling stop_reason.

The loop in Python

Below is the loop on the Anthropic Python client. It uses claude-opus-5-5, the model the documentation suggests starting with for most workloads. The run_tool function is your dispatcher. It takes a tool name and its input, and it returns a result or raises an exception.

agent_loop.py
import json
import anthropic

client = anthropic.Anthropic()
MAX_TURNS = 20  # a fuse, not the normal way out

def run_agent(system, tools, messages):
    for _ in range(MAX_TURNS):
        response = client.messages.create(
            model="claude-opus-5-5",
            max_tokens=4096,
            system=system,
            tools=tools,
            messages=messages,
        )
        # Keep every block: thinking, text and tool_use.
        messages.append({"role": "assistant", "content": response.content})

        if response.stop_reason == "tool_use":
            results = []
            for block in response.content:
                if block.type != "tool_use":
                    continue
                try:
                    output = run_tool(block.name, block.input)
                    results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": json.dumps(output),
                    })
                except Exception as err:
                    results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": f"{type(err).__name__}: {err}",
                        "is_error": True,
                    })
            # One user message with a result for every tool_use.
            messages.append({"role": "user", "content": results})
            continue

        if response.stop_reason == "pause_turn":
            continue  # server tools only: send the turn back as is

        # end_turn, max_tokens, refusal and the rest: handle each one.
        return response

    raise RuntimeError(f"no final answer after {MAX_TURNS} turns")

Three places do most of the work. The first appends the whole response.content to the history: thinking, text and tool_use together. The second returns one tool_result for each tool_use block, with the same tool_use_id. All results go in one user message. The third turns an exception into a result with is_error: true, so the loop does not crash.

Claude can ask for several tools at once in one response. The API does not prescribe an order. You can run reads in parallel, and tools with side effects are better run one after another. If you skip a call, still send a tool_result for it with is_error: true and a short reason. The disable_parallel_tool_use field turns parallel calls off. It goes inside tool_choice, not at the top level of the request.

The SDK also has a tool runner that drives this loop for you. It is in beta. The tool runner page suggests going back to a manual loop when you need human approval, custom logging or conditional execution. A refund is exactly that case.

NO PARTNER EMPLOYER?

Anthropic admits to its exams only people from Claude Partner Network organizations. Join our collective of AI consultants and sit all four exams through our partner organization.

Who picks process_refund or escalate_to_human

The loop does not pick the tool. It runs what the model asks for and sends the result back. The documentation describes this split as a contract: “You specify what operations are available and what shape their inputs and outputs take; Claude determines when and how to call them.” The model never runs anything on its own. The Architect Foundations guide calls this “model-driven decision-making (Claude reasons about which tool to call next based on context)”. The opposite is a pre-configured decision tree.

Take an example. A customer asks for a refund. The agent calls lookup_order and gets a purchase date 45 days back. The next step depends on what the model has in context:

  • A policy in the system prompt. The prompt says: “refunds within 30 days of purchase, escalate after that”. The model compares 45 with 30 and calls escalate_to_human.
  • The window in the tool result. lookup_order itself says whether a refund is allowed, and why. The model then has no days to count.
  • Tool descriptions. The process_refund description says when to use it, and the escalate_to_human description says when to hand the case over. The documentation calls a detailed description “by far the most important factor in tool performance”.
  • No policy at all. Nothing mentions a time limit, so the model guesses. The exam guide advises “escalating when policy is ambiguous or silent on the customer’s specific request”.

The second option is the most reliable. Code works out the window, and the model reads a finished result. Such a result can look like this. It is an example, not customer data:

example: a lookup_order result
{
  "order_id": "A-1042",
  "days_since_purchase": 45,
  "refund_window_days": 30,
  "refund_eligible": false,
  "reason": "outside the 30-day refund window"
}

The model reads a tool result the same way it reads the rest of its context. So a result can also smuggle in an instruction when it carries text from someone else’s page or email. The documentation says to treat such content as untrusted. I walk through the mechanism in the post on instructions that reach an agent inside tool results.

Newsletter

The Agentic Architect

Practical patterns, case studies, agentic projects taken apart and AI news. Zero spam, once a week.

You'll get one email to confirm. Unsubscribe with one click. Privacy policy

When the decision must not be left to the model

A system prompt influences the choice, but it does not guarantee it. The Architect Foundations guide says that “when deterministic compliance is required (e.g., identity verification before financial operations), prompt instructions alone have a non-zero failure rate”. It lists “choosing hooks over prompt-based enforcement when business rules require guaranteed compliance” as a skill. You have three places for such a rule:

  • A guard in the process_refund handler. Code checks the window and the amount again before any money moves. It works no matter who called the tool.
  • A prerequisite. Code blocks process_refund until get_customer has returned a verified customer. The guide gives this exact example.
  • A PreToolUse hook. In the Agent SDK and in Claude Code the hook returns permissionDecision: "deny" and a reason in permissionDecisionReason. The tool does not run, and Claude gets the denial message as its result.

The block is half the job. The model still has to know what to do next. So return a result with is_error: true and content it can act on:

example: a denial the model can act on
{
  "type": "tool_result",
  "tool_use_id": "toolu_01B",
  "is_error": true,
  "content": "business_rule: refunds close 30 days after purchase, order A-1042 is 45 days old. Not retryable. Call escalate_to_human with the order id."
}

For such an error the Architect Foundations guide wants a category, whether a retry makes sense, and a description a person can read. The names errorCategory and isRetryable come from the guide. They are content you write yourself, not Messages API fields. The only API field is is_error. Without that information the agent may keep retrying the same refund.

I put this principle first while I was studying for the exam. It is rule one from my exam cheat sheet: whatever must always hold goes into code. The prompt is left with the decisions that need judgment.

Five bugs in an agentic loop

1. You drop the text and thinking blocks. The code pulls only tool_use out of the response and appends only that block to the history. Yet within a tool use turn the API requires thinking blocks to come back complete and unmodified. A modified block ends in a 400 error. Append the whole response.content and filter nothing.

2. A result is missing or in the wrong place. Every tool_use block needs a result with a matching tool_use_id, even when the tool failed. Results go first in the message, before any text. Otherwise the API returns the error “tool_use ids were found without tool_result blocks immediately after”. You can practise this case on a Developer practice question about a 400 error after a timed-out call.

With client tools only, text after the results is allowed, but the documentation advises against adding it right after them. Claude then learns to end its turn and can return an empty response with end_turn. Send an extra request as a separate message once the turn is over.

3. You end the loop on the wrong signal. A loop that stops when a polite sign-off appears in the text loses the tool call still waiting. Treating max_tokens as the end does the same damage, because a cut-off tool_use block lacks its full input. Drive the loop with stop_reason, not with the content of the text. This scenario is worked through in a question in my CCAR-F set about a loop that stops on the model’s polite sign-off.

4. You turn an error into an empty success. The handler catches an exception and returns an empty result. The model sees no order and tells the customer that the order does not exist. Return is_error: true with a description of what went wrong and what to do next. An empty result with an OK status is a silent failure that looks like a valid answer in the logs.

5. The loop has no fuse. The Agent SDK has no turn limit by default. The documentation calls a budget a good default for production agents. The limits are max_budget_usd and max_turns. The Architect Foundations guide lists “setting arbitrary iteration caps as the primary stopping mechanism” among the anti-patterns, next to parsing text. The loop ends on stop_reason, and the limit catches the cases where something went wrong. When it trips, log it as an error.

Test your loop on three orders. The first is 25 days old and should end with a process_refund call. The second is 45 days old and should go to escalate_to_human. The third is also 45 days old, but the customer writes that a manager has already agreed. Here code must stop the refund, even if the model tries it. In the log, read the tool_use blocks, not only the final text.

Frequently asked questions

Claude responds with both explanatory text and a tool use block. What type of message structure is this?

It is a single assistant message with multiple content blocks of different types. The content field holds a text block and a tool_use block, and stop_reason is tool_use. The Claude API has no separate tool role. The tool result comes back as a tool_result block in the next user message.

How does the agentic loop choose between process_refund and escalate_to_human when lookup_order shows a purchase from 45 days ago?

The loop does not choose. The model does, based on the tool result, the policy in the system prompt and the tool descriptions. If the prompt sets a 30-day refund window, the model should escalate. If the window must hold every time, check it in code: in the process_refund handler or in a PreToolUse hook that denies the call.

What stop_reason values does the Messages API return?

Seven: end_turn, tool_use, max_tokens, stop_sequence, pause_turn, refusal and model_context_window_exceeded. The agentic loop continues on tool_use, and with server tools also on pause_turn. The other values end the loop, and each one needs its own handling.

Should an agentic loop have an iteration cap?

Yes, as a fuse. The Agent SDK has no turn limit by default, and the documentation advises a budget for production agents. The main exit condition should still be stop_reason. The Architect Foundations guide lists an arbitrary iteration cap as the primary stopping mechanism among the anti-patterns.

Where this comes from. The API details come from the Claude Platform and Agent SDK documentation, checked on 3 November 2026. The exam details come from the official Architect Foundations and Developer Foundations guides, version 1.0 of July 2026. The order examples are invented. This is unofficial material, not affiliated with Anthropic.

SP

Szymon Paluch

Claude Certified Architect · ex-CTO

Practise the agentic loop on mock questions

60 original Architect Foundations questions, including the customer support agent scenario with lookup_order and process_refund. Study mode shows the explanation after each answer.

Take the practice exam
Related posts