The Agent SDK is Claude Code as a library: the same agent loop, built-in tools (file ops, bash, search, web), context management, and permission system, programmable from Python or TypeScript. Verified against code.claude.com/docs/en/agent-sdk (2026-06).
| Python | TypeScript | |
|---|---|---|
| Package | claude-agent-sdk (pip install claude-agent-sdk) |
@anthropic-ai/claude-agent-sdk (npm install @anthropic-ai/claude-agent-sdk) |
| Runtime | Python ≥ 3.10 | Node (bundles a native Claude Code binary — no separate install) |
| Entry point | query(prompt, options=ClaudeAgentOptions(...)) → async iterator |
query({ prompt, options }) → async iterator |
| Option naming | snake_case (allowed_tools) |
camelCase (allowedTools) |
Auth: ANTHROPIC_API_KEY, or third-party providers via env flags
(CLAUDE_CODE_USE_BEDROCK=1, CLAUDE_CODE_USE_VERTEX=1,
CLAUDE_CODE_USE_FOUNDRY=1, CLAUDE_CODE_USE_ANTHROPIC_AWS=1 +
ANTHROPIC_AWS_WORKSPACE_ID). Note: from June 15, 2026, Agent SDK / claude -p
usage on subscription plans draws from a separate monthly Agent SDK credit —
production apps should use API keys.
| Signal | Choice |
|---|---|
| Agent must read/edit files, run commands, search a codebase | Agent SDK — tools are built in, loop is handled |
| CI/CD automation, "fix the failing test", repo-scale refactors | Agent SDK |
| You want hooks, permission gating, session resume out of the box | Agent SDK |
| Single call: classify/summarize/extract | Messages API — SDK is overkill |
| You need exact control of every request (params, caching breakpoints, message shapes) | Messages API + tool use |
| Your tools are pure in-process functions, no filesystem | Messages API + tool runner is lighter |
| Hosted agent, no infra at all | Managed Agents (REST, Anthropic-run sandbox) |
The difference in code:
# Messages API: you implement the loop
response = client.messages.create(...)
while response.stop_reason == "tool_use":
result = your_tool_executor(...)
response = client.messages.create(...)
# Agent SDK: the loop, tools, and context management are inside query()
async for message in query(prompt="Fix the bug in auth.py"):
print(message)
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions
async def main():
async for message in query(
prompt="Find all TODO comments and create a summary",
options=ClaudeAgentOptions(allowed_tools=["Read", "Glob", "Grep"]),
):
if hasattr(message, "result"): # final ResultMessage
print(message.result)
asyncio.run(main())
import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({
prompt: "Find all TODO comments and create a summary",
options: { allowedTools: ["Read", "Glob", "Grep"] },
})) {
if ("result" in message) console.log(message.result);
}
The iterator yields typed messages: system messages (subtype: "init" carries
session_id), assistant/tool activity, and a final result message
(message.result / ResultMessage).
| Python | TypeScript | Purpose |
|---|---|---|
allowed_tools |
allowedTools |
Pre-approved tools (no permission prompt). Include "Agent" to auto-approve subagent spawns |
disallowed_tools |
disallowedTools |
Hard-blocked tools |
permission_mode |
permissionMode |
e.g. "default", "acceptEdits", "bypassPermissions", "plan" |
system_prompt |
systemPrompt |
Replace or extend the system prompt |
mcp_servers |
mcpServers |
MCP server map (see below) |
hooks |
hooks |
Lifecycle callbacks (see below) |
agents |
agents |
Named subagent definitions |
resume |
resume |
Session ID to continue with full context |
cwd |
cwd |
Working directory for the agent |
model |
model |
Override model |
max_turns |
maxTurns |
Cap agent-loop iterations |
setting_sources |
settingSources |
Restrict which filesystem config loads (.claude/, ~/.claude/) |
plugins |
plugins |
Programmatic plugin loading |
Read, Write, Edit, Bash, Glob, Grep, WebSearch, WebFetch,
Monitor (watch a background process), AskUserQuestion (clarifying
questions with options), Agent (spawn subagents). A read-only agent is just
allowed_tools=["Read", "Glob", "Grep"].
Callbacks at lifecycle points: PreToolUse, PostToolUse, Stop,
SessionStart, SessionEnd, UserPromptSubmit, and more. Use them to audit,
block, or transform agent behavior.
from claude_agent_sdk import query, ClaudeAgentOptions, HookMatcher
from datetime import datetime
async def log_file_change(input_data, tool_use_id, context):
path = input_data.get("tool_input", {}).get("file_path", "unknown")
with open("./audit.log", "a") as f:
f.write(f"{datetime.now()}: modified {path}\n")
return {} # empty dict = allow; hooks can also block/modify
options = ClaudeAgentOptions(
permission_mode="acceptEdits",
hooks={"PostToolUse": [HookMatcher(matcher="Edit|Write", hooks=[log_file_change])]},
)
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 {};
};
const options = {
permissionMode: "acceptEdits" as const,
hooks: { PostToolUse: [{ matcher: "Edit|Write", hooks: [logFileChange] }] },
};
matcher is a regex over tool names. A PreToolUse hook returning a deny
decision blocks the call — this is the programmatic equivalent of a human
approval gate.
Wire any MCP server (stdio command or remote) into the agent:
options = ClaudeAgentOptions(
mcp_servers={
"playwright": {"command": "npx", "args": ["@playwright/mcp@latest"]},
},
)
const options = {
mcpServers: {
playwright: { command: "npx", args: ["@playwright/mcp@latest"] },
},
};
MCP tools surface as mcp__<server>__<tool> — add them to
allowed_tools to pre-approve. This is how you give an agent databases,
browsers, and third-party APIs without writing tool plumbing.
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
options = ClaudeAgentOptions(
allowed_tools=["Read", "Glob", "Grep", "Agent"], # Agent tool approves spawns
agents={
"code-reviewer": AgentDefinition(
description="Expert code reviewer for quality and security reviews.",
prompt="Analyze code quality and suggest improvements.",
tools=["Read", "Glob", "Grep"],
),
},
)
# prompt: "Use the code-reviewer agent to review this codebase"
Messages emitted inside a subagent carry parent_tool_use_id so you can
attribute output to the spawning call. Use subagents to isolate context
(reviewer doesn't pollute the main transcript) and to parallelize independent
legs.
Session state is JSONL on your filesystem. Capture the session ID from the init message, resume later with full context:
from claude_agent_sdk import query, ClaudeAgentOptions, SystemMessage, ResultMessage
session_id = None
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"]
async for message in query(prompt="Now find all places that call it",
options=ClaudeAgentOptions(resume=session_id)):
if isinstance(message, ResultMessage):
print(message.result)
let sessionId: string | undefined;
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;
}
}
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 can also be forked to explore alternative approaches from the same context point.
With default options the SDK loads Claude Code's filesystem config from
.claude/ (project) and ~/.claude/ (user): skills
(.claude/skills/*/SKILL.md), commands, CLAUDE.md memory, plugins. Restrict
with setting_sources / settingSources when you want a hermetic agent (CI)
that ignores developer-machine state.
permission_mode="bypassPermissions" (or a tight
allowed_tools list) + max_turns cap + hooks for audit logging. Never
bypass permissions on a machine with credentials you don't want the agent
exercising.PreToolUse hook on Bash|Write|Edit that checks the
input and returns deny for out-of-policy actions.PostToolUse for tool-level metrics; final ResultMessage includes
cost/usage data.claude -p) — anything
you can do interactively you can automate via the SDK.