Home › Study guides › CCAR-F › Domain 1 › Lesson 1.1
CCAR-F · Domain 1 · 27% of the exam · Lesson 1.1 · 21 min read
Designing agentic loops
Why an agent is a loop, who decides what happens in it, why stop_reason is the only signal your code should trust, and the loop mistakes the exam asks about.
Written against task statement 1.1 of the official CCAR-F exam guide (Version 1.0, effective July 2026). An independent resource, not affiliated with Anthropic; the practice questions are written from scratch.
1.1.1 Why an agent needs a loop at all
Start with a situation that has nothing to do with software. You ask a colleague, "Is order 4471 still on its way, and can I get a refund if it arrived broken?" They cannot answer by thinking harder. They have to DO something: open the order system, read what it says, maybe check the returns policy, and only then form a reply. If the first screen shows nothing useful, they look somewhere else. When they finally know enough, they stop looking and answer you.
A language model on its own cannot do any of that. By default it does exactly one thing: you send it text, it sends text back, and the exchange is over. It has no hands. It cannot open your order system, read a file or call a payment service. It can only describe what it would like to do. That single request-and-reply is powerful for writing and reasoning, but it is a dead end the moment the task needs an action in the real world.
An agent is what we call the model once we give it hands: the ability to ask for an action, see what came back, and keep going until the job is done. The machinery that makes this possible is almost embarrassingly simple. It is a loop. Your program asks the model what it wants to do, does it, hands back the result, and asks again. Round and round until the model says it is finished. That repeating cycle is the agentic loop, and it is the single most important pattern in this domain. Everything else here (coordinators, hooks, handoffs, sessions) is built on top of it.
A plain model call versus an agent
Plain call one shot
Agent a loop
repeat until the model is finished
1.1.2 Who decides: the model or your code?
Here is the question that trips people up, and the exam loves it: inside the loop, who is in charge, your code or the model? The answer is a deliberate split, and once it is clear, every rule in this lesson follows from it.
The model decides WHAT to do. On each turn it reads the whole conversation so far and chooses which tool it wants next and with which inputs, such as lookup_order for order 4471. Nobody wrote that sequence down in advance. The model reasons from the request and the tool descriptions it was given. That is why an agent can handle a request you never planned for and chain tools in an order you never scripted. The docs and the exam guide call this model-driven decision-making.
Your code decides WHETHER anything happens. The model never runs a tool itself; it emits a structured request and waits. Your loop reads that request, runs the real function, and decides whether to go around again. Think of a driving instructor with dual controls. The learner (the model) says "turn left here" and "now speed up". The instructor (your code) is the one who can actually press the brake, and the one who decides when the lesson is over. The model proposes; your code disposes.
The alternative the guide contrasts this with is a pre-configured decision tree. Your code decides that any message containing the word "order" triggers lookup_order, then get_customer, then a reply, in that fixed order. That is not an agent; it is a script with a model bolted on for the wording. Scripts are not wrong, but they are the wrong answer for ambiguous requests like returns, billing disputes and account issues. A script stops working the moment a customer writes something it did not anticipate. A single rule your code enforces is different, such as blocking process_refund until get_customer has verified the customer. The model still chooses every step; your code only holds back one move until its condition is met.
The division of labour in every loop
The model decides WHAT
Your code decides WHETHER
1.1.3 The four steps, one turn at a time
Now we can walk the loop itself. It is four steps, repeated until the model is done. We will follow the support agent the whole way: a customer writes "Order 4471 arrived broken, I want a refund", and the agent has the tools get_customer, lookup_order, process_refund and escalate_to_human.
- SEND. Your code sends a request to Claude through the Messages API, the service your program calls to talk to Claude (API stands for application programming interface). The request carries the list of tools and the conversation so far. On the first turn that is just the customer's message.
- INSPECT. The reply arrives with a small field called
stop_reason. Your code reads that field, and only that field, to decide what happens next. This is the decision point of the whole loop. - ACT (when the model wants a tool). If
stop_reasonis"tool_use", the reply contains one or moretool_useblocks, each naming a tool and its inputs:lookup_orderwith{"order_id": "4471"}. Your code runs the real lookup, adds the result to the conversation, and goes back to step 1. - FINISH (when the model is done). If
stop_reasonis"end_turn", the model has what it needs and has written its final answer. Your code leaves the loop and shows the customer the reply.
The four-step lifecycle
stop_reason, the decision pointtool_use: run the tool, append the resultend_turn: show the answerNotice how many times the loop can run for one message. A realistic run for our customer goes: tool_use for get_customer to verify who is asking, then tool_use for lookup_order, then tool_use for process_refund, then end_turn with a reply. Four trips to the model, three tool calls, one answer. The model decided it needed those three calls in that order; your loop carried each one out and never had to know the order in advance.
| What the reply says | What it means | What your loop does |
|---|---|---|
stop_reason: "tool_use" plus a tool_use block for get_customer |
"First I need to know who is asking" | Run get_customer, append the result, send again |
stop_reason: "tool_use" plus a tool_use block for lookup_order |
"Verified; now I need the order" | Run lookup_order, append the result, send again |
stop_reason: "tool_use" plus a tool_use block for process_refund |
"It arrived damaged; issue the refund" | Run it, append the confirmation, send again |
stop_reason: "end_turn" plus a text block |
"Here is my reply to the customer" | Leave the loop, show the text |
1.1.4 Why you resend the whole conversation every time
Step 3 said "add the result to the conversation, then go back to step 1". That short instruction is where most first agents quietly break, so let's slow down on it.
The Messages API is stateless. Each request is a blank slate: the service keeps no memory of what you sent thirty seconds ago. The model does not secretly remember that it asked for order 4471. If you want it to know something, that something must be physically present in the conversation you send THIS time. Picture a brilliant consultant with total amnesia between meetings. Every meeting, you must hand over the entire case file again, or they start from nothing.
So when a tool returns a result, you do not hold it in a variable and move on. You append two things to the conversation: first the model's own reply (the message that contained the tool_use block), then a new user message carrying a tool_result block with the tool's answer. Every tool_use block has a unique id, and your tool_result quotes it back in its tool_use_id field, so the model knows which answer belongs to which request. Then you send the whole, longer conversation. Now, on the next turn, the model can actually SEE that lookup_order returned "delivered yesterday, status: damaged claim open" and reason about it.
Forget the append and something strange happens. The model asks for the order, you fetch it, and then you send back the same conversation as before, with no order in it. Seeing no answer, the model asks for the order AGAIN. You have built an infinite loop of forgetting. From the outside it looks like the agent "keeps calling the same tool".
The conversation you resend grows every turn
Turn 1 sends
Turn 2 sends
get_customerget_customerappended by your codeTurn 3 sends
get_customerget_customerlookup_orderlookup_orderThe whole loop fits in a dozen lines. Look at the two append lines: one records what the model asked for, the other records what the tools returned, each result tagged with the tool_use_id it answers. run_tool stands for your own code that calls the real system.
messages = [{"role": "user", "content": "Order 4471 arrived broken, I want a refund"}]
while True:
response = client.messages.create(
model=MODEL, max_tokens=1024, tools=tools,
messages=messages, # the FULL history, every time
)
if response.stop_reason != "tool_use":
break # end_turn = done; any other value: stop and handle it
messages.append({"role": "assistant", "content": response.content}) # what the model asked for
results = [{"type": "tool_result", "tool_use_id": block.id, # which request it answers
"content": run_tool(block.name, block.input)} # really call lookup_order etc.
for block in response.content if block.type == "tool_use"]
messages.append({"role": "user", "content": results}) # APPEND the tool results
If you build with the Claude Agent SDK (SDK stands for software development kit, a ready-made library) rather than the raw API, this loop is written for you. The SDK sends each request, calls every tool Claude asks for (its built-in tools, or your own such as lookup_order) and feeds the results back, until Claude replies without asking for a tool. You still need to understand it, because everything you configure later (hooks, subagents, sessions) attaches to a specific step of exactly this cycle.
1.1.5 stop_reason: the signal that runs everything
We keep saying "read stop_reason, do not read the model's words". Let's understand why that rule is so absolute, because it is the idea this task statement returns to most often.
When the model finishes a turn it could, in principle, tell you in plain English: "I have finished, here is your answer." The temptation is to write code that scans the reply for a phrase like that. Resist it completely. Natural language is ambiguous. The model might write "I have finished checking the order" while fully intending to issue the refund next. If your code sees the word "finished" and stops, you have cut the agent off mid-task. Human language was never designed to be a control signal.
That is exactly why every reply carries stop_reason: a small, fixed, machine-readable field whose entire job is to tell your code, without ambiguity, why the model stopped. It never holds free text, only one of a short, fixed list of values, so your code can test it with a simple comparison. The exam centres on two values, "tool_use" (the model wants a tool, so continue) and "end_turn" (the model is genuinely done, so stop). A production agent should recognise the others too, because each describes a real situation your code has to handle.
stop_reason |
What it means in plain terms | What your loop should do |
|---|---|---|
end_turn |
The model finished naturally; the normal "I'm done" | Leave the loop and show the answer |
tool_use |
The model wants a tool run before it can continue | Run the tool, append the result, go round again |
max_tokens |
The reply hit the length cap you set in max_tokens (tokens are the word pieces the model writes) and was cut off |
Treat as truncated; if it was cut mid tool call, retry with a higher limit |
stop_sequence |
The model wrote one of the custom stop sequences you set | Read stop_sequence to see which one, then handle it |
pause_turn |
A tool Anthropic runs for you (web search, for example) hit its step limit | Send the reply straight back so the model can finish |
refusal |
The model declined to respond, for safety reasons | Stop; read stop_details for the category, and retry on a fallback model if you wish |
model_context_window_exceeded |
The reply filled the model's context window, the most text it can handle at once | Treat as truncated, like max_tokens |
Learn tool_use and end_turn cold; recognise the rest so a real agent does not choke on them. The rule that matters: your loop goes round again on tool_use, and on pause_turn only if you give the model tools Anthropic runs. Any other value means "stop and handle", and only end_turn means "stop because the job is done".
The same logic rules out checking for text. The reply that asks for a tool often contains a short text block as well ("Let me look that order up first") before the tool_use block. Both blocks sit in the same reply, so whether the reply "has text" tells you nothing about whether the model is finished.
1.1.6 The exam traps
Now that you know WHY stop_reason rules the loop, the classic mistakes almost explain themselves. Most of them steer the loop by the wrong signal. Questions about them usually describe an agent misbehaving and ask for the fix, and the fix is essentially always the same.
- ✗ Reading the model's words to decide it is done. ✓ Branch on
stop_reason. Language is ambiguous: "finished checking the order" does not mean finished.stop_reasonexists to remove that ambiguity. - ✗ Using an iteration cap as the main stop. ✓ Stop on
end_turn; keep a cap (say 20, or the Agent SDK'smax_turnsoption) only as a safety net so a buggy agent cannot run forever. A cap as the primary rule either cuts off a task that needed 12 turns or wastes turns on one that finished in 3. - ✗ Stopping because the reply contains a text block. ✓ Use
stop_reason. The model routinely returns explanatory text AND atool_userequest in the same reply, so seeing text proves nothing. - ✗ Scripting the tool order in code. ✓ Let the model choose each next tool from the conversation and good tool descriptions. A keyword router or a fixed sequence is a workflow, and it breaks on the first message it did not anticipate.
- ✗ Dropping tool results to save tokens. ✓ Append every result before the next request. Trimming context is a real technique, but it is a deliberate decision made later, never an accident inside the loop.
Four wrong signals, one right one
stop_reasoncontinue on tool_use, stop on end_turn1.1.7 Put it together: build a loop and break it
You now have every piece: why an agent needs a loop, who decides what, the four steps, why the history is resent, the role of stop_reason, and the anti-patterns. The fastest way to make it stick is to build a tiny agent and then deliberately break it, because feeling the failure is what cements the rule.
Everything in the rest of Domain 1 is this loop, scaled up. Multi-agent orchestration (1.2) is a loop that starts other loops. Spawning subagents (1.3) is about what those inner loops are told. Workflow enforcement (1.4) is your code using its control of the loop to guarantee that certain steps happen in order, such as verifying the customer before any refund. Hooks (1.5) are code that runs at fixed moments inside the cycle, such as just before a tool call. Task decomposition (1.6) splits a large job into pieces those loops can handle. Sessions (1.7) are the saved history of one loop. Master the loop here and the rest of the domain becomes a series of natural extensions.
Key takeaways
- ✓ A plain model call answers once. An agent wraps that call in a loop: send, run the requested tool, feed the result back, repeat until done.
- ✓ The model decides WHAT to do; your code runs the tools and decides WHETHER to continue. The model proposes; your code disposes.
- ✓ The four steps: SEND the full history, INSPECT
stop_reason, ACT ontool_useby running the tool and appending the result, FINISH onend_turn. - ✓ The API is stateless, so the conversation history is the agent's only memory. Every tool request and result must be appended and resent, each result matched to its request by
tool_use_id. - ✓
stop_reasonis the only reliable loop-control signal. The exam centres ontool_useversusend_turn; real agents also handle the other values, such asmax_tokens,refusalandpause_turn. - ✓ The anti-patterns are all one mistake, steering the loop by the wrong signal: parsing the reply text, an iteration cap as the primary stop, checking for a text block, scripting the tool order. The fix: let the model choose each step and branch on
stop_reason.
Check your understanding
4 questions written for this lesson, then one from the CCAR-F question bank on the same topic. Every answer option is explained, including the ones you did not pick. Nothing is stored.
96 CCAR-F questions on Domain 1, free
Every question in the bank is tagged to a domain, so you can drill 96 questions on Agentic Architecture & Orchestration alone, or sit the full 60-question timed simulator.
Open the CCAR-F question bank → Back to Domain 1 →
The question bank is free. It asks for an account only because the quiz engine has to store answers to score them and show which domains are weak. The questions on this page need nothing.