Agent SDK — Build Custom Agents Programmatically
The Agent SDK lets you build production AI agents using Claude Code as a library in your own Python or TypeScript code. Same tools, same agent loop, same capabilities as the CLI — but fully programmable.
Why Use the Agent SDK?
Section titled “Why Use the Agent SDK?”| Scenario | Use |
|---|---|
| Interactive development | Claude Code CLI |
| CI/CD pipelines | Agent SDK |
| Custom applications | Agent SDK |
| One-off tasks | CLI |
| Production automation | Agent SDK |
The key difference from the raw Anthropic API: you don’t implement the tool loop. The SDK handles file reading, command execution, and result passing automatically.
# Raw Anthropic API — you implement the tool loopresponse = client.messages.create(...)while response.stop_reason == "tool_use": result = your_tool_executor(response.tool_use) response = client.messages.create(tool_result=result, **params)
# Agent SDK — Claude handles tools autonomouslyasync for message in query(prompt="Fix the bug in auth.py"): print(message)Installation
Section titled “Installation”# Python (requires Python 3.10+)pip install claude-agent-sdk
# TypeScript / Node.jsnpm install @anthropic-ai/claude-agent-sdkThe TypeScript SDK bundles a native Claude Code binary for your platform — no separate install needed.
Authentication
Section titled “Authentication”export ANTHROPIC_API_KEY=your-api-keyAlso supports Bedrock, Vertex AI, Azure (set environment flags before running).
Hello World
Section titled “Hello World”# Pythonimport asynciofrom claude_agent_sdk import query, ClaudeAgentOptions
async def main(): async for message in query( prompt="What files are in this directory?", options=ClaudeAgentOptions(allowed_tools=["Bash", "Glob"]), ): if hasattr(message, "result"): print(message.result)
asyncio.run(main())// TypeScriptimport { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({ prompt: "What files are in this directory?", options: { allowedTools: ["Bash", "Glob"] }})) { if ("result" in message) console.log(message.result);}Built-in Tools
Section titled “Built-in Tools”| Tool | What it does |
|---|---|
Read | Read any file in the working directory |
Write | Create new files |
Edit | Make precise edits to existing files |
Bash | Run terminal commands, scripts, git operations |
Monitor | Watch a background script, react to each output line |
Glob | Find files by pattern (**/*.ts, src/**/*.py) |
Grep | Search file contents with regex |
WebSearch | Search the web for current information |
WebFetch | Fetch and parse web page content |
AskUserQuestion | Ask the user clarifying questions |
Restrict Tools (Permissions)
Section titled “Restrict Tools (Permissions)”# Read-only agent — can analyze but not modifyoptions=ClaudeAgentOptions( allowed_tools=["Read", "Glob", "Grep"])options: { allowedTools: ["Read", "Glob", "Grep"] }Add Hooks (Lifecycle Events)
Section titled “Add Hooks (Lifecycle Events)”Hooks let you run custom code at key points in the agent lifecycle:
from claude_agent_sdk import query, ClaudeAgentOptions, HookMatcherfrom datetime import datetime
async def log_file_change(input_data, tool_use_id, context): file_path = input_data.get("tool_input", {}).get("file_path", "unknown") with open("./audit.log", "a") as f: f.write(f"{datetime.now()}: modified {file_path}\n") return {}
options = ClaudeAgentOptions( permission_mode="acceptEdits", hooks={ "PostToolUse": [ HookMatcher(matcher="Edit|Write", hooks=[log_file_change]) ] })Available hooks: PreToolUse, PostToolUse, Stop, SessionStart, SessionEnd, UserPromptSubmit
Define Custom Subagents
Section titled “Define Custom Subagents”from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
options = ClaudeAgentOptions( allowed_tools=["Read", "Glob", "Grep", "Agent"], # include Agent to enable subagents agents={ "code-reviewer": AgentDefinition( description="Expert code reviewer for quality and security reviews.", prompt="Analyze code quality and suggest improvements.", tools=["Read", "Glob", "Grep"], ) })
async for message in query( prompt="Use the code-reviewer agent to review this codebase", options=options): ...Connect MCP Servers
Section titled “Connect MCP Servers”options = ClaudeAgentOptions( mcp_servers={ "playwright": {"command": "npx", "args": ["@playwright/mcp@latest"]} })
async for message in query( prompt="Open example.com and describe what you see", options=options): ...Session Management (Resume Across Calls)
Section titled “Session Management (Resume Across Calls)”from claude_agent_sdk import query, ClaudeAgentOptions, SystemMessage
session_id = None
# First queryasync for message in query( prompt="Read the authentication module", options=ClaudeAgentOptions(allowed_tools=["Read", "Glob"]),): if isinstance(message, SystemMessage) and message.subtype == "init": session_id = message.data["session_id"]
# Resume with full contextasync for message in query( prompt="Now find all places that call it", # "it" = auth module from previous query options=ClaudeAgentOptions(resume=session_id),): if hasattr(message, "result"): print(message.result)Structured Output
Section titled “Structured Output”Get JSON instead of text:
from pydantic import BaseModelfrom claude_agent_sdk import query, ClaudeAgentOptions
class BugReport(BaseModel): file: str line: int description: str fix: str
options = ClaudeAgentOptions( output_schema=BugReport.model_json_schema())Agent SDK vs Other Approaches
Section titled “Agent SDK vs Other Approaches”| Agent SDK | Client SDK (raw API) | Managed Agents | |
|---|---|---|---|
| Runs in | Your process | Your process | Anthropic cloud |
| Tool loop | Handled by SDK | You implement it | Anthropic handles it |
| Interface | Python/TypeScript library | Python/TypeScript/HTTP | REST API |
| Files | Your filesystem | Your filesystem | Managed sandbox |
| Best for | Local automation, CI/CD | Full API control | Production, long-running |
Real Example: Bug Fixer Agent
Section titled “Real Example: Bug Fixer Agent”import asynciofrom claude_agent_sdk import query, ClaudeAgentOptions
async def fix_bug(file_path: str, error_message: str): prompt = f""" Fix the bug in {file_path}.
Error: {error_message}
Steps: 1. Read the file 2. Find the bug 3. Fix it 4. Run the tests to verify """
async for message in query( prompt=prompt, options=ClaudeAgentOptions( allowed_tools=["Read", "Edit", "Bash"], permission_mode="acceptEdits", ), ): if hasattr(message, "result"): print(f"Done: {message.result}") elif hasattr(message, "type") and message.type == "assistant": # Stream assistant messages as they arrive print(".", end="", flush=True)
asyncio.run(fix_bug("auth.py", "AttributeError: 'NoneType' object has no attribute 'user_id'"))Cloud Provider Support
Section titled “Cloud Provider Support”# Amazon Bedrockexport CLAUDE_CODE_USE_BEDROCK=1# + configure AWS credentials
# Google Vertex AIexport CLAUDE_CODE_USE_VERTEX=1# + configure Google Cloud credentials
# Azure AI Foundryexport CLAUDE_CODE_USE_FOUNDRY=1# + configure Azure credentialsKey Resources
Section titled “Key Resources”🧠 Quick Recall
Section titled “🧠 Quick Recall”- Trick: “The SDK is Claude Code minus the terminal — same loop, your rules.”
👨🏫 Teach It
Section titled “👨🏫 Teach It”- Show: the 30-line quickstart with one custom tool; print the cost at the end.
- They’ll trip on: assuming CLI settings carry over — the SDK configures permissions explicitly.
Learning path: ⬅ 39-slack-chrome-channels.md · Index · ➡ 41-cost-management.md
Written by Fenil Patel