What Is an Agent (With Code) — Building AI Agents, Part 1
- Shawn West
- Jul 8
- 3 min read
Updated: Aug 6
Building AI Agents · Part 1
An agent is an LLM that decides what to do next, takes an action, observes the result, and decides again. This tutorial shows the smallest working agent so the concept is concrete.
What You'll Build
A working agent in ~50 lines of code that solves a simple task.
Step 1: The Agent Loop (5 min)
1. Observe (current state)
2. Decide (what to do next)
3. Act (call a tool)
4. Observe (result)
5. Decide (next action or "done")
6. Loop until done
This is the core. Everything else is refinement.
Step 2: The Minimal Tools (10 min)
For our example: a calculator agent.
def add(a: float, b: float) -> float:
return a + b
def multiply(a: float, b: float) -> float:
return a * b
def subtract(a: float, b: float) -> float:
return a - b
TOOLS = [
{
"name": "add",
"description": "Add two numbers",
"input_schema": {
"type": "object",
"properties": {
"a": {"type": "number"},
"b": {"type": "number"},
},
"required": ["a", "b"],
}
},
# ... multiply, subtract similarly
]
TOOL_FN = {"add": add, "multiply": multiply, "subtract": subtract}
Step 3: The Agent Loop in Code (15 min)
def agent(task: str, max_steps: int = 10):
messages = [{"role": "user", "content": task}]
for step in range(max_steps):
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=TOOLS,
messages=messages,
)
if response.stop_reason == "tool_use":
# Find the tool call
tool_use = next(c for c in response.content if c.type == "tool_use")
# Execute
result = TOOL_FN[tool_use.name](**tool_use.input)
print(f"[Step {step}] Called {tool_use.name}({tool_use.input}) = {result}")
# Add to conversation
messages.append({"role": "assistant", "content": response.content})
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": str(result),
}]
})
else:
# Final answer
return response.content[0].text
return "Max steps reached"
That's the agent. 30 lines.
Step 4: Run It (5 min)
result = agent("What is (5 + 3) * 7?")
print(f"\nFinal: {result}")
Watch the steps:
[Step 0] Called add({'a': 5, 'b': 3}) = 8
[Step 1] Called multiply({'a': 8, 'b': 7}) = 56
Final: (5 + 3) * 7 = 56
The agent:
Decided to add 5 + 3 first
Got the result
Decided to multiply by 7
Returned the final answer
Each step was the model deciding what to do.
Step 5: Try a More Complex Task (5 min)
result = agent("If I have 4 boxes of 12 apples each, how many do I have? Then if I give half away, how many remain?")
print(result)
Watch the multi-step reasoning unfold.
Step 6: Understand What's Different From Tools (5 min)
In Part 7 of Path 9, you used tools. The model called a tool, got a result, responded.
An agent does multi-step:
Tool → result → another tool → another result → another tool → ... → final answer
The model controls the loop
The number of steps is dynamic
That's the agent abstraction.
Step 7: Add a System Prompt (10 min)
For more reliable behavior:
SYSTEM_PROMPT = """
You are a math assistant. You have access to add, multiply, and subtract tools.
When given a math problem:
1. Break it into single-operation steps
2. Use the tools for each step (don't compute mentally)
3. Use intermediate results in subsequent steps
4. Give a clear final answer
If a task doesn't need calculation, answer directly without tools.
"""
response = client.messages.create(
system=SYSTEM_PROMPT,
# ...
)
The agent's behavior steers significantly.
Step 8: Log the Trace (10 min)
For each agent run, log:
trace = {
"task": task,
"steps": [],
"final": None,
}
for step in range(max_steps):
response = client.messages.create(...)
if response.stop_reason == "tool_use":
tool_use = ...
result = ...
trace["steps"].append({
"step": step,
"tool": tool_use.name,
"input": tool_use.input,
"output": str(result),
})
# ...
else:
trace["final"] = response.content[0].text
break
log_trace(trace)
Traces are how you debug agents. Save every one in development.
Step 9: Add Error Handling (10 min)
Tools can fail:
def execute_tool(tool_use):
try:
return TOOL_FN[tool_use.name](**tool_use.input)
except Exception as e:
return f"Error: {str(e)}"
Return the error to the agent. It can recover (try different approach) or give up.
Step 10: The Iteration (varies)
This is the foundation. From here:
Part 2: more tools
Part 3-10: progressively more sophisticated
But the loop stays the same. Decide. Act. Observe. Repeat.
What You Just Did
You built a working AI agent. The loop is visible; the code fits on a screen; the concept is concrete.
Common Failure Modes
No max_steps. Agent loops forever on confused tasks.
No error handling. Tool failure breaks the loop.
No trace logging. Can't debug.
Single-step thinking. Tools defined but agent doesn't actually chain them.
Hidden mental math. Model computes 5+3 without calling the tool. Force tool use via prompt.
Continue the Building AI Agents path
Part of the Building AI Agents learning path.


