A10.0 What This Teaches
list_files and
read_file tools repeatedly until it can answer a question about the
project. It covers:
- The agent loop pattern - call, tool, result, repeat
- Handling multiple tool calls in a single response
- Setting a
max_iterationssafety limit - Combining tools to answer multi-step questions
A10.1 What an Agent Is
- Call the model with the current message history and available tools
- If
stop_reason == "end_turn", return the final answer and stop - If
stop_reason == "tool_use", find all tool calls in the response, execute each one, collect the results - Append the assistant message and the tool results to the history
- Go back to step 1
A10.2 The Tools
# agent_tools.py - file-system tools for the agent.
import os
def list_files(directory: str) -> str:
# returns filenames as a comma-separated string
try:
names = os.listdir(directory)
return ", ".join(names) if names else "(empty directory)"
except FileNotFoundError:
return f"Error: directory not found: {directory}"
def read_file(path: str) -> str:
# returns first 500 chars to keep token usage manageable
try:
with open(path, "r", encoding="utf-8") as f:
content = f.read(500)
return content if content else "(empty file)"
except FileNotFoundError:
return f"Error: file not found: {path}"
TOOLS = [
{
"name": "list_files",
"description": "List the files in a directory. Returns filenames as a comma-separated string.",
"input_schema": {
"type": "object",
"properties": {
"directory": {"type": "string", "description": "Directory path to list"}
},
"required": ["directory"],
},
},
{
"name": "read_file",
"description": "Read the first 500 characters of a text file.",
"input_schema": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "File path to read"}
},
"required": ["path"],
},
},
]
FUNCTION_MAP = {"list_files": list_files, "read_file": read_file}
A10.3 The Agent Loop
end_turn or the iteration
limit is hit. When the model calls multiple tools in one response, collect all
results before sending them back.
# Agent loop skeleton.
MAX_ITERATIONS = 10
def run_agent(question: str) -> str:
messages = [{"role": "user", "content": question}]
for iteration in range(MAX_ITERATIONS):
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=TOOLS,
messages=messages,
)
if response.stop_reason == "end_turn":
return response.content[0].text
if response.stop_reason != "tool_use":
return f"Unexpected stop_reason: {response.stop_reason}"
# collect all tool results from this turn
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = FUNCTION_MAP[block.name](**block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
})
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
return "Stopped: max_iterations reached without a final answer."
A10.4 Complete Implementation
# simple_agent.py - file-reading agent using Claude.
# Usage: python simple_agent.py
import os
import anthropic
client = anthropic.Anthropic()
MAX_ITERATIONS = 10
def list_files(directory: str) -> str:
try:
names = os.listdir(directory)
return ", ".join(names) if names else "(empty directory)"
except FileNotFoundError:
return f"Error: directory not found: {directory}"
def read_file(path: str) -> str:
try:
with open(path, "r", encoding="utf-8") as f:
content = f.read(500)
return content if content else "(empty file)"
except FileNotFoundError:
return f"Error: file not found: {path}"
TOOLS = [
{
"name": "list_files",
"description": "List the files in a directory. Returns filenames as a comma-separated string.",
"input_schema": {
"type": "object",
"properties": {
"directory": {"type": "string", "description": "Directory path"}
},
"required": ["directory"],
},
},
{
"name": "read_file",
"description": "Read the first 500 characters of a text file.",
"input_schema": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "File path"}
},
"required": ["path"],
},
},
]
FUNCTION_MAP = {"list_files": list_files, "read_file": read_file}
def run_agent(question: str) -> str:
messages = [{"role": "user", "content": question}]
for iteration in range(MAX_ITERATIONS):
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=TOOLS,
messages=messages,
)
if response.stop_reason == "end_turn":
return response.content[0].text
if response.stop_reason != "tool_use":
return f"Unexpected stop_reason: {response.stop_reason}"
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = FUNCTION_MAP[block.name](**block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
})
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
return "Stopped: max_iterations reached."
if __name__ == "__main__":
question = "What Python files are in the current directory and what does the first one do?"
print(run_agent(question))
A10.5 Safety Limits
- A tool always returns new data that triggers another tool call
- A bug in a tool returns an error that the model keeps retrying
- The question is unanswerable with the tools provided
max_iterations before running an agent loop. Ten is
reasonable for simple file tasks. Complex agents may need more, but start low
and raise only when needed - each iteration costs tokens.
# Safety guard at the top of the loop.
for iteration in range(MAX_ITERATIONS):
...
return "Stopped: max_iterations reached without a final answer."
A10.6 Sample Session
$ python simple_agent.py
[Agent calls list_files(".")]
-> app.py, requirements.txt, simple_agent.py
[Agent calls read_file("app.py")]
-> # app.py - Flask chat server backed by Claude. ...
The current directory contains three Python-related files:
- app.py: A Flask web server that provides a chat interface backed by Claude.
It defines two routes: GET / serves the chat page and POST /chat accepts
a message, calls Claude, and returns the reply as JSON.
- requirements.txt: Lists the project dependencies (flask, anthropic).
- simple_agent.py: The agent script itself.
A10.7 Extending the Agent
write_file(path, content)- lets the agent create or update files. Add a confirmation prompt before executing: writing the wrong file can destroy work.run_python(code)- lets the agent execute code snippets. Dangerous without sandboxing; never run this on untrusted input.search_file(path, pattern)- returns lines matching a regex. Useful for finding function definitions or TODO comments.
A10.8 Exercise
Exercise
Add a
The agent should call
word_count(path) tool that returns the number of words in a
file. Then ask the agent: "Which file in the current directory has the most words?"
- Define
word_count(path)usingopen()andlen(content.split()). - Add the tool schema and add it to
FUNCTION_MAP. - Run the agent with the question above.
list_files(".") to get the filenames, then
call word_count on each one, then report which file has the highest
count.
A10.9 Common Mistakes
No max_iterations limit
Returning only one tool_result when multiple tools were called
tool_result for each one. The API validates that every
tool_use_id in the assistant message has a matching
tool_result in the next user message. Missing one causes a
validation error.
Giving the agent write or delete tools without safety checks
delete_file tool and a misunderstood question can
delete files you did not intend to remove. For any tool with a destructive side
effect, print what the agent intends to do and require confirmation before
executing.
A10.10 Key Terms
| Term | Meaning |
|---|---|
| agent loop | A while/for loop that repeatedly calls the model, executes tools, and feeds results back until the model finishes |
| tool call | A request from the model to execute a named function with specific arguments |
| tool result | The return value of the executed function, sent back to the model as a tool_result content block |
| max_iterations | An upper bound on how many times the agent loop may run; prevents runaway loops |
| stop_reason | "end_turn" means the model is done; "tool_use" means it wants to call a function |
| end_turn | The stop_reason value that signals the model has finished and produced its final answer |
| multi-step reasoning | The model answering a question by chaining multiple tool calls, each using the result of the previous one |