Rubric
Contents — domains, guide and mocks

Agentic loops

CCAR-F 1.19 min read · checked 21 September 2026

Task statementDesign and implement agentic loops for autonomous task execution

The agentic loop

  1. Send requestmessages + tools + system prompt
  2. Claude respondstext, tool requests, or both
  3. Check stop_reasontool_use or end_turn?
  4. Run the toolsappend tool_result blocks

tool_use → go round again · end_turn → return the final answer

The model decides; your code executes. The loop only ends when the model returns a response with no tool request — stop_reason of end_turn.

The loop, precisely

An agentic loop is four steps repeated: send the conversation to Claude, inspect the response, execute any tools it asked for, and append the results so the next request can reason about them. The model — not your code — decides what to call next.

  1. Send the messages, system prompt and tool definitions.
  2. Read stop_reason on the response.
  3. If it is tool_use: run each requested tool, and append the assistant turn and a user turn containing the matching tool_result blocks.
  4. If it is end_turn: the model has finished. Return its final text.
A minimal, correct looppython
messages = [{"role": "user", "content": task}]

while True:
    response = client.messages.create(
        model=MODEL, max_tokens=4096, tools=TOOLS, messages=messages,
    )
    messages.append({"role": "assistant", "content": response.content})

    if response.stop_reason != "tool_use":
        break                                   # end_turn — or a stop you must handle

    results = []
    for block in response.content:
        if block.type == "tool_use":
            output = run_tool(block.name, block.input)
            results.append({
                "type": "tool_result",
                "tool_use_id": block.id,        # ties the result to the request
                "content": output,
            })
    messages.append({"role": "user", "content": results})

Two details carry most of the marks. The whole assistant turn goes back into history, not just its text. And every tool_use block gets a tool_result with the same tool_use_id — one response can request several tools at once, and each needs its own answer.

One turn, message by message

Your code
Claude API
Your tools
Step 1: Your code to Claude API: User task + tool definitions
Step 2: Claude API to Your code: tool_use block, id toolu_01
Step 3: Your code to Your tools: Run get_order("A-1042")
Step 4: Your tools to Your code: { status: "shipped" }
Step 5: Your code to Claude API: Assistant turn + tool_result
Step 6: Claude API to Your code: Final answer · end_turn
The assistant turn that asked for the tool goes back into history before the results do. Skip it and the model sees answers to questions it has no record of asking.

Model-driven versus pre-configured

The guide draws a line between a loop where Claude chooses the next tool from context, and a decision tree or fixed sequence that your code walks through. Neither is wrong. A fixed sequence is predictable and cheap; model-driven selection handles inputs you did not anticipate. The exam asks you to pick the one the scenario calls for — which usually turns on how varied the inputs are.

Stop signalReliable?Why
stop_reason == "end_turn"YesThe API's own statement that the model finished
stop_reason == "tool_use"Yes — keep goingThe model is waiting on tool results
Assistant text contains “done”NoProse is not a protocol; phrasing varies
Response has any text contentNoClaude often explains and calls a tool in one turn
Iteration cap reachedOnly as a backstopStops good runs and hides the real bug

Every stop reason, and what to do about it

“Not tool_use” does not mean “success”. The API documents several other values, and a production loop handles each one on purpose:

stop_reasonWhat happenedHandle it by
end_turnThe model finishedReturning the answer
tool_useThe model wants a tool runRunning it and looping
max_tokensOutput hit your max_tokens limit mid-answerRaising the limit or continuing — never treating the partial text as complete
stop_sequenceOne of your custom stop sequences firedChecking which one, then acting on it
pause_turnA server-side tool loop hit its iteration limitSending the assistant content back so it can continue
refusalThe model declinedReading the details; retrying or escalating — not looping
model_context_window_exceededThe response filled the context windowTreating it as truncated and trimming context

Reading the stop reason

What is stop_reason?
  • tool_use
    Run tools, loop again
  • end_turn
    Done — return answer
  • max_tokens · model_context_window_exceeded
    Truncated — don't trust it
  • refusal
    Stop and escalate

The same loop, inside the Agent SDK

You rarely write this loop by hand in production. The Claude Agent SDK runs it for you: it sends the request, executes built-in and custom tools, appends results, and repeats until Claude replies with no tool calls. Each round trip is one turn. What you get back at the end is a ResultMessage whose subtype tells you how the loop ended.

