Claude Agent SDK

The Claude Agent SDK packages the same agent engine that powers Claude Code as a library you import — Python (claude-agent-sdk) or TypeScript (@anthropic-ai/claude-agent-sdk). You get the built-in tool set (Read, Write, Edit, Bash, Glob, Grep, WebSearch, WebFetch, Monitor, AskUserQuestion), the agent loop, hooks, subagents, skills, MCP integration, and session management — without writing the tool dispatcher yourself. Compare to the Anthropic Client SDK (claude-api-and-sdks), which gives raw API access and leaves the tool loop to you.

See also

1. Install

# 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 — no separate install needed.

2. Auth

export ANTHROPIC_API_KEY=your-key

Or cloud-provider auth via env vars (same as Claude Code CLI):

  • CLAUDE_CODE_USE_BEDROCK=1 + AWS creds
  • CLAUDE_CODE_USE_VERTEX=1 + GCP creds
  • CLAUDE_CODE_USE_FOUNDRY=1 + Azure creds
  • CLAUDE_CODE_USE_ANTHROPIC_AWS=1 + ANTHROPIC_AWS_WORKSPACE_ID

Note from docs: “Unless previously approved, Anthropic does not allow third-party developers to offer claude.ai login or rate limits for their products.” Use API key auth.

Also: starting 2026-06-15, Agent SDK + claude -p usage on subscription plans draws from a new monthly Agent SDK credit, separate from interactive usage limits.

3. The two main APIs

3.1 query — fire-and-forget streaming

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)   # AssistantMessage / UserMessage / SystemMessage / ResultMessage
 
asyncio.run(main())
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);
}

query returns an async iterator of message events. Iterate until ResultMessage arrives — that’s the final result with summary and cost.

3.2 ClaudeSDKClient — persistent session

from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions
 
async with ClaudeSDKClient(options=ClaudeAgentOptions(...)) as client:
    await client.query("Analyze the codebase")
    async for msg in client.receive_messages():
        if hasattr(msg, "result"):
            print(msg.result)
    
    # Same session, more turns
    await client.query("Now fix the bug you found")
    async for msg in client.receive_messages():
        print(msg)

Use for multi-turn agents where session state matters.

4. ClaudeAgentOptions — the configuration surface

Mirrors the CLI flag set. Selected fields:

FieldTypePurpose
modelstringModel alias or full ID
allowed_tools / allowedToolslist of stringsTools that bypass permission prompts
disallowed_tools / disallowedToolslist of stringsTools to deny
permission_mode / permissionModestringdefault, acceptEdits, plan, auto, dontAsk, bypassPermissions
system_prompt / systemPromptstring | dictCustom or preset system prompt (see §5)
append_system_prompt / appendSystemPromptstringAppend to default
custom_system_prompt / customSystemPromptstringReplace default
max_thinking_tokens / maxThinkingTokensintCap thinking budget
max_turns / maxTurnsintCap agentic turns
cwdstringWorking directory
add_dirs / addDirslist of stringsAdditional working dirs
setting_sources / settingSourceslist["user", "project", "local"] — restricts which settings.json files load
mcp_servers / mcpServersdictMCP server configs (same shape as .mcp.json)
agentsdictInline subagent definitions
hooksdictHook callbacks (event → list of HookMatcher)
skillsdictInline skills
pluginslistPlugin paths/URLs
resumestringSession ID or name to resume
session_id / sessionIdstring (UUID)Force specific session UUID
effortstringlow/medium/high/xhigh/max
output_format / outputFormatstringtext / json / stream-json
include_partial_messages / includePartialMessagesboolStream partial events

5. System prompt presets

The SDK exposes presets you can opt into (instead of writing your own).

options = ClaudeAgentOptions(
    system_prompt={"type": "preset", "name": "claude_code"},
    # or override
    custom_system_prompt="You are a Python expert assistant.",
    append_system_prompt="Always use type hints.",
)

The claude_code preset is the full Claude Code system prompt (long, dense, includes tool-use guidance + safety + coding conventions). Use when you want a Claude Code-like agent. Omit for a leaner agent with no default prompt — you’re responsible for tool guidance.

6. Built-in tools

Available out of the box (no implementation needed):

ToolPurpose
ReadRead any file in cwd
WriteCreate new files
EditPrecise edits to existing files (str_replace)
MultiEditMultiple edits to a file in one call
BashTerminal commands, scripts, git
GlobFind files by pattern
GrepSearch file contents (ripgrep-based)
WebSearchWeb search with citations
WebFetchFetch + parse a URL
MonitorWatch a background script, react per output line
AskUserQuestionMultiple-choice clarifying questions
Task / AgentDispatch a subagent (Task renamed to Agent in v2.1.63)
SkillInvoke a skill
NotebookEditEdit Jupyter notebooks
TaskStopStop a running task
ExitWorktree / EnterWorktreeWorktree navigation
ToolSearchSearch for deferred MCP tools
WaitForMcpServersWait for slow MCP servers

Add to your agent via allowed_tools=[...]. Tool restriction prevents Claude from even seeing the tool when omitted.

7. Hooks (callback-based)

Same hook system as Claude Code CLI, but with callback functions instead of subprocesses.

import asyncio
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 {}   # or {"hookSpecificOutput": {"permissionDecision": "deny"}}
 
 
async def main():
    async for message in query(
        prompt="Refactor utils.py",
        options=ClaudeAgentOptions(
            permission_mode="acceptEdits",
            hooks={
                "PostToolUse": [
                    HookMatcher(matcher="Edit|Write", hooks=[log_file_change])
                ]
            },
        ),
    ):
        if hasattr(message, "result"):
            print(message.result)
 
 
asyncio.run(main())

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.py to improve readability",
  options: {
    permissionMode: "acceptEdits",
    hooks: { PostToolUse: [{ matcher: "Edit|Write", hooks: [logFileChange] }] }
  }
})) {
  if ("result" in message) console.log(message.result);
}

Same events available as CLI hooks (see claude-code-hooks-deep).

8. Subagents

Inline definitions:

from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
 
options = ClaudeAgentOptions(
    allowed_tools=["Read", "Glob", "Grep", "Agent"],
    agents={
        "code-reviewer": AgentDefinition(
            description="Expert code reviewer.",
            prompt="Analyze code quality and suggest improvements.",
            tools=["Read", "Glob", "Grep"],
            model="sonnet",
        )
    },
)

TypeScript:

options: {
  allowedTools: ["Read", "Glob", "Grep", "Agent"],
  agents: {
    "code-reviewer": {
      description: "Expert code reviewer.",
      prompt: "Analyze code quality and suggest improvements.",
      tools: ["Read", "Glob", "Grep"]
    }
  }
}

Messages from inside a subagent’s context include a parent_tool_use_id field for tracking.

9. MCP servers

Same .mcp.json-style config:

options = ClaudeAgentOptions(
    mcp_servers={
        "playwright": {"command": "npx", "args": ["@playwright/mcp@latest"]},
        "github": {
            "type": "http",
            "url": "https://api.githubcopilot.com/mcp/",
            "headers": {"Authorization": f"Bearer {os.environ['GH_TOKEN']}"},
        },
    },
)

Also accepts inline server definitions inside subagent configs (per-subagent MCP, see claude-code-subagents-and-skills).

10. Permissions

options = ClaudeAgentOptions(
    allowed_tools=["Read", "Glob", "Grep"],   # auto-approve these
    disallowed_tools=["Bash(rm *)"],          # deny specific patterns
    permission_mode="default",                 # default | acceptEdits | plan | auto | dontAsk | bypassPermissions
)

For interactive approval flows in headless mode, implement permission_prompt_tool (point to an MCP tool that decides). See Handle approvals and user input.

The AskUserQuestion built-in tool lets the agent ask multiple-choice questions and waits for user response.

11. Sessions

Capture session ID on first call, resume later:

session_id = None
 
async for msg in query(prompt="Read the auth module", options=ClaudeAgentOptions(allowed_tools=["Read", "Glob"])):
    if isinstance(msg, SystemMessage) and msg.subtype == "init":
        session_id = msg.data["session_id"]
 
# Resume with full context
async for msg in query(prompt="Now find all callers", options=ClaudeAgentOptions(resume=session_id)):
    if isinstance(msg, ResultMessage):
        print(msg.result)

Sessions persist as JSONL on disk in the same location as Claude Code CLI.

12. Message types

query yields these (per docs + SDK source):

  • SystemMessage — startup info, session ID. subtype field: "init", etc.
  • AssistantMessage — content from Claude. content is a list of blocks (text, tool_use, thinking, etc.).
  • UserMessage — user / tool result content.
  • ResultMessage — final summary. Includes result (text), total_cost_usd, usage, duration_ms, etc.
async for msg in query(prompt="..."):
    if isinstance(msg, ResultMessage):
        print(f"Done in {msg.duration_ms}ms, cost ${msg.total_cost_usd:.4f}")
        print(msg.result)

13. Skills

Same skill format as Claude Code. Loaded from .claude/skills/ and ~/.claude/skills/ by default. Restrict via setting_sources:

options = ClaudeAgentOptions(setting_sources=["project"])  # only project skills

Or define skills inline via skills option (less common — usually loaded from disk).

14. Plugins

options = ClaudeAgentOptions(plugins=["./my-plugin", "https://example.com/plugin.zip"])

Loads plugin’s commands, agents, hooks, MCP servers, skills.

15. Streaming partial messages

options = ClaudeAgentOptions(include_partial_messages=True)
async for msg in query(prompt="..."):
    # Includes partial events (token-level streaming)
    print(msg)

Requires print-mode equivalent settings. Useful for live UI updates.

16. Structured outputs

output_format and JSON schema:

options = ClaudeAgentOptions(
    output_format="json",
    json_schema={"type": "object", "properties": {"summary": {"type": "string"}, "files_changed": {"type": "array"}}, "required": ["summary"]},
)

The agent’s final response conforms to the schema.

17. Compare to other Claude tools

Agent SDKClient SDKClaude Code CLIManaged Agents (REST)
Tool executionBuilt-inYou implementBuilt-inAnthropic-hosted
Tool loopAutoYou writeAutoAuto
Session stateJSONL on your filesystemNone (you store)JSONL on filesystemAnthropic-hosted event log
Custom toolsPython/TS functions in-processAnything you can dispatchMCP serversTriggers come to you; you execute and return
InterfaceLibraryLibraryCLI / IDEREST API
Best forProduction agents with local filesCustom integrations, OpenAI-style flowsDaily dev work, ad-hoc scriptsSandboxed long-running agents

A common path: prototype with Agent SDK locally, move to Managed Agents for production when you don’t want to operate sandboxes.

18. Worked example: bug-fix agent

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
 
async def fix_bug(repo_path: str, bug_description: str):
    async for msg in query(
        prompt=f"Find and fix this bug: {bug_description}",
        options=ClaudeAgentOptions(
            cwd=repo_path,
            model="claude-opus-4-7",
            permission_mode="acceptEdits",
            allowed_tools=["Read", "Edit", "Bash", "Glob", "Grep"],
            max_turns=20,
            append_system_prompt="""
Always run the tests after editing. Use ripgrep for searches.
If you can't reproduce the bug, ask the user for a repro.
            """.strip(),
        ),
    ):
        if isinstance(msg, ResultMessage):
            return {
                "summary": msg.result,
                "cost_usd": msg.total_cost_usd,
                "duration_ms": msg.duration_ms,
            }
 
result = asyncio.run(fix_bug("/path/to/repo", "Login button doesn't work on mobile"))

19. Common gotchas

  1. Forgetting to await async iteration — yields are nothing without async for.
  2. Agent in allowed_tools — required for subagent dispatch (otherwise Claude can see them but can’t auto-approve).
  3. setting_sources defaults to loading everything (["user", "project", "local"]) — restrict for hermetic agents.
  4. Hook callbacks must return a dict (or empty {} for no-op).
  5. Skills load asynchronously — for one-shot scripts, may not find skills if you call query immediately after startup. Use ClaudeSDKClient for reliable skill loading.
  6. include_partial_messages is expensive — most use cases don’t need token-level streaming.
  7. Resuming sessions across SDK versions — transcript format is stable but skill content may have changed.
  8. TypeScript bundle ships native binary — large install, but means no separate Claude Code install.

20. Further reading