Intermediate
Building a Simple Agent
A minimal agent is just a loop: call Claude, check if it wants to use a tool, execute the tool, send the result back, repeat until done. This page shows a complete minimal implementation in Python using the Anthropic SDK.
Setup: SDK and Tool Definition
import anthropic
import json
client = anthropic.Anthropic()
# Define a simple tool
tools = [
{
"name": "get_stock_price",
"description": "Get the current stock price for a ticker symbol. Returns the price as a float.",
"input_schema": {
"type": "object",
"properties": {
"ticker": {
"type": "string",
"description": "The stock ticker symbol, e.g. 'AAPL' or 'GOOGL'"
}
},
"required": ["ticker"]
}
}
]
def get_stock_price(ticker: str) -> float:
# In a real implementation, this would call a stock API
prices = {"AAPL": 175.42, "GOOGL": 140.88, "MSFT": 378.91}
return prices.get(ticker, None)The Basic Agent Loop
User Message
Add to messages[]
Call Claude
API request
stop_reason?
tool_use or end_turn
Execute Tool
Append result
Return Answer
Loop complete
Simple agent loop — iterate until end_turn or max_iterations
def run_agent(user_message: str, max_iterations: int = 10) -> str:
messages = [{"role": "user", "content": user_message}]
for iteration in range(max_iterations):
# Call Claude with the current messages and tools
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=messages
)
# Add Claude's response to the messages array
messages.append({"role": "assistant", "content": response.content})
# If Claude is done (no tool calls), return the final text response
if response.stop_reason == "end_turn":
# Extract text from the final response
for block in response.content:
if hasattr(block, "text"):
return block.text
return "Task completed."
# If Claude wants to use tools, process the tool calls
if response.stop_reason == "tool_use":
tool_results = []
for block in response.content:
if block.type == "tool_use":
# Execute the requested tool
result = execute_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result)
})
# Send all tool results back to Claude
messages.append({"role": "user", "content": tool_results})
return "Max iterations reached."
def execute_tool(name: str, inputs: dict):
if name == "get_stock_price":
price = get_stock_price(inputs["ticker"])
if price is None:
return {"error": f"Ticker '{inputs['ticker']}' not found"}
return {"ticker": inputs["ticker"], "price": price}
return {"error": f"Unknown tool: {name}"}When Claude Signals It Is Done
The stop_reason field tells you why Claude stopped generating:
end_turn— Claude finished its response naturally. The task is complete (or Claude needs more information from the user).tool_use— Claude is requesting tool calls. Execute them and continue the loop.max_tokens— The response was cut off. Increasemax_tokensor handle this as an error.stop_sequence— A configured stop sequence was reached.
Maximum Iterations: Preventing Infinite Loops
Always set a maximum iteration count. Without it, a bug in your tool implementation or an unexpected model behaviour could cause the loop to run indefinitely:
- 10–15 iterations is appropriate for most simple agents
- Complex research or coding agents may need 20–50 iterations
- If max iterations is reached, return an error or partial result — do not silently truncate
- Log the iteration count for each run — consistently high iteration counts indicate a problem with your tool descriptions or task scope
Running the Agent
# Example usage
result = run_agent("What's the current price of Apple and Microsoft stock? Which one is higher?")
print(result)
# Claude will call get_stock_price twice (AAPL and MSFT),
# then compare and answer the question.Checklist: Do You Understand This?
- The agent loop is a while/for loop in your code — Claude does not loop itself
- Maintain a messages array; append Claude's response after each API call, then append tool results
- Check
stop_reason:end_turn= done;tool_use= call tools and continue - Always set a max iterations limit — no safeguard against infinite loops otherwise
- Return descriptive errors from tool calls — Claude adapts based on what the error message says