Introduction
By the end of this course you'll have a terminal AI agent that takes a task like "research the top 3 vector databases and write a comparison to comparison.md", breaks it into steps on its own, actually browses and reads, writes the file, and reports back — no LangChain, no CrewAI, just the Anthropic API and about 150 lines of Python you wrote and understand completely.
Today you're not building any of that yet. You're building the one piece everything else sits on top of: a single tool-augmented API call, and you're going to read the raw response closely enough to see exactly what an "agent" actually is.
Objective
The loop, stripped of marketing language
Every agent product you've used — Claude Code, a customer support bot, a research assistant — is running the same four-step loop:
- Perceive — the model receives the current state: the task, the conversation so far, the result of anything it already did
- Think — the model decides what to do next: answer directly, or call a tool
- Act — if it calls a tool, your code runs that tool (the model can't run anything itself — it can only ask)
- Observe — you feed the tool's result back into the conversation, and go to step 1
That's it. There's no separate "agent brain." It's a chat model, a loop, and some Python that executes what the model asks for. The entire rest of this course is refining that loop and giving it better tools.
The model never runs anything
This trips people up constantly: when the model "calls a tool," it isn't executing code. It's returning a structured request — "please run web_search with this query" — and stopping. Your Python code decides whether to actually run it, what to do if it fails, and what to send back. The model has no hands. You are the hands.
Tool calling vs. a normal chat response
When you send Claude a message without tools, you get back text. When you give it a list of tools it's allowed to use, one of two things happens instead:
- It answers in plain text, same as always (it decided a tool wasn't needed)
- It returns a
tool_use content block: a tool name and a JSON object of arguments — and stops generating, waiting for you to send the result back
The second case is the entire mechanism. There's no special "agent mode" flag — you're using the exact same messages.create call, just with a tools parameter attached.
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
tools=[{
"name": "get_weather",
"description": "Get the current weather for a city",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
}],
messages=[{"role": "user", "content": "What's the weather in Lisbon?"}],
)
print(response.stop_reason)
print(response.content)
stop_reason is "tool_use", not "end_turn" — that's the signal your loop checks to know whether it needs to execute something before continuing. Notice response.content is a list, and it can contain a text block AND a tool_use block together (the model narrating what it's about to do, then asking to do it). Your code has to walk that list and handle both.
Your first real call
get_weather above is a toy — there's no function backing it, so if you actually ran that code and tried to feed a result back, you'd have nothing to feed. Today's lab uses a real tool with almost no logic (add_numbers) specifically so you can see the full request → tool_use → result → final-answer cycle without worrying about a complicated tool implementation yet. Section 2 replaces this with the 4 tools your agent will actually use.
A mistake you will make (so let's make it now, on purpose)
If you forget the input_schema's required field, Claude will sometimes call your tool with arguments missing, and your Python will throw a KeyError with no useful context. Try removing "required": ["city"] from the schema above, ask a vaguer question like "what's the weather like today?", and watch what the model does with an under-specified tool. This is the single most common early agent bug — a tool schema that doesn't tell the model what it actually needs.
Your agent's stop_reason comes back as "end_turn" instead of "tool_use" after you asked it something that should obviously require a tool call. What's the most likely cause?
stop_reason is decided entirely by the model's read of your tool description and the conversation — if it didn't call the tool, it didn't think the tool was relevant. That's almost always a signal problem in your tool description, not a code bug. (Your tool-execution code never even runs if the model doesn't ask for the tool — so a bug there wouldn't produce this symptom at all.)
What you're building
Set up the project, write one real (trivial) tool, make a single tool-augmented call, and inspect the raw JSON-like response object closely enough that the loop in Section 3 won't feel like magic when you build it.