🧰Claude Agent SDK Overview
Build custom agents with Claude's official Agent SDK
What Is the Claude Agent SDK?
The Claude Agent SDK is Anthropic's official framework for building autonomous AI agents powered by Claude. Instead of writing raw API calls and manually managing conversation loops, the SDK gives you a structured, production-ready way to create agents that can use tools, maintain memory, handle errors, and run multi-turn conversations — all out of the box.
Think of it this way: the Claude API gives you the engine. The Agent SDK gives you the entire vehicle — steering, brakes, dashboard, and GPS included.
Why Use the Agent SDK Instead of Raw API Calls?
You can absolutely build agents using the Claude API directly. But here is what you would need to handle yourself:
- Conversation loop management — tracking messages, sending them back, and deciding when to stop
- Tool call parsing — extracting tool calls from responses, executing them, and feeding results back
- Error recovery — retrying failed tool calls, handling rate limits, managing timeouts
- Memory and state — persisting context across turns and sessions
- Configuration — managing model selection, token limits, system prompts, and safety settings
The Agent SDK handles all of this for you. It provides a clean, declarative interface where you define your agent's behavior and the SDK manages the execution loop.
| Aspect | Raw API | Agent SDK |
|---|---|---|
| Conversation loop | You build it manually | Built-in agent loop |
| Tool execution | You parse and execute | Automatic dispatch |
| Error handling | Custom retry logic | Configurable retry policies |
| Memory | You manage context | Pluggable memory providers |
| Multi-turn | Manual message tracking | Automatic turn management |
| Setup time | Hours to days | Minutes |
Installation
The Agent SDK is available for both Python and TypeScript. Install it using your preferred package manager.
Python Installation
# Using pip
pip install claude-agent-sdk
# Using poetry
poetry add claude-agent-sdk
# Using uv (recommended for new projects)
uv add claude-agent-sdkTypeScript Installation
# Using npm
npm install @anthropic-ai/agent-sdk
# Using yarn
yarn add @anthropic-ai/agent-sdk
# Using pnpm
pnpm add @anthropic-ai/agent-sdkEnvironment Setup
Both installations require an Anthropic API key. Set it as an environment variable:
export ANTHROPIC_API_KEY="sk-ant-your-key-here"Or pass it directly when creating the agent (not recommended for production):
# Python — passing the key directly
from claude_agent_sdk import Agent
agent = Agent(api_key="sk-ant-your-key-here")Creating Your First Agent
Let us start with the simplest possible agent — one that takes a prompt and returns a response, with no tools and no memory.
Python — Basic Agent
from claude_agent_sdk import Agent
# Create a basic agent
agent = Agent(
model="claude-sonnet-4-20250514",
system_prompt="You are a helpful assistant that answers questions concisely.",
max_turns=1
)
# Run the agent with a single prompt
result = agent.run("What is the capital of France?")
print(result.output)
# Output: The capital of France is Paris.TypeScript — Basic Agent
const agent = new Agent({
model: "claude-sonnet-4-20250514",
systemPrompt: "You are a helpful assistant that answers questions concisely.",
maxTurns: 1,
});
const result = await agent.run("What is the capital of France?");
console.log(result.output);
// Output: The capital of France is Paris.This is the foundation. Every agent you build starts here and adds capabilities on top.
Registering Tools
Tools are what make agents powerful. A tool is a function that the agent can call to interact with the outside world — search the web, read a file, query a database, call an API, or perform calculations.
Python — Defining and Registering Tools
from claude_agent_sdk import Agent, tool
@tool(
name="get_weather",
description="Get the current weather for a given city.",
parameters={
"city": {
"type": "string",
"description": "The city name, e.g. 'London'"
}
}
)
def get_weather(city: str) -> str:
# In production, call a real weather API
return f"The weather in {city} is 22C and sunny."
@tool(
name="calculate",
description="Evaluate a mathematical expression and return the result.",
parameters={
"expression": {
"type": "string",
"description": "A math expression like '2 + 2' or '144 ** 0.5'"
}
}
)
def calculate(expression: str) -> str:
import ast
try:
# Use ast.literal_eval for safe evaluation
result = ast.literal_eval(expression)
return str(result)
except (ValueError, SyntaxError) as e:
return f"Error: {e}"
# Create agent with tools
agent = Agent(
model="claude-sonnet-4-20250514",
system_prompt="You are a helpful assistant with access to weather and math tools.",
tools=[get_weather, calculate],
max_turns=10
)
result = agent.run("What is the weather in Tokyo, and what is 15% of 230?")
print(result.output)TypeScript — Defining and Registering Tools
const getWeather = defineTool({
name: "get_weather",
description: "Get the current weather for a given city.",
parameters: {
type: "object",
properties: {
city: { type: "string", description: "The city name" },
},
required: ["city"],
},
execute: async ({ city }) => {
return `The weather in ${city} is 22C and sunny.`;
},
});
const calculate = defineTool({
name: "calculate",
description: "Evaluate a mathematical expression and return the result.",
parameters: {
type: "object",
properties: {
expression: { type: "string", description: "A math expression" },
},
required: ["expression"],
},
execute: async ({ expression }) => {
try {
// Parse and compute safely
const result = Number(expression);
return String(result);
} catch (e) {
return `Error: ${e}`;
}
},
});
const agent = new Agent({
model: "claude-sonnet-4-20250514",
systemPrompt: "You are a helpful assistant with weather and math tools.",
tools: [getWeather, calculate],
maxTurns: 10,
});
const result = await agent.run("What is the weather in Tokyo, and what is 15% of 230?");
console.log(result.output);The agent automatically decides when to use each tool, calls them, reads the results, and continues reasoning until it has a complete answer.
The Agent Loop
The agent loop is the core execution cycle of every agent. Understanding it is essential for debugging and optimizing your agents.
Here is how the loop works:
- Receive input — the user sends a message or the loop continues from a previous step
- Think — Claude processes the input and decides what to do next
- Act — if Claude decides to use a tool, the SDK executes the tool call automatically
- Observe — the tool result is fed back into the conversation
- Repeat — steps 2-4 repeat until Claude produces a final text response or the max turns limit is reached
Python — Observing the Agent Loop
from claude_agent_sdk import Agent, tool
@tool(name="search_docs", description="Search documentation.", parameters={
"query": {"type": "string", "description": "Search query"}
})
def search_docs(query: str) -> str:
return f"Found 3 results for '{query}': [Doc A, Doc B, Doc C]"
agent = Agent(
model="claude-sonnet-4-20250514",
system_prompt="You are a documentation assistant.",
tools=[search_docs],
max_turns=5,
verbose=True # Enables detailed logging of each loop iteration
)
result = agent.run("Find documentation about authentication")
# Access loop metadata
print(f"Total turns used: {result.turns_used}")
print(f"Tool calls made: {result.tool_calls_count}")
print(f"Tokens used: {result.total_tokens}")When verbose=True is set, you will see output like:
[Turn 1] User: Find documentation about authentication
[Turn 1] Assistant: I will search the docs for authentication.
[Turn 1] Tool call: search_docs(query="authentication")
[Turn 1] Tool result: Found 3 results for 'authentication': [Doc A, Doc B, Doc C]
[Turn 2] Assistant: I found 3 relevant documents about authentication...
[Loop complete] Final response delivered in 2 turns.
Memory Integration
By default, agents are stateless — each call to agent.run() starts a fresh conversation. To maintain context across interactions, you can use the SDK's memory providers.
Python — Conversation Memory
from claude_agent_sdk import Agent, ConversationMemory
# Create a memory provider
memory = ConversationMemory(max_messages=50)
agent = Agent(
model="claude-sonnet-4-20250514",
system_prompt="You are a helpful assistant. Remember previous conversations.",
memory=memory,
max_turns=5
)
# First interaction
result1 = agent.run("My name is Sarah and I work at Acme Corp.")
print(result1.output)
# Second interaction — the agent remembers
result2 = agent.run("What is my name and where do I work?")
print(result2.output)
# Output: Your name is Sarah and you work at Acme Corp.TypeScript — Conversation Memory
const memory = new ConversationMemory({ maxMessages: 50 });
const agent = new Agent({
model: "claude-sonnet-4-20250514",
systemPrompt: "You are a helpful assistant. Remember previous conversations.",
memory,
maxTurns: 5,
});
const result1 = await agent.run("My name is Sarah and I work at Acme Corp.");
console.log(result1.output);
const result2 = await agent.run("What is my name and where do I work?");
console.log(result2.output);
// Output: Your name is Sarah and you work at Acme Corp.Persistent Memory with File Storage
For production agents that need to persist memory across restarts:
from claude_agent_sdk import Agent, FileMemory
# Memory is saved to disk and survives restarts
memory = FileMemory(
path="./agent_memory.json",
max_messages=200
)
agent = Agent(
model="claude-sonnet-4-20250514",
system_prompt="You are a project manager assistant.",
memory=memory,
max_turns=10
)Multi-Turn Conversations
Multi-turn conversations allow you to have interactive, back-and-forth sessions with your agent — similar to a chat interface but with full tool access and memory.
Python — Interactive Multi-Turn Session
from claude_agent_sdk import Agent, ConversationMemory
agent = Agent(
model="claude-sonnet-4-20250514",
system_prompt="You are a code review assistant.",
memory=ConversationMemory(),
max_turns=5
)
# Simulate a multi-turn conversation
conversation = [
"Review this function: def add(a, b): return a + b",
"Can you suggest type hints for it?",
"Now write unit tests for the improved version.",
]
for message in conversation:
result = agent.run(message)
print(f"User: {message}")
print(f"Agent: {result.output}")
print("---")TypeScript — Streaming Multi-Turn Session
const agent = new Agent({
model: "claude-sonnet-4-20250514",
systemPrompt: "You are a code review assistant.",
memory: new ConversationMemory(),
maxTurns: 5,
});
const messages = [
"Review this function: function add(a, b) { return a + b; }",
"Can you suggest TypeScript types for it?",
"Now write unit tests for the improved version.",
];
for (const message of messages) {
const result = await agent.run(message, { stream: true });
process.stdout.write(`Agent: `);
for await (const chunk of result.stream) {
process.stdout.write(chunk);
}
console.log("\n---");
}Error Handling
Production agents must handle errors gracefully. The SDK provides multiple layers of error handling.
Python — Error Handling Strategies
from claude_agent_sdk import Agent, AgentError, ToolError, RateLimitError
agent = Agent(
model="claude-sonnet-4-20250514",
system_prompt="You are a data analyst.",
max_turns=10,
retry_config={
"max_retries": 3,
"retry_delay": 1.0, # seconds between retries
"backoff_factor": 2.0, # exponential backoff multiplier
"retryable_errors": [RateLimitError],
}
)
try:
result = agent.run("Analyze the Q4 sales data.")
print(result.output)
except RateLimitError as e:
print(f"Rate limited after retries: {e}")
print(f"Retry after: {e.retry_after} seconds")
except ToolError as e:
print(f"Tool execution failed: {e.tool_name} — {e.message}")
except AgentError as e:
print(f"Agent error: {e}")TypeScript — Error Handling Strategies
Agent,
AgentError,
ToolError,
RateLimitError,
} from "@anthropic-ai/agent-sdk";
const agent = new Agent({
model: "claude-sonnet-4-20250514",
systemPrompt: "You are a data analyst.",
maxTurns: 10,
retryConfig: {
maxRetries: 3,
retryDelay: 1000,
backoffFactor: 2.0,
retryableErrors: [RateLimitError],
},
});
try {
const result = await agent.run("Analyze the Q4 sales data.");
console.log(result.output);
} catch (error) {
if (error instanceof RateLimitError) {
console.error(`Rate limited. Retry after: ${error.retryAfter}s`);
} else if (error instanceof ToolError) {
console.error(`Tool ${error.toolName} failed: ${error.message}`);
} else if (error instanceof AgentError) {
console.error(`Agent error: ${error.message}`);
}
}Configuration Options
The Agent SDK provides a wide range of configuration options to control agent behavior.
| Option | Python | TypeScript | Default | Description |
|---|---|---|---|---|
| model | model | model | Required | The Claude model to use |
| system_prompt | system_prompt | systemPrompt | None | Instructions for the agent |
| tools | tools | tools | [] | List of tools the agent can use |
| max_turns | max_turns | maxTurns | 10 | Maximum loop iterations |
| memory | memory | memory | None | Memory provider for state persistence |
| max_tokens | max_tokens | maxTokens | 4096 | Maximum tokens per response |
| temperature | temperature | temperature | 1.0 | Response randomness (0.0 - 1.0) |
| verbose | verbose | verbose | False | Enable detailed logging |
| timeout | timeout | timeout | 120 | Timeout per API call in seconds |
| retry_config | retry_config | retryConfig | Default policy | Retry behavior for errors |
| stop_sequences | stop_sequences | stopSequences | [] | Custom stop sequences |
| metadata | metadata | metadata | {} | Custom metadata for tracking |
Full Working Example: Research Assistant Agent
Let us put everything together into a complete, working example — a research assistant that can search the web, take notes, and compile findings.
Python — Full Example
from claude_agent_sdk import Agent, tool, ConversationMemory, AgentError
# Define tools
@tool(
name="web_search",
description="Search the web for information on a topic.",
parameters={
"query": {"type": "string", "description": "The search query"}
}
)
def web_search(query: str) -> str:
# In production, integrate with a real search API
return f"Search results for '{query}': [Result 1: Overview of {query}, Result 2: Deep dive into {query}, Result 3: Latest news about {query}]"
@tool(
name="save_note",
description="Save a research note for later reference.",
parameters={
"title": {"type": "string", "description": "Note title"},
"content": {"type": "string", "description": "Note content"}
}
)
def save_note(title: str, content: str) -> str:
# In production, save to a database or file
print(f" [Note saved] {title}")
return f"Note '{title}' saved successfully."
@tool(
name="read_notes",
description="Read all saved research notes.",
parameters={}
)
def read_notes() -> str:
return "Notes: [1] Topic overview, [2] Key findings, [3] Open questions"
# Create the research assistant
research_agent = Agent(
model="claude-sonnet-4-20250514",
system_prompt="""You are a thorough research assistant. When given a topic:
1. Search for information using web_search
2. Save important findings as notes using save_note
3. Review your notes using read_notes
4. Compile a structured summary with key findings, sources, and open questions.
Always be thorough and cite your sources.""",
tools=[web_search, save_note, read_notes],
memory=ConversationMemory(max_messages=100),
max_turns=15,
max_tokens=4096,
temperature=0.7,
verbose=True,
retry_config={
"max_retries": 3,
"retry_delay": 1.0,
"backoff_factor": 2.0,
}
)
# Run the agent
try:
result = research_agent.run(
"Research the current state of quantum computing in 2025. "
"Focus on recent breakthroughs, major players, and practical applications."
)
print("\n=== Research Report ===")
print(result.output)
print(f"\nTurns used: {result.turns_used}")
print(f"Tool calls: {result.tool_calls_count}")
print(f"Total tokens: {result.total_tokens}")
except AgentError as e:
print(f"Research failed: {e}")TypeScript — Full Example
Agent,
defineTool,
ConversationMemory,
AgentError,
} from "@anthropic-ai/agent-sdk";
const webSearch = defineTool({
name: "web_search",
description: "Search the web for information on a topic.",
parameters: {
type: "object",
properties: {
query: { type: "string", description: "The search query" },
},
required: ["query"],
},
execute: async ({ query }) => {
return `Search results for '${query}': [Result 1, Result 2, Result 3]`;
},
});
const saveNote = defineTool({
name: "save_note",
description: "Save a research note for later reference.",
parameters: {
type: "object",
properties: {
title: { type: "string", description: "Note title" },
content: { type: "string", description: "Note content" },
},
required: ["title", "content"],
},
execute: async ({ title }) => {
console.log(` [Note saved] ${title}`);
return `Note '${title}' saved successfully.`;
},
});
const readNotes = defineTool({
name: "read_notes",
description: "Read all saved research notes.",
parameters: { type: "object", properties: {} },
execute: async () => {
return "Notes: [1] Topic overview, [2] Key findings, [3] Open questions";
},
});
const researchAgent = new Agent({
model: "claude-sonnet-4-20250514",
systemPrompt: `You are a thorough research assistant. When given a topic:
1. Search for information using web_search
2. Save important findings as notes using save_note
3. Review your notes using read_notes
4. Compile a structured summary.`,
tools: [webSearch, saveNote, readNotes],
memory: new ConversationMemory({ maxMessages: 100 }),
maxTurns: 15,
maxTokens: 4096,
temperature: 0.7,
verbose: true,
retryConfig: {
maxRetries: 3,
retryDelay: 1000,
backoffFactor: 2.0,
},
});
async function main() {
try {
const result = await researchAgent.run(
"Research the current state of quantum computing in 2025."
);
console.log("\n=== Research Report ===");
console.log(result.output);
console.log(`Turns used: ${result.turnsUsed}`);
console.log(`Tool calls: ${result.toolCallsCount}`);
} catch (error) {
if (error instanceof AgentError) {
console.error(`Research failed: ${error.message}`);
}
}
}
main();Key Takeaways
- The Claude Agent SDK is the official framework for building agents — it handles the loop, tools, memory, and errors for you
- Installation is straightforward with pip or npm — just set your API key and you are ready
- Tools are simple functions you register with the agent — the SDK handles calling them automatically
- The agent loop (think, act, observe, repeat) runs until the agent has a final answer or hits the turn limit
- Memory providers let you maintain context across conversations — in-memory for development, file-based for production
- Error handling is built in with configurable retry policies and typed exceptions
- Start simple (basic agent, no tools) and add capabilities incrementally — tools, then memory, then error handling