The Agent Loop: Putting It All Together
You now have all three pieces: the think-act-observe loop (Lesson 1), a call to the model (Lesson 2), and a tool call round trip (Lesson 3). In this lesson you assemble them into one complete agent: a small research helper that can answer questions and reach for a tool whenever it needs an exact fact.
This is the heart of the course. Once you see the loop run, you will understand every agent framework, because they all wrap this exact code.
What You'll Learn
- How a single tool call becomes a repeating loop
- The complete code for a working agent
- Why the loop can take several turns for one question
- How to give your agent more than one tool
From one round trip to a loop
In Lesson 3 you handled one tool call by hand. But a real task might need several: the model uses the calculator, sees the result, realizes it needs another calculation, and asks again. You do not want to write that out by hand each time. You want a loop that keeps going until the model is done.
The rule is simple. Keep looping as long as the model is asking for tools. Stop when it returns a normal answer.
- Send messagesCall the model
- Tool requested?Check stop_reason
- Run + appendIf yes, send tool_result
- Final answerIf no, return the text
That middle decision, "did it ask for a tool?", is the entire loop. If yes: run the tool, add the result to the conversation, and call the model again. If no: you have your answer.
The complete agent
Here is a full, working research-helper agent in about 40 lines. It runs on your machine (it calls the live model), so read it here and copy it into a file when you have a key set up. Every piece should look familiar from the last three lessons.
import os
from dotenv import load_dotenv
from anthropic import Anthropic
load_dotenv()
client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
# 1. The tool implementation (your code).
def calculator(expression):
allowed = set("0123456789+-*/(). ")
if not set(expression) <= allowed:
return "Error: unsupported characters in expression."
return str(eval(expression))
# A registry maps tool names to functions, so routing is one lookup.
TOOLS = {"calculator": calculator}
# 2. The tool definitions (what the model sees).
tool_defs = [
{
"name": "calculator",
"description": "Evaluate a basic arithmetic expression. Use for any exact math.",
"input_schema": {
"type": "object",
"properties": {
"expression": {"type": "string", "description": "e.g. '35 * 52'"}
},
"required": ["expression"],
},
}
]
# 3. The agent loop.
def run_agent(question, max_turns=5):
messages = [{"role": "user", "content": question}]
for _ in range(max_turns):
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=600,
system="You are a research helper. Use the calculator for exact math.",
tools=tool_defs,
messages=messages,
)
# If the model is not asking for a tool, we are done.
if response.stop_reason != "tool_use":
return response.content[0].text
# Otherwise, run every tool it asked for and feed results back.
messages.append({"role": "assistant", "content": response.content})
results = []
for block in response.content:
if block.type == "tool_use":
fn = TOOLS[block.name] # look up the function
output = fn(**block.input) # run it with the model's args
results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": output,
})
messages.append({"role": "user", "content": results})
return "Stopped: reached the turn limit."
print(run_agent("If I save 35 dollars a week, how much is that in a year?"))
Run it and you will see the model decide to call calculator("35 * 52"), get 1820, and answer "1,820 dollars a year." It used a tool to get a fact it could trust.
Reading the loop, line by line
A few details are worth slowing down on:
max_turnsis a safety cap. Without it, a confused agent could loop forever and burn through your budget. Five turns is plenty for a simple helper.messages.append(...)is how the agent remembers. Each turn we add the model's request and our result, so the next call sees the full history.TOOLS[block.name]is the registry lookup that replaces theif/elifchain from Lesson 3. Adding a tool is now two steps: add it toTOOLSand add its definition totool_defs.- The loop body handles a list of
tool_useblocks, because a model can request several tools at once. We run each and send all the results together.
Why one question can take several turns
A single question does not always mean a single tool call. Suppose you ask, "What is 12% of 3,400, and then how much per month is that over a year?" The agent might:
- Call
calculator("0.12 * 3400"), observe408. - Think, then call
calculator("408 / 12"), observe34. - Write the final answer: "12% of 3,400 is 408, which is 34 dollars per month."
That is the think-act-observe loop from Lesson 1, running twice before it finishes. Your for _ in range(max_turns) loop handles it automatically.
Adding a second tool
The registry pattern makes growth easy. Want your research helper to also report the current date? Add a function and a definition, nothing else changes.
from datetime import date
def today():
return date.today().isoformat()
TOOLS["today"] = today
tool_defs.append({
"name": "today",
"description": "Return today's date in YYYY-MM-DD form. Use when the user asks about the current date.",
"input_schema": {"type": "object", "properties": {}},
})
Now the same loop drives an agent with two tools. The model picks whichever fits the question. This is exactly how bigger agents work: same loop, more tools.
Trace the loop offline (runs in your browser)
You cannot call the live model here, but you can run a faithful simulation of the loop with a scripted "model" so the mechanics click. Watch how the loop runs the tool, feeds the result back, and only stops when the model returns text.
Change the script to make the model ask for two calculations before answering, and watch the loop run an extra turn. That is the whole engine of an agent.
Key Takeaways
- An agent is a loop: call the model, and if it asks for a tool, run it and call again. Stop when it returns text.
- Always set a
max_turnscap so a confused agent cannot loop forever. - Append both the model's request and your result to
messagesso the agent remembers context across turns. - A tool registry (
TOOLS[name]) plus a definition list makes adding tools trivial. - One question can take several turns; the loop handles that automatically. This same loop scales to any number of tools.