Who runs the loop

Messages API — you write the loop

  • You check stop_reason after every response
  • You execute each tool and build the tool_result blocks
  • You decide the iteration cap and what hitting it means

Agent SDK — the SDK runs it

  • The loop ends when Claude replies with no tool calls
  • Built-in tools (Read, Bash, Grep…) run automatically, subject to permissions
  • max_turns / maxTurns and max_budget_usd / maxBudgetUsd are backstops
ResultMessage.subtypeMeaningIs result present?
successClaude finished the taskYes
error_max_turnsHit max_turns before finishingNo
error_max_budget_usdHit the spend cap before finishingNo
error_during_executionSomething interrupted the loopNo

Traps the wrong answers are built from

Tempting but wrongDo this instead
Ending the loop when the reply sounds finishedEnd on stop_reason == "end_turn".
Treating any text content as completionA turn can contain text and tool_use together; check stop_reason.
An iteration cap as the main exitKeep a cap as a backstop and treat hitting it as a failure.
Appending only the tool output, not the assistant turnAppend the full assistant content, then a user turn of tool_result blocks.

You should now be able to

  • Write loop control that continues on tool_use and stops on end_turn.
  • Append tool results between iterations so the model reasons over new information.
  • Recognise and remove prose-parsing, text-presence and cap-as-exit stopping logic.
  • Choose between model-driven tool selection and a fixed sequence for a given workflow.

Practice questions

Original questions written for this lesson, in the exam’s style. Answer first, then open the reasoning — every option is explained, including why the wrong ones are tempting.

  1. Question 1

    A support agent sometimes stops after its first tool call, telling the customer “Let me look that up” and then nothing else. The loop exits whenever the response contains any text.

    What is the most appropriate fix?

    1. ARaise the iteration limit from 5 to 20 so the loop can finish.
    2. BExit only when stop_reason is end_turn, and continue while it is tool_use.
    3. CTell the model in the system prompt never to write text before calling a tool.
    4. DExit when the text contains a phrase such as “Is there anything else?”
    Show answer and reasoning
    1. AIncorrect. The loop is not hitting a limit; it is exiting on the wrong signal.
    2. BCorrect. The response contained text *and* a tool request. stop_reason is tool_use, so the loop should run the tool and continue.
    3. CIncorrect. A prompt instruction is probabilistic and treats the symptom; the stopping logic is still wrong.
    4. DIncorrect. Parsing prose for termination is the anti-pattern the guide names.
  2. Question 2

    A single response contains three tool_use blocks. What must the next request contain?

    1. AOne tool_result for whichever tool finished first.
    2. BThe assistant turn, then one user turn with three matching tool_result blocks.
    3. CThree separate requests, one for each tool result in turn.
    4. DOnly the tool outputs as plain user text; the ids are optional.
    Show answer and reasoning
    1. AIncorrect. Every requested call needs its answer.
    2. BCorrect. Results are matched to requests by id, and the assistant turn must be in history for the model to reason about them.
    3. CIncorrect. The results belong together in the turn that follows the request.
    4. DIncorrect. Without ids the model cannot tell which result answers which call.
  3. Question 3

    A document-processing agent built on the Agent SDK is configured with max_turns=10. On a very long contract it stops, and the application shows an empty summary to the user.

    What should the application do?

    1. AShow the user whatever text the last assistant message contained.
    2. BRemove max_turns entirely so that the agent can never be cut off again.
    3. CCheck ResultMessage.subtype; on error_max_turns, resume with a higher limit or report it.
    4. DAsk the model in the system prompt to finish within ten turns.
    Show answer and reasoning
    1. AIncorrect. A run that hit the turn cap did not finish; its last text is not an answer.
    2. BIncorrect. The cap is a legitimate backstop against runaway cost; removing it swaps one problem for another.
    3. CCorrect. The subtype says the loop hit the cap. The session id lets you resume, and the user is told the truth instead of seeing an empty result.
    4. DIncorrect. Prompting does not change how the application interprets a capped run.

Sources

Drafted with AI assistance and checked against the sources above; expert review is in progress. Spotted an error? Tell us and it gets fixed, dated and listed on how this is written.