Skip to main content

Claude Agent SDK — Build Production AI Agents

Use the Python and TypeScript SDKs to build, deploy, and monitor autonomous AI agents

40 minutes
8 min read
Updated March 28, 2026

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.

Where the Agent SDK Fits
Claude Code is for developers at a terminal. The Agent SDK is for agents running in your software.

What Is the Agent SDK?

The Agent SDK is available in two languages:

LanguagePackageLatest VersionInstall Command
Pythonclaude-agent-sdkv0.1.48pip install claude-agent-sdk
TypeScript@anthropic-ai/claude-agent-sdkv0.2.71npm 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.

ScenarioUseWhy
Interactive coding at a terminalClaude CodeBuilt for human developers — approval flows, undo, conversation
Automated CI/CD pipelineAgent SDKNo human present — needs programmatic control and error handling
Code review bot on PRsAgent SDKTriggered by webhook, runs unattended, posts results to GitHub
Production SaaS featureAgent SDKEmbedded in your app, serves many users, needs rate limit management
Exploring a new codebaseClaude CodeConversational, iterative — you steer the exploration
Batch processing 100 reposAgent SDKParallelism, retries, structured output — all programmatic

Getting Started

Prerequisites

You need an Anthropic API key. Set it as an environment variable:

Bash
export ANTHROPIC_API_KEY=your-key-here

Installation

Bash
pip install claude-agent-sdk

Requires Python 3.10 or later.

Hello World

The simplest possible agent: send a task, get a result.

Python
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.

The Agent Loop
The agent repeats this cycle until it has a final answer or hits a limit.
  1. 1

    Receive Task

    Your code calls agent.run() with a natural-language task.

  2. 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. 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. 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:

ToolPurposeExample Use
ReadRead file contentsInspect source code, config files, logs
WriteCreate or overwrite filesGenerate new files, rewrite configs
EditMake targeted edits to filesFix a bug, update a function
BashRun shell commandsInstall packages, run tests, git operations
GrepSearch file contents with regexFind all usages of a function
GlobFind files by name patternLocate all TypeScript test files

You can also restrict which tools an agent has access to:

Python
# Read-only agent — cannot modify files or run commands
agent = 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.

Python
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
)

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

HookWhen It FiresCommon Use Cases
PreToolUseBefore a tool executesBlock dangerous commands, validate inputs, add logging
PostToolUseAfter a tool returnsInspect results, trigger side effects, redact sensitive data
PreMessageBefore each LLM callInject context, modify prompts, enforce guardrails
PostMessageAfter each LLM responseLog responses, check for hallucinations, accumulate metrics

Hook Evaluation Modes

Hooks can run in four modes, depending on how sophisticated your validation needs to be:

  1. Inline function — A synchronous function that returns allow/block. Fast, no extra API calls.
  2. 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.
  3. 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.
  4. 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:

Python
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.

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:

Python
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
Bash
pip install claude-agent-sdk python-dotenv

The Complete Agent

Python
import os
import json
from dataclasses import dataclass
from claude_agent_sdk import Agent, Hook, HookAction
@dataclass
class 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 diff
to review. Your job:
1. Use Bash to run `git diff` and get the changes
2. Use Read and Grep to understand the context around each change
3. 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

Bash
## 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.

Deploying Agents

Running as a Service

Wrap your agent in an HTTP server for production use:

Python
from fastapi import FastAPI, BackgroundTasks
from 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

Python
from claude_agent_sdk import Agent, AgentError, RateLimitError, ContextOverflowError
import 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.

WindowLimit TypeStrategy
5-hour rollingRequests and tokensExponential backoff with jitter. Spread batch jobs over time.
7-day rollingTotal spend / usageMonitor 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:

Python
# 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, latency

This 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:

  1. Define the task as a single sentence. If you cannot, split across multiple agents (Multi-Agent Architectures).
  2. Start with minimal tools. Fewer tools means fewer decisions for the agent and more predictable behavior.
  3. Add safety hooks before deploying. Non-negotiable for any agent that runs Bash or writes files.
  4. Handle failures — retry logic, rate limit backoff, context overflow recovery.
  5. 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:

Share this article