Claude Certification Guides
CCAR-F/Domain 19 min read

Lesson 1.1

Agentic Loops and Stop Reasons

How a Claude agent actually runs, request by request, and why the stop_reason field decides what your code does next.

An agent built on the Messages API is not a single call. It is a loop: your code sends a request, inspects why the model stopped, acts on that reason, and sends the next request. The model never executes anything itself. Every tool call is a request from Claude that your code chooses to fulfil. The exam cares about this because almost every orchestration question reduces to one thing: what does the loop do when it sees a particular stop_reason?

The shape of the loop

A minimal agentic loop looks like this in Python:

messages = [{"role": "user", "content": task}]

while True:
    response = client.messages.create(
        model=MODEL,            # current model ID from the Anthropic docs
        max_tokens=4096,
        tools=TOOLS,
        messages=messages,
    )
    # Always append the full content list, not just the text.
    messages.append({"role": "assistant", "content": response.content})

    if response.stop_reason == "tool_use":
        results = []
        for block in response.content:
            if block.type == "tool_use":
                out = run_tool(block.name, block.input)
                results.append({"type": "tool_result",
                                "tool_use_id": block.id,
                                "content": out})
        messages.append({"role": "user", "content": results})
        continue

    break   # end_turn, max_tokens, stop_sequence, refusal, pause_turn

Three details matter. First, you append response.content in full so that tool_use blocks (and any thinking blocks) stay in the transcript; the API rejects a tool_result whose tool_use_id has no matching tool_use block. Second, when Claude issues several tool calls in one turn, all of the tool_result blocks go back in a single user message. Splitting them across messages works, but it teaches the model to stop calling tools in parallel. Third, the API is stateless: the messages array is the only memory the agent has between requests.

Stop reasons

The stop_reason field tells you why generation ended. The table is worth memorising.

stop_reasonMeaningWhat the loop should do
end_turnClaude finished naturallyReturn the answer; the loop is done
tool_useClaude wants one or more tools runExecute them, return tool_result blocks, call again
max_tokensOutput hit the max_tokens ceilingTreat as truncated; raise the limit or continue
stop_sequenceA custom stop_sequences string was producedRead stop_sequence to see which one; usually done
pause_turnA server-side tool loop pausedResend the conversation unchanged to resume
refusalA safety classifier declinedCheck stop_details; do not retry blindly

max_tokens deserves care. If the cut-off happens in the middle of a tool_use block, the input JSON is incomplete and must not be executed. The remedy is to increase max_tokens (or stream, which removes most practical ceilings) and retry, not to parse what arrived.

pause_turn only appears when server-side tools such as web search run inside the request. The server runs its own internal loop with an iteration limit, and when that limit is reached it hands control back. The correct response is to append the assistant turn and call again with no new user message; the API recognises the trailing server tool block and continues. Adding a "please continue" message is a common mistake.

refusal arrives with HTTP 200, not an error status. The stop_details object is populated only for this reason and carries a category and explanation. Code that reads content[0].text without checking stop_reason first will crash or mislead on a refusal.

Tool errors are part of the loop

When a tool fails, do not drop the result or raise an exception out of the loop. Return a tool_result with is_error: true and a readable message. Claude will typically acknowledge the failure and either retry differently or explain the problem. The same applies to invalid tool input: validate block.input against your schema and send the validation error back as an error result.

Guarding the loop

A loop with no exit other than end_turn is a liability. Production loops add at least one of:

  • a maximum iteration count (the Agent SDK exposes this as maxTurns);
  • a cost or token budget tracked from response.usage across iterations;
  • a wall-clock timeout;
  • a set of tools whose execution requires human approval before the loop continues.

The SDK tool runners (client.beta.messages.tool_runner in Python, toolRunner in TypeScript) implement the request, execute, append cycle for you and expose per-turn hooks for approval and logging. Note that they do not automatically resume pause_turn; that still needs handling.

Exam focus

  • tool_use means "call me back with results"; the model does not run tools and the loop is not finished.
  • All tool_result blocks for one assistant turn belong in a single user message, each carrying the matching tool_use_id.
  • max_tokens is a truncation signal, not success; a truncated tool_use block must never be executed.
  • pause_turn is resumed by resending the conversation unchanged, without a new user message.
  • refusal is a 200 response with stop_details, not an HTTP error; check stop_reason before reading content.
  • Append the full response.content, never just the extracted text, or later turns will fail validation.

Check yourself

5 questions

  1. 1.A developer is implementing a manual agentic loop directly on the Messages API. After each response arrives, which field should the loop inspect to decide whether to execute tools, return the final answer, or handle truncation?

  2. 2.A team's agent loop receives a response with stop_reason 'tool_use' containing two tool_use blocks. The code executes the first tool and sends its result in a user message, then executes the second and sends a separate user message. Over time Claude stops requesting tools in parallel. What is the correct fix?

  3. 3.An agent response returns stop_reason 'max_tokens' and the last content block is an incomplete tool_use block whose input JSON is cut off. What should the loop do?

  4. 4.A research agent uses Anthropic's server-side web search tool. Some responses come back with stop_reason 'pause_turn' and a trailing server_tool_use block. The team's loop appends a user message reading 'Please continue' and re-sends, but results are inconsistent. Which design is correct?

  5. 5.Which statement correctly distinguishes a stop_reason of 'refusal' from an API error?