The complete guide to Claude Code. Opus 4.7, Sonnet 4.6, Haiku 4.5. 1M token context window. 27 hook events. 43 production-tested chapters across 6 topical Parts (Foundation, Workflow, Extension, Context Engineering, Advanced, Reference). Three install tiers. CC 2.1.121+ compatible.
Part III — Extension · Chapter 3b
The Claude Agent SDK lets you build custom agents programmatically, embedding Claude Code’s tool loop, permission system, and context management into your own applications. Available as @anthropic-ai/claude-agent-sdk for TypeScript and claude-agent-sdk for Python.
Note on naming: The SDK was previously called the “Claude Code SDK” (@anthropic-ai/claude-code-sdk). It is now the Claude Agent SDK. If you’re migrating older code, the package name, imports, and some option keys changed. The core model — agent loop, tools, hooks — is the same.
This chapter covers what the SDK is, how it differs from the CLI, the key configuration surfaces, and the patterns that work in production.
| Aspect | CLI agents (.claude/agents/) |
SDK (claude-agent-sdk) |
|---|---|---|
| Definition | Markdown files with frontmatter | Code — TS or Python |
| Execution | Via Task inside Claude Code |
Via your own application |
| Configuration | YAML frontmatter | Programmatic options object |
| Tool control | tools: field |
allowedTools / disallowedTools |
| Context | Inherits from parent session | Fully configurable |
| Use case | Extend Claude Code workflows | Build standalone AI apps, CI/CD |
Use the SDK when you’re building your own application, wiring agents into CI/CD, or running custom workflows outside Claude Code. Use CLI agents when you’re extending Claude Code’s interactive workflow.
Many teams use both: CLI for daily development, SDK for production automation. Patterns translate directly.
# TypeScript
npm install @anthropic-ai/claude-agent-sdk
# Python
pip install claude-agent-sdk
The TypeScript SDK bundles a native Claude Code binary for your platform as an optional dependency, so you don’t need to install Claude Code separately.
export ANTHROPIC_API_KEY=your-api-key
The SDK also supports third-party API providers:
CLAUDE_CODE_USE_BEDROCK=1CLAUDE_CODE_USE_VERTEX=1CLAUDE_CODE_USE_FOUNDRY=1Configure the platform credentials separately per their own auth flows.
The canonical entry point is query() — an async iterator that streams messages as the agent works.
Python:
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions
async def main():
async for message in query(
prompt="Find and fix the bug in auth.py",
options=ClaudeAgentOptions(allowed_tools=["Read", "Edit", "Bash"]),
):
print(message)
asyncio.run(main())
TypeScript:
import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({
prompt: "Find and fix the bug in auth.ts",
options: { allowedTools: ["Read", "Edit", "Bash"] }
})) {
console.log(message);
}
The SDK handles the tool loop internally — Claude reads files, runs commands, and edits code without you implementing tool dispatch. Messages stream as Claude works: tool calls, tool results, intermediate reasoning, and the final result.
Each query() invocation starts fresh. No memory of previous calls. Fastest, cheapest, and easiest to reason about.
Use for: one-off tasks, CI/CD steps, isolated operations, anything where each invocation should be independent.
Capture the session_id from the first query and resume to continue with full context:
import { query } from "@anthropic-ai/claude-agent-sdk";
let sessionId: string | undefined;
// First query — capture session ID from the init message
for await (const message of query({
prompt: "Read the authentication module",
options: { allowedTools: ["Read", "Glob"] }
})) {
if (message.type === "system" && message.subtype === "init") {
sessionId = message.session_id;
}
}
// Resume with full prior context
for await (const message of query({
prompt: "Now find all places that call it",
options: { resume: sessionId }
})) {
if ("result" in message) console.log(message.result);
}
Sessions preserve files read, prior analysis, and conversation history. They can also be forked to explore alternative approaches without losing the baseline.
Use for: long-running workflows, interactive assistants, multi-step pipelines where later steps need earlier context.
The SDK has three tool permission mechanisms. They differ meaningfully from CLI agent frontmatter, and mixing them up is the most common SDK configuration bug.
allowedTools — auto-approveTools in this list run without asking for permission. Other tools remain available but will prompt (or be denied, depending on permissionMode).
options: { allowedTools: ["Read", "Grep", "Glob"] }
disallowedTools — remove entirelyTools in this list are removed from the agent’s context. Claude can’t see or use them at all. Security-critical agents use this to ensure commands or file writes are literally impossible.
options: { disallowedTools: ["Bash", "Write"] }
In CLI frontmatter, tools: [...] restricts to the listed tools — everything else is unavailable. In the SDK, allowedTools auto-approves but does NOT restrict — other tools remain available. To truly restrict in the SDK, use disallowedTools for the tools you want to block.
| Mechanism | Tools visible? | User prompt? | Use for |
|---|---|---|---|
allowedTools |
All | No (listed) | Auto-approve safe tools |
disallowedTools |
All minus list | N/A | Remove dangerous tools |
permissionMode: "bypassPermissions" |
All | No (any) | Fully trusted automation |
Control how the agent handles permission requests.
options: { permissionMode: "acceptEdits" }
| Mode | Behavior | Use case |
|---|---|---|
default |
Prompts for dangerous operations | Interactive use |
acceptEdits |
Auto-accepts file edits, prompts others | Code-writing agents |
plan |
Read-only explore, writes a plan file for approval | Pre-flight review |
bypassPermissions |
Auto-approves everything | Trusted environments only |
For background agents that can’t prompt a human, either pre-approve via allowedTools or accept specific categories via acceptEdits. Avoid bypassPermissions for anything touching external systems — no recovery from mistakes.
By default the SDK loads Claude Code’s filesystem config from .claude/ in your working directory and ~/.claude/. This includes CLAUDE.md, rules, skills, slash commands, and agent definitions — the SDK agent gets the same context as a CLI session.
To restrict which sources load:
Python:
options = ClaudeAgentOptions(
setting_sources=["project", "user"], # exclude enterprise
)
TypeScript:
options: {
settingSources: ["project", "user"]
}
When the same setting exists at multiple levels, higher priority wins:
.claude/ in the working directory~/.claude/Matches CLI behavior where project rules override user rules.
| Feature | Location | Description |
|---|---|---|
| Skills | .claude/skills/*/SKILL.md |
Specialized capabilities in markdown |
| Slash commands | .claude/commands/*.md |
Custom commands for common tasks |
| Memory | CLAUDE.md or .claude/CLAUDE.md |
Project context |
| Plugins | Programmatic via plugins option |
Bundles of commands, agents, MCP servers |
SDK agents connect to MCP servers the same way CLI sessions do — pass an mcpServers map in options.
import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({
prompt: "Open example.com and describe what you see",
options: {
mcpServers: {
playwright: { command: "npx", args: ["@playwright/mcp@latest"] }
}
}
})) {
console.log(message);
}
Tools from MCP servers become available to the agent alongside the built-ins. Full MCP coverage is in chapter 2 of this Part — the SDK integration is mostly about passing the same server config programmatically instead of through settings.json.
Use plan mode for complex tasks where you want to review the approach before execution.
options: { permissionMode: "plan" }
In plan mode the agent:
When to use: multi-file changes, architectural decisions, anything where a wrong turn costs more than the plan review.
The SDK supports subagent dispatch via an Agent tool. Define custom subagents in the options object and include "Agent" in allowedTools so the parent can invoke them.
import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({
prompt: "Use the code-reviewer agent to review this codebase",
options: {
allowedTools: ["Read", "Glob", "Grep", "Agent"],
agents: {
"code-reviewer": {
description: "Expert code reviewer for quality and security reviews.",
prompt: "Analyze code quality and suggest improvements.",
tools: ["Read", "Glob", "Grep"]
}
}
}
})) {
console.log(message);
}
Messages from within a subagent’s context carry a parent_tool_use_id field so you can attribute each message to the right subagent execution. Useful for per-subagent cost tracking and trace spans.
See chapter 3 for the broader subagent design guidance (descriptions as routing, file-boundary discipline, result offloading). Same principles apply in the SDK — just configured in code instead of frontmatter.
The SDK supports the same hook events as the CLI, passed as callback functions instead of shell commands. All the event types (PreToolUse, PostToolUse, SessionStart, SessionEnd, UserPromptSubmit, Stop, etc.) behave identically — this just gives you a richer in-process hook surface.
Python:
from datetime import datetime
from claude_agent_sdk import query, ClaudeAgentOptions, HookMatcher
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 {}
async for message in query(
prompt="Refactor utils.py",
options=ClaudeAgentOptions(
permission_mode="acceptEdits",
hooks={
"PostToolUse": [HookMatcher(matcher="Edit|Write", hooks=[log_file_change])]
},
),
):
print(message)
TypeScript:
import { query, HookCallback } from "@anthropic-ai/claude-agent-sdk";
import { appendFile } from "fs/promises";
const logFileChange: HookCallback = async (input) => {
const filePath = (input as any).tool_input?.file_path ?? "unknown";
await appendFile("./audit.log",
`${new Date().toISOString()}: modified ${filePath}\n`);
return {};
};
for await (const message of query({
prompt: "Refactor utils.ts",
options: {
permissionMode: "acceptEdits",
hooks: {
PostToolUse: [{ matcher: "Edit|Write", hooks: [logFileChange] }]
}
}
})) {
console.log(message);
}
Same stdin JSON payload format as CLI hooks — just delivered as a function argument instead of over stdin.
Streaming messages from query() give you a natural observability surface. Wire them into your logging/tracing stack.
Every yielded message carries a type. Typical types include:
system (init, shutdown) — session metadata, including session_idtool_use — the agent is calling a tooltool_result — result came backassistant — Claude’s text outputresult — final responsefor await (const message of query({ prompt, options })) {
log.info({
kind: message.type,
subtype: (message as any).subtype,
session_id: (message as any).session_id,
ts: Date.now(),
});
}
result messages carry usage and cost fields. Aggregate per session for cost dashboards. Tag by application context (user ID, feature flag, environment) to slice costs.
For OpenTelemetry-style tracing, emit a span per tool_use + tool_result pair, keyed by tool_use_id. Subagent messages carry parent_tool_use_id — use it to nest child spans under the parent subagent span.
Wire PreToolUse / PostToolUse hooks to emit structured events:
This gives you a complete audit log even when Claude’s result message is terse. Especially valuable for CI pipelines where the agent might run unattended.
A read-only review agent, safe for automated CI pipelines — cannot modify files even if Claude tries.
import { query } from "@anthropic-ai/claude-agent-sdk";
async function reviewPR(prNumber: number): Promise<string> {
let output = "";
for await (const message of query({
prompt: `Review PR #${prNumber}. Check for:
1. Security vulnerabilities
2. Missing error handling
3. Test coverage gaps
Return findings as a markdown checklist.`,
options: {
allowedTools: ["Read", "Glob", "Grep", "Bash"],
disallowedTools: ["Write", "Edit"], // cannot modify files
permissionMode: "acceptEdits",
settingSources: ["project"],
}
})) {
if ("result" in message) output = (message as any).result;
}
return output;
}
Key design choices:
disallowedTools for Write/Edit — the agent literally cannot modify filesallowedTools auto-approves the read-only tools — no human in the loopsettingSources: ["project"] loads project rules but not user-level config — hermetic CIpermissionMode: "acceptEdits" is safe here because writes are already blockedIf you’re on the older @anthropic-ai/claude-code-sdk:
npm install @anthropic-ai/claude-agent-sdk (or pip install claude-agent-sdk)@anthropic-ai/claude-code-sdk → @anthropic-ai/claude-agent-sdk; claude_code_sdk → claude_agent_sdknew Agent({...}).run(...) calls with query({ prompt, options }) iterationallowedTools vs allowed_tools casing per language; permissionMode replaces prior flags like dontAsk/yesAlwaysHookMatcher (Python) / { matcher, hooks: [...] } (TypeScript)See the official migration guide at code.claude.com/docs/en/agent-sdk/migration-guide for exhaustive detail.
allowedTools up with CLI tools: — allowedTools auto-approves; it does not restrict. Use disallowedTools to actually remove tools.bypassPermissions + external systems — never. Pre-approve specific tools or accept edits; reserve bypass for hermetic sandboxes.session_id — if you want to resume or fork, you have to read it from the system init message. Once the query completes without capture, it’s gone.settingSources — by default the SDK loads your home ~/.claude/ config. For CI you usually want ["project"] only so user-level rules don’t leak.| Use case | Pick |
|---|---|
| Interactive development | CLI |
| CI/CD pipelines | SDK |
| Custom applications | SDK |
| One-off exploratory tasks | CLI |
| Production automation | SDK |
| Agents embedded in user-facing apps | SDK |
| Quick experiments | CLI |
Workflows translate directly between them — an agent you prototype in the CLI can become an SDK-driven automation with minimal translation.