The difference between a chatbot and an agent. The agent loop. Your first working agent in ~50 lines of Python.
A basic agent that runs a loop: calls Claude, receives a tool call request, executes the tool, feeds the result back, and continues until Claude has a final answer. It handles search and math. ~50 lines of Python.
A chatbot takes a message in, returns a message out. That's the entire lifecycle. One turn, one response, done.
An agent runs a loop. It can take multiple actions, observe the results of those actions, and decide what to do next based on those observations. The loop runs until the task is complete — or until it hits a stopping condition.
This distinction matters because most real-world tasks aren't "one message in, one answer out." They require:
Chatbots handle questions. Agents handle tasks.
The key to this loop is tool calling. Here's what that means in plain English: instead of just writing a text answer, Claude can say "I need to run a calculation — here's the math expression" and your code actually runs that calculation, then hands the result back to Claude. Claude reads the result and continues from there.
Think of it like a research analyst who can pick up the phone and call an expert, wait for the answer, and then incorporate it into their work. The analyst (Claude) decides when to call and what to ask. The expert (your tool) just answers the question. The analyst writes the final report.
This pattern — Claude decides, your code acts, Claude reads the result, repeat — is the foundation of every serious AI agent system. The architecture is simple. The leverage comes from what tools you give the agent access to.
Install the SDK first:
pip install anthropic export ANTHROPIC_API_KEY=your_key_here
Here's the complete first agent:
import anthropic import json import math client = anthropic.Anthropic() # ── Tool definitions ────────────────────────────── TOOLS = [ { "name": "search", "description": "Search for information about a topic", "input_schema": { "type": "object", "properties": { "query": {"type": "string", "description": "Search query"} }, "required": ["query"] } }, { "name": "calculate", "description": "Evaluate a mathematical expression", "input_schema": { "type": "object", "properties": { "expression": {"type": "string", "description": "Math expression to evaluate"} }, "required": ["expression"] } } ] # ── Tool implementations ─────────────────────────── def search(query: str) -> str: # Simulated search — replace with real API in production results = { "population of france": "France has a population of approximately 68 million (2024).", "gdp of germany": "Germany's GDP is approximately $4.4 trillion (2023).", "python release date": "Python was first released in 1991 by Guido van Rossum.", } for key, val in results.items(): if any(word in query.lower() for word in key.split()): return val return f"No results found for '{query}'" def calculate(expression: str) -> str: try: # Safe eval: only math operations allowed = {'__builtins__': {}, 'math': math} for name in dir(math): allowed[name] = getattr(math, name) result = eval(expression, allowed) return str(result) except Exception as e: return f"Error: {e}" def execute_tool(tool_name: str, tool_input: dict) -> str: if tool_name == "search": return search(tool_input["query"]) elif tool_name == "calculate": return calculate(tool_input["expression"]) raise ValueError(f"Unknown tool: {tool_name}") # ── The agent loop ───────────────────────────────── def run_agent(task: str, max_steps: int = 10) -> str: messages = [{"role": "user", "content": task}] for step in range(max_steps): print(f"\n--- Step {step + 1} ---") response = client.messages.create( model="claude-sonnet-4-5", max_tokens=1024, tools=TOOLS, messages=messages ) # If no tool calls, Claude has a final answer if response.stop_reason == "end_turn": final = response.content[0].text print(f"Final answer: {final}") return final # Process tool calls tool_results = [] for block in response.content: if block.type == "tool_use": print(f" Tool: {block.name}({block.input})") result = execute_tool(block.name, block.input) print(f" Result: {result}") tool_results.append({ "type": "tool_result", "tool_use_id": block.id, "content": result }) # Add assistant response + tool results to history messages.append({"role": "assistant", "content": response.content}) messages.append({"role": "user", "content": tool_results}) return "Max steps reached without final answer." # ── Test it ──────────────────────────────────────── if __name__ == "__main__": run_agent( "What is the population of France divided by 1000? " "Search for the population first, then calculate." )
Run it: python agent_day1.py. You should see Step 1 call the search tool, Step 2 call calculate with the result, and Step 3 produce the final answer. That three-step sequence is the agent loop in action.
Each tool is a Python dictionary (a key-value data structure) with three fields:
name — what the tool is called (Claude uses this to refer to it)description — plain English explanation of what it does (Claude reads this to decide when to use it)input_schema — a description of what inputs the tool accepts, written in a standard format called JSON SchemaIn plain English: the description is the job posting, and the schema is the required fields on the application form. Write clear descriptions — they directly affect whether Claude picks the right tool.
The code maintains a running list called messages. Every turn of the conversation gets added to this list — your original task, Claude's responses, the results of tool calls. On each API call, you send the entire list so Claude can see everything that happened so far. That's how it knows what it has already done and what still needs to happen.
Every response from Claude includes a stop_reason field that says why Claude stopped generating. Two values matter here:
"end_turn" — Claude has a final answer. No more tools needed. The loop exits and returns the answer."tool_use" — Claude wants to call a tool. The loop runs the tool, adds the result to the message history, and calls Claude again.Why max_steps? Agents can loop forever if the task isn't solvable or a bug creates a cycle. The max_steps parameter is a safety limit — after this many steps, the loop stops regardless. Always set one. In production, you'll also want to set a token budget (a limit on how much text is processed in total). We cover that on Day 5.
get_current_time that returns the current datetimestep_count to the print output so you can see the loop clearlyTomorrow: Tools. You'll build 5 real tools (web search, file reading, API calls, database queries) and give the agent the ability to solve complex multi-step problems.
Before moving on, make sure you can answer these without looking: