Build an autonomous Claude agent in Python using the Anthropic SDK. Agentic loop pattern: Claude calls tools, you execute them, Claude continues until done. Working code examples.
An agentic loop lets Claude autonomously call tools until it solves your task. Here's the complete pattern in Python.
import anthropic
client = anthropic.Anthropic()
def run_agent(user_task: str, tools: list, tool_executor) -> str:
"""Run Claude in an agentic loop until it completes the task."""
messages = [{"role": "user", "content": user_task}]
max_iterations = 15
for _ in range(max_iterations):
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=4096,
tools=tools,
messages=messages
)
# Append assistant response
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason == "end_turn":
# Claude is done — extract final text
for block in response.content:
if hasattr(block, "text"):
return block.text
return ""
if response.stop_reason == "tool_use":
# Execute all tool calls in this response
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = tool_executor(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": str(result)
})
messages.append({"role": "user", "content": tool_results})
return "Agent hit iteration limit without completing."
import anthropic, os, json
client = anthropic.Anthropic()
# Define the tools Claude can use
TOOLS = [
{
"name": "read_file",
"description": "Read the contents of a file at the given path.",
"input_schema": {
"type": "object",
"properties": {"path": {"type": "string", "description": "Relative file path to read"}},
"required": ["path"]
}
},
{
"name": "write_file",
"description": "Write content to a file. Creates the file if it doesn't exist.",
"input_schema": {
"type": "object",
"properties": {
"path": {"type": "string"},
"content": {"type": "string"}
},
"required": ["path", "content"]
}
},
{
"name": "list_files",
"description": "List files in a directory.",
"input_schema": {
"type": "object",
"properties": {"directory": {"type": "string", "description": "Directory path (use '.' for current)"}},
"required": ["directory"]
}
}
]
def execute_tool(name: str, inputs: dict) -> str:
if name == "read_file":
try:
with open(inputs["path"]) as f:
return f.read()
except FileNotFoundError:
return f"Error: file not found: {inputs['path']}"
elif name == "write_file":
with open(inputs["path"], "w") as f:
f.write(inputs["content"])
return f"Wrote {len(inputs['content'])} chars to {inputs['path']}"
elif name == "list_files":
entries = os.listdir(inputs.get("directory", "."))
return json.dumps(sorted(entries))
return f"Unknown tool: {name}"
# Run the agent
result = run_agent(
user_task="List the files in the current directory, then write a SUMMARY.md file listing them.",
tools=TOOLS,
tool_executor=execute_tool
)
print(result)
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=4096,
system=[
{
"type": "text",
"text": "You are an expert software engineer...",
"cache_control": {"type": "ephemeral"} # cache the system prompt
}
],
tools=TOOLS, # tools are also cacheable
messages=messages
)
Caching the system prompt saves ~90% cost on long system prompts after the first call. See the prompt caching guide for full details. For multi-agent orchestration patterns, see the tool use deep-dive.