Claude Agent SDK — Build Production AI Agents
Use the Python and TypeScript SDKs to build, deploy, and monitor autonomous AI agents
Claude Agent SDK
The Agent SDK lets you embed autonomous Claude-powered agents directly into your own applications, services, and CI/CD pipelines. Unlike Claude Code (the interactive CLI), the SDK is a library you import into your code and ship as part of your product.
Info
Formerly called the "Claude Code SDK," the package was renamed in late 2025 to Agent SDK to reflect its general-purpose agent runtime capabilities. If you see older docs referencing claude-code-sdk, they refer to the same project.
What Is the Agent SDK?
The Agent SDK is available in two languages:
| Language | Package | Latest Version | Install Command |
|---|---|---|---|
| Python | claude-agent-sdk | v0.1.48 | pip install claude-agent-sdk |
| TypeScript | @anthropic-ai/claude-agent-sdk | v0.2.71 | npm install @anthropic-ai/claude-agent-sdk |
Both packages provide the same core capabilities:
- Agent loop — Reason, decide, execute tools, observe results, repeat
- Built-in tools — Read, Edit, Bash, Grep, Glob, Write (the same tools Claude Code uses)
- Hooks — Intercept and control agent behavior at every step
- Context management — Handle long conversations with compaction strategies
- Observability — OpenTelemetry attributes for monitoring
When to Use the SDK vs. Claude Code
This is the most common question. The answer depends on who (or what) is driving the agent.
| Scenario | Use | Why |
|---|---|---|
| Interactive coding at a terminal | Claude Code | Built for human developers — approval flows, undo, conversation |
| Automated CI/CD pipeline | Agent SDK | No human present — needs programmatic control and error handling |
| Code review bot on PRs | Agent SDK | Triggered by webhook, runs unattended, posts results to GitHub |
| Production SaaS feature | Agent SDK | Embedded in your app, serves many users, needs rate limit management |
| Exploring a new codebase | Claude Code | Conversational, iterative — you steer the exploration |
| Batch processing 100 repos | Agent SDK | Parallelism, retries, structured output — all programmatic |
Pro Tip
Rule of thumb: if a human is watching and guiding, use Claude Code. If code is orchestrating the agent, use the SDK.
Getting Started
Prerequisites
You need an Anthropic API key. Set it as an environment variable:
export ANTHROPIC_API_KEY=your-key-hereInstallation
pip install claude-agent-sdkRequires Python 3.10 or later.
Hello World
The simplest possible agent: send a task, get a result.
from claude_agent_sdk import Agent agent = Agent() result = agent.run("What files are in the current directory? List them.")print(result.output)That is the entire program. The SDK handles the agent loop internally — reasoning, tool selection, execution, and observation all happen behind agent.run().
Core Concepts
The Agent Loop
Every SDK agent follows the same cycle. Understanding it is essential.
- 1
Receive Task
Your code calls
agent.run()with a natural-language task. - 2
Reason and Select Tool
Claude analyzes the task and accumulated context. If it needs more information or must take an action, it selects a tool and generates arguments.
- 3
Execute and Observe
The SDK executes the tool locally (file reads, shell commands, searches) and feeds the result back into context. The cycle repeats from Step 2.
- 4
Return Final Output
When the agent has enough information, it produces a text response.
agent.run()returns this to your code.
Built-in Tools
SDK agents have access to the same tools Claude Code uses:
| Tool | Purpose | Example Use |
|---|---|---|
| Read | Read file contents | Inspect source code, config files, logs |
| Write | Create or overwrite files | Generate new files, rewrite configs |
| Edit | Make targeted edits to files | Fix a bug, update a function |
| Bash | Run shell commands | Install packages, run tests, git operations |
| Grep | Search file contents with regex | Find all usages of a function |
| Glob | Find files by name pattern | Locate all TypeScript test files |
You can also restrict which tools an agent has access to:
# Read-only agent — cannot modify files or run commandsagent = Agent(allowed_tools=["Read", "Grep", "Glob"])Context Management
Every reasoning step, tool call, and result accumulates in the message history. The SDK tracks token usage automatically and supports compaction — summarizing older messages when context nears the limit.
agent = Agent( system_prompt="You are a security auditor. Review code for vulnerabilities.", max_turns=50, # Stop after 50 tool-use cycles max_tokens=128_000, # Context budget compaction_strategy="summarize", # Summarize old context when near limit)Warning
Watch your token usage. Each tool call and result adds to the context. A Bash command that dumps a 10,000-line log file will consume a large chunk of your budget. Design your agents to read only what they need.
Hooks
Hooks are the SDK's control mechanism. They let you intercept, validate, and modify agent behavior at every step — without changing the agent's core logic.
Hook Types
| Hook | When It Fires | Common Use Cases |
|---|---|---|
| PreToolUse | Before a tool executes | Block dangerous commands, validate inputs, add logging |
| PostToolUse | After a tool returns | Inspect results, trigger side effects, redact sensitive data |
| PreMessage | Before each LLM call | Inject context, modify prompts, enforce guardrails |
| PostMessage | After each LLM response | Log responses, check for hallucinations, accumulate metrics |
Hook Evaluation Modes
Hooks can run in four modes, depending on how sophisticated your validation needs to be:
- Inline function — A synchronous function that returns allow/block. Fast, no extra API calls.
- Prompt hook — A single-turn LLM evaluation. The hook sends the pending action to Claude with a validation prompt and gets back a pass/fail judgment.
- Agent hook — A multi-turn verification with full tool access. The hook spins up a mini-agent that can read files, run commands, and reason before deciding.
- HTTP hook — POSTs the pending action to an external URL. Your server responds with allow/block. Useful for centralized policy enforcement.
Practical Example: Safety Guard
This hook blocks any Bash command that contains destructive patterns:
from claude_agent_sdk import Agent, Hook, HookAction BLOCKED_PATTERNS = [ "rm -rf /", "rm -rf ~", "mkfs.", ":(){:|:&};:", "> /dev/sda", "dd if=/dev/zero",] def safety_guard(tool_name: str, tool_input: dict) -> HookAction: """Block dangerous shell commands before they execute.""" if tool_name != "Bash": return HookAction.ALLOW command = tool_input.get("command", "") for pattern in BLOCKED_PATTERNS: if pattern in command: return HookAction.BLOCK( reason=f"Blocked dangerous command containing '{pattern}'" ) return HookAction.ALLOW agent = Agent( hooks=[ Hook(event="PreToolUse", handler=safety_guard), ]) result = agent.run("Delete everything on the system")# The agent will attempt rm -rf /, the hook will block it,# and the agent will see the block reason in its context.Pro Tip
Hooks compose. You can attach multiple hooks to the same event — they run in order, and the first BLOCK wins. Layer a fast pattern-match hook first, then a slower LLM-based check only if the fast one passes. For HTTP hooks, the SDK can POST pending actions to an external URL for centralized policy enforcement.
For more on how hooks work inside Claude Code itself, see Claude Code Best Practices.
Custom Tools
The built-in tools cover file and shell operations. For anything else — database queries, API calls, message sending — you define custom tools:
from claude_agent_sdk import Agent, Tool def query_database(sql: str) -> str: """Execute a read-only SQL query and return results as CSV.""" import sqlite3 conn = sqlite3.connect("app.db") cursor = conn.execute(sql) rows = cursor.fetchall() headers = [desc[0] for desc in cursor.description] conn.close() lines = [",".join(headers)] for row in rows: lines.append(",".join(str(v) for v in row)) return "\n".join(lines) db_tool = Tool( name="query_database", description="Run a read-only SQL query against the application database", input_schema={ "type": "object", "properties": { "sql": {"type": "string", "description": "SQL SELECT query to execute"} }, "required": ["sql"] }, handler=query_database,) agent = Agent(custom_tools=[db_tool])result = agent.run("How many users signed up in the last 7 days?")Custom tools follow the same schema format as the Anthropic API's tool definitions. The handler function receives the parsed input and returns a string result.
Example: Build a Code Review Agent
Let's build something real. This agent reads a git diff, analyzes code quality, and produces a structured review.
Project Setup
- reviewer.py
- requirements.txt
- .env
pip install claude-agent-sdk python-dotenvThe Complete Agent
import osimport jsonfrom dataclasses import dataclassfrom claude_agent_sdk import Agent, Hook, HookAction @dataclassclass ReviewComment: file: str line: int severity: str # "critical", "warning", "suggestion" message: str class CodeReviewAgent: """An agent that reviews git diffs and produces structured feedback.""" SYSTEM_PROMPT = """You are a senior code reviewer. You will be given a git diffto review. Your job: 1. Use Bash to run `git diff` and get the changes2. Use Read and Grep to understand the context around each change3. Identify issues in these categories: - Bugs or logic errors (critical) - Performance problems (warning) - Style and readability (suggestion) - Missing error handling (warning) - Security concerns (critical) 4. Produce your review as a JSON array of objects with these fields: - file: the file path - line: the approximate line number - severity: "critical", "warning", or "suggestion" - message: a clear explanation of the issue and how to fix it Return ONLY the JSON array, no other text.""" def __init__(self): self.agent = Agent( system_prompt=self.SYSTEM_PROMPT, allowed_tools=["Read", "Grep", "Glob", "Bash"], max_turns=30, hooks=[ Hook(event="PreToolUse", handler=self._restrict_bash), ], ) def _restrict_bash(self, tool_name: str, tool_input: dict) -> HookAction: """Only allow read-only bash commands.""" if tool_name != "Bash": return HookAction.ALLOW command = tool_input.get("command", "") allowed_prefixes = ["git diff", "git log", "git show", "git status", "cat ", "wc "] if any(command.strip().startswith(p) for p in allowed_prefixes): return HookAction.ALLOW return HookAction.BLOCK( reason=f"Only git and read-only commands are allowed. Got: {command}" ) def review(self, diff_target: str = "HEAD~1") -> list[ReviewComment]: """Review changes in the given diff range.""" result = self.agent.run( f"Review the code changes in `git diff {diff_target}`. " f"Focus on bugs, security issues, and maintainability." ) # Parse structured output try: comments_data = json.loads(result.output) return [ReviewComment(**c) for c in comments_data] except (json.JSONDecodeError, TypeError) as e: print(f"Warning: Could not parse structured output: {e}") print(f"Raw output:\n{result.output}") return [] def format_review(self, comments: list[ReviewComment]) -> str: """Format review comments grouped by severity.""" if not comments: return "No issues found." lines = [f"## Code Review — {len(comments)} issue(s)\n"] for sev in ["critical", "warning", "suggestion"]: group = [c for c in comments if c.severity == sev] if group: lines.append(f"\n### [{sev.upper()}] ({len(group)})\n") for c in group: lines.append(f"**{c.file}:{c.line}** — {c.message}\n") return "\n".join(lines) if __name__ == "__main__": reviewer = CodeReviewAgent() comments = reviewer.review("HEAD~1") print(reviewer.format_review(comments))Sample Output
## Code Review — 3 issue(s) found ### [CRITICAL] (1) **src/auth.py:42** SQL query built with string concatenation is vulnerable to injection. Use parameterized queries: `cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))` ### [WARNING] (1) **src/api/routes.py:118** The `except Exception` clause silently swallows all errors. At minimum, log the exception. Consider catching specific exception types. ### [SUGGESTION] (1) **src/utils/helpers.py:7** Function `do_stuff` is 85 lines long with 6 levels of nesting. Extract the inner logic into well-named helper functions.Success
This agent is production-viable. Add it to a GitHub Actions workflow, trigger it on pull requests, and post the review as PR comments. The hook system ensures it can only run read-only commands.
Deploying Agents
Running as a Service
Wrap your agent in an HTTP server for production use:
from fastapi import FastAPI, BackgroundTasksfrom claude_agent_sdk import Agent app = FastAPI() @app.post("/review")async def review_endpoint(repo_url: str, diff_ref: str, background_tasks: BackgroundTasks): """Trigger a code review. Returns immediately; posts results when done.""" background_tasks.add_task(run_review, repo_url, diff_ref) return {"status": "queued"} async def run_review(repo_url: str, diff_ref: str): agent = Agent(system_prompt="You are a code reviewer...", max_turns=30) result = agent.run(f"Clone {repo_url} and review changes in {diff_ref}") post_results(result.output) # Post to GitHub, Slack, etc.Error Handling and Retries
from claude_agent_sdk import Agent, AgentError, RateLimitError, ContextOverflowErrorimport time def run_with_resilience(task: str, max_retries: int = 3) -> str: for attempt in range(max_retries): try: agent = Agent(max_turns=50) return agent.run(task).output except RateLimitError: wait = min(2 ** attempt * 10, 120) print(f"Rate limited. Waiting {wait}s... (attempt {attempt + 1})") time.sleep(wait) except ContextOverflowError: return Agent(max_turns=50, compaction_strategy="summarize").run(task).output except AgentError as e: if attempt == max_retries - 1: raise raise RuntimeError("Max retries exceeded")Rate Limits
The Anthropic API enforces rate limits at two windows. Each agent.run() may make many API requests internally (one per loop iteration), so a 30-turn agent run could consume 30+ API calls.
| Window | Limit Type | Strategy |
|---|---|---|
| 5-hour rolling | Requests and tokens | Exponential backoff with jitter. Spread batch jobs over time. |
| 7-day rolling | Total spend / usage | Monitor cumulative usage. Alert at 80% threshold. Queue non-urgent work. |
Monitoring with OpenTelemetry
The SDK emits OTel spans automatically. Set up your preferred exporter and every agent run produces structured traces:
# Spans emitted by the SDK:# agent.run — total duration, final status# agent.turn — each reasoning + tool cycle# agent.tool_call — tool name, input/output size, duration# agent.llm_call — model, tokens in/out, latencyThis gives you dashboards showing agent duration, tool usage patterns, token consumption, and failure rates.
Putting It All Together
A checklist for your next agent project:
- Define the task as a single sentence. If you cannot, split across multiple agents (Multi-Agent Architectures).
- Start with minimal tools. Fewer tools means fewer decisions for the agent and more predictable behavior.
- Add safety hooks before deploying. Non-negotiable for any agent that runs Bash or writes files.
- Handle failures — retry logic, rate limit backoff, context overflow recovery.
- Monitor with OTel. Watch tool usage, token consumption, and failure rates. Refine based on real data.
Next Steps
Now that you understand the Agent SDK:
- Building Agents — Learn the fundamentals of agent design with the raw Claude API before reaching for the SDK
- Multi-Agent Architectures — Orchestrate multiple SDK agents as subagents and swarms
- Claude Code Best Practices — Hooks, safety patterns, and workflow techniques that apply to both Claude Code and SDK agents
Success
Start small. Build a single-purpose agent that solves one problem in your workflow — a test runner, a log analyzer, a doc generator. Get it working end-to-end, then add hooks, monitoring, and error handling. The SDK makes it straightforward to grow from prototype to production.