Skip to content

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.


ScenarioUse
Interactive developmentClaude Code CLI
CI/CD pipelinesAgent SDK
Custom applicationsAgent SDK
One-off tasksCLI
Production automationAgent 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 loop
response = 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 autonomously
async for message in query(prompt="Fix the bug in auth.py"):
print(message)

Terminal window
# Python (requires Python 3.10+)
pip install claude-agent-sdk
# TypeScript / Node.js
npm install @anthropic-ai/claude-agent-sdk

The TypeScript SDK bundles a native Claude Code binary for your platform — no separate install needed.


Terminal window
export ANTHROPIC_API_KEY=your-api-key

Also supports Bedrock, Vertex AI, Azure (set environment flags before running).


# Python
import asyncio
from 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())
// TypeScript
import { 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);
}

ToolWhat it does
ReadRead any file in the working directory
WriteCreate new files
EditMake precise edits to existing files
BashRun terminal commands, scripts, git operations
MonitorWatch a background script, react to each output line
GlobFind files by pattern (**/*.ts, src/**/*.py)
GrepSearch file contents with regex
WebSearchSearch the web for current information
WebFetchFetch and parse web page content
AskUserQuestionAsk the user clarifying questions

# Read-only agent — can analyze but not modify
options=ClaudeAgentOptions(
allowed_tools=["Read", "Glob", "Grep"]
)
options: { allowedTools: ["Read", "Glob", "Grep"] }

Hooks let you run custom code at key points in the agent lifecycle:

from claude_agent_sdk import query, ClaudeAgentOptions, HookMatcher
from 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


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

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

from claude_agent_sdk import query, ClaudeAgentOptions, SystemMessage
session_id = None
# First query
async 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 context
async 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)

Get JSON instead of text:

from pydantic import BaseModel
from 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 SDKClient SDK (raw API)Managed Agents
Runs inYour processYour processAnthropic cloud
Tool loopHandled by SDKYou implement itAnthropic handles it
InterfacePython/TypeScript libraryPython/TypeScript/HTTPREST API
FilesYour filesystemYour filesystemManaged sandbox
Best forLocal automation, CI/CDFull API controlProduction, long-running

import asyncio
from 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'"))

Terminal window
# Amazon Bedrock
export CLAUDE_CODE_USE_BEDROCK=1
# + configure AWS credentials
# Google Vertex AI
export CLAUDE_CODE_USE_VERTEX=1
# + configure Google Cloud credentials
# Azure AI Foundry
export CLAUDE_CODE_USE_FOUNDRY=1
# + configure Azure credentials

  • Trick: “The SDK is Claude Code minus the terminal — same loop, your rules.”
  • 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