Open this lesson in your favourite AI. It'll walk you through the why, explain the demo, and quiz you on the try-it list.
Start the smallest. A true agent needs: one tool, a loop, a hard iteration cap, and a way to stop. The minimal example fits in 30 lines and exposes the core mechanics: tool definition, model decision, tool execution, result handoff, loop. Master the minimum before reaching for LangGraph or LlamaIndex; otherwise you're debugging the framework, not your agent.
The pattern: define a tool's schema, send it to the model with a user message, the model either generates end_turn (final answer) or tool_use (call the tool). On tool_use, execute the tool, append the result as a tool_result message, loop. Iteration cap prevents infinite loops. This is the entire agentic primitive — everything else (sub-agents, memory, planning) is layered on top.
Use these three in order. Each builds on the one before.
Walk me through the minimal agent loop in one paragraph. What's the smallest piece you can remove?
Walk me through stop_reason in the Anthropic API: when does it say end_turn vs tool_use? What about max_tokens / stop_sequence?
Design an agent that does 'open browser → search → read result → answer'. What tool surface, what max_steps, what's the iteration cost?
from anthropic import Anthropic
client = Anthropic()
# One tool
TOOLS = [{
"name": "get_weather",
"description": "Get the current weather for a city.",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string", "description": "City name"}},
"required": ["city"],
},
}]
def get_weather(city):
# call your weather API
return {"city": city, "temp_c": 22, "conditions": "sunny"}
def run_agent(user_msg, max_steps=4):
messages = [{"role": "user", "content": user_msg}]
for step in range(max_steps):
resp = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=800,
tools=TOOLS,
messages=messages,
)
messages.append({"role": "assistant", "content": resp.content})
if resp.stop_reason == "end_turn":
return resp.content[0].text
# execute every tool_use block
tool_results = []
for block in resp.content:
if block.type == "tool_use":
if block.name == "get_weather":
result = get_weather(block.input["city"])
else:
result = {"error": "unknown tool"}
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result),
})
messages.append({"role": "user", "content": tool_results})
return "(agent reached max_steps without finishing)"
print(run_agent("What's the weather in Paris and Berlin?"))
# Expected: model calls get_weather twice (often in parallel), then answers
# with both temperatures. ~2 LLM calls + 2 tool calls.python3 main.py