--- title: "Agent SDK Python API Reference" aliases: [python-sdk-reference, claude-agent-sdk-python] tags: [agent-sdk, python, api-reference, anthropic, claude] sources: [raw/Agent SDK reference - Python.md] created: 2026-04-17 updated: 2026-04-17 --- ## Installation ```bash pip install claude-agent-sdk ``` ## query() vs ClaudeSDKClient Two interaction modes — choose based on whether you need conversation continuity. | Feature | `query()` | `ClaudeSDKClient` | |---------|-----------|-------------------| | Session | New each call | Reuses same session | | Conversation | Single exchange | Multi-turn context | | Interrupts | No | Yes | | Hooks | Yes | Yes | | Custom Tools | Yes | Yes | | Use Case | One-off tasks | Continuous conversations | **Use `query()`** for independent tasks, automation scripts, no history needed. **Use `ClaudeSDKClient`** for chat interfaces, follow-up questions, response-driven logic. --- ## Core Functions ### query() ```python async def query( *, prompt: str | AsyncIterable[dict[str, Any]], options: ClaudeAgentOptions | None = None, transport: Transport | None = None ) -> AsyncIterator[Message] ``` Creates a new session per call. Returns an `AsyncIterator[Message]`. ```python async for message in query(prompt="Create a Python web server", options=options): print(message) ``` ### tool() decorator Defines MCP tools with type safety. ```python @tool("greet", "Greet a user", {"name": str}) async def greet(args: dict[str, Any]) -> dict[str, Any]: return {"content": [{"type": "text", "text": f"Hello, {args['name']}!"}]} ``` Input schema options: - Simple: `{"text": str, "count": int}` - Full JSON Schema: `{"type": "object", "properties": {...}, "required": [...]}` **ToolAnnotations** (optional hints, not security decisions): - `readOnlyHint` — tool doesn't modify environment - `destructiveHint` — tool may perform destructive updates - `idempotentHint` — repeated calls have no extra effect - `openWorldHint` — tool interacts with external entities ### create_sdk_mcp_server() ```python def create_sdk_mcp_server( name: str, version: str = "1.0.0", tools: list[SdkMcpTool[Any]] | None = None ) -> McpSdkServerConfig ``` Creates an in-process MCP server. Pass result to `ClaudeAgentOptions.mcp_servers`. ```python calculator = create_sdk_mcp_server(name="calculator", tools=[add, multiply]) options = ClaudeAgentOptions( mcp_servers={"calc": calculator}, allowed_tools=["mcp__calc__add", "mcp__calc__multiply"], ) ``` ### Session Management (synchronous) | Function | Description | |---------|-------------| | `list_sessions(directory, limit, include_worktrees)` | List past sessions with metadata | | `get_session_messages(session_id, directory, limit, offset)` | Get messages from a past session | | `get_session_info(session_id, directory)` | Metadata for a single session by ID | | `rename_session(session_id, title, directory)` | Set custom title; most recent wins | | `tag_session(session_id, tag, directory)` | Tag a session; pass `None` to clear | `SDKSessionInfo` fields: `session_id`, `summary`, `last_modified`, `custom_title`, `first_prompt`, `git_branch`, `cwd`, `tag`, `created_at`. --- ## ClaudeSDKClient Maintains conversation context across multiple exchanges. ```python async with ClaudeSDKClient() as client: await client.query("What's the capital of France?") async for message in client.receive_response(): ... await client.query("What's the population of that city?") # retains context async for message in client.receive_response(): ... ``` ### Key Methods | Method | Description | |--------|-------------| | `connect(prompt)` | Connect with optional initial prompt | | `query(prompt, session_id)` | Send request in streaming mode | | `receive_messages()` | All messages as async iterator | | `receive_response()` | Messages until (and including) `ResultMessage` | | `interrupt()` | Send stop signal (streaming mode only) | | `set_permission_mode(mode)` | Change permission mode mid-session | | `set_model(model)` | Change model; `None` resets to default | | `rewind_files(user_message_id)` | Restore files to state at message (requires `enable_file_checkpointing=True`) | | `get_mcp_status()` | Status of all MCP servers | | `reconnect_mcp_server(name)` | Retry failed MCP server | | `toggle_mcp_server(name, enabled)` | Enable/disable MCP server mid-session | | `stop_task(task_id)` | Stop a background task | | `disconnect()` | End session | ### Interrupt pattern `interrupt()` sends a stop signal but **does not clear the buffer**. You must drain the interrupted task's messages (including its `ResultMessage` with `subtype="error_during_execution"`) before reading the new response. ```python await client.interrupt() async for message in client.receive_response(): # drain interrupted task if isinstance(message, ResultMessage): break await client.query("Just say hello instead") async for message in client.receive_response(): # now get new response ... ``` --- ## ClaudeAgentOptions Key configuration fields: | Field | Type | Description | |-------|------|-------------| | `allowed_tools` | `list[str]` | Auto-approve these tools (doesn't restrict others) | | `disallowed_tools` | `list[str]` | Always deny; overrides `allowed_tools` and `bypassPermissions` | | `permission_mode` | `PermissionMode` | `"default"`, `"acceptEdits"`, `"plan"`, `"dontAsk"`, `"bypassPermissions"` | | `system_prompt` | `str \| SystemPromptPreset` | Custom string or `{"type":"preset","preset":"claude_code"}` | | `mcp_servers` | `dict[str, McpServerConfig]` | MCP server configs or path to config file | | `cwd` | `str \| Path` | Working directory | | `model` | `str` | Claude model to use | | `max_turns` | `int` | Max agentic turns (tool-use round trips) | | `max_budget_usd` | `float` | Stop when cost estimate reaches this value | | `can_use_tool` | `CanUseTool` | Custom permission callback | | `hooks` | `dict[HookEvent, list[HookMatcher]]` | Hook configurations | | `thinking` | `ThinkingConfig` | `{"type":"adaptive"}`, `{"type":"enabled","budget_tokens":N}`, `{"type":"disabled"}` | | `effort` | `Literal["low","medium","high","xhigh","max"]` | Thinking depth | | `enable_file_checkpointing` | `bool` | Enable file rewinding | | `setting_sources` | `list[SettingSource]` | Which filesystem settings to load (`"user"`, `"project"`, `"local"`); `[]` disables all | | `agents` | `dict[str, AgentDefinition]` | Programmatic subagents | | `sandbox` | `SandboxSettings` | Sandbox configuration | | `output_format` | `dict` | `{"type":"json_schema","schema":{...}}` for structured output | | `fork_session` | `bool` | When resuming, fork instead of continuing original | | `include_partial_messages` | `bool` | Emit `StreamEvent` for partial updates | ### SettingSource / settings_sources ```python # Disable all filesystem settings (CI/SDK-only apps) ClaudeAgentOptions(setting_sources=[]) # Load only shared team settings ClaudeAgentOptions(setting_sources=["project"]) # Load project settings to include CLAUDE.md ClaudeAgentOptions( system_prompt={"type":"preset","preset":"claude_code"}, setting_sources=["project"] ) ``` Settings precedence: local > project > user. Programmatic options override filesystem settings. Managed policy settings win over everything. --- ## Permission System ### CanUseTool callback ```python async def custom_permission( tool_name: str, input_data: dict, context: ToolPermissionContext ) -> PermissionResultAllow | PermissionResultDeny: if tool_name == "Write" and "config" in input_data.get("file_path", ""): return PermissionResultAllow(updated_input={**input_data, "file_path": f"./sandbox/{input_data['file_path']}"}) return PermissionResultAllow(updated_input=input_data) ``` - `PermissionResultAllow(updated_input=...)` — allow, optionally modify input - `PermissionResultDeny(message="...", interrupt=True)` — deny, optionally interrupt --- ## Message Types ``` Message = UserMessage | AssistantMessage | SystemMessage | ResultMessage | StreamEvent | RateLimitEvent ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock ``` ### ResultMessage key fields - `subtype` — `"success"` or `"error_during_execution"` - `total_cost_usd` — client-side cost estimate - `usage` — `{input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens}` - `model_usage` — per-model breakdown (camelCase keys: `inputTokens`, `costUSD`, etc.) - `session_id`, `num_turns`, `duration_ms` ### Background task messages - `TaskStartedMessage` — emitted when background task starts; `task_type`: `"local_bash"`, `"local_agent"`, `"remote_agent"` - `TaskProgressMessage` — periodic updates with `TaskUsage` (tokens, tool_uses, duration_ms) - `TaskNotificationMessage` — completion; `status`: `"completed"`, `"failed"`, `"stopped"` ### RateLimitEvent `status`: `"allowed"`, `"allowed_warning"`, `"rejected"`. Use to warn users or back off. --- ## Hook System ```python HookEvent = Literal[ "PreToolUse", "PostToolUse", "PostToolUseFailure", "UserPromptSubmit", "Stop", "SubagentStop", "PreCompact", "Notification", "SubagentStart", "PermissionRequest" ] ``` ```python options = ClaudeAgentOptions( hooks={ "PreToolUse": [ HookMatcher(matcher="Bash", hooks=[validate_bash], timeout=120), HookMatcher(hooks=[log_all_tools]), # matches all tools ] } ) ``` Hook callback signature: `async def hook(input_data, tool_use_id, context) -> HookJSONOutput` Return values: - `{}` — no-op - `{"continue_": False, "stopReason": "..."}` — stop execution - `{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny", ...}}` - `{"async_": True, "asyncTimeout": 5000}` — defer execution Note: Python SDK hooks don't yet support `SessionStart`, `SessionEnd`, `Setup` (TypeScript only). --- ## Sandbox Settings ```python SandboxSettings = { "enabled": True, "autoAllowBashIfSandboxed": True, "excludedCommands": ["docker"], # always bypass sandbox, no model involvement "allowUnsandboxedCommands": True, # model can request bypass via dangerouslyDisableSandbox "network": { "allowLocalBinding": True, "allowUnixSockets": ["/var/run/docker.sock"], # ⚠️ grants full Docker/host access } } ``` > **Security**: `bypassPermissions` + `allowUnsandboxedCommands=True` lets the model escape the sandbox silently. --- ## Built-in Tool Reference (Input/Output shapes) | Tool | Key Input | Key Output | |------|-----------|------------| | `Bash` | `command`, `timeout`, `run_in_background` | `output`, `exitCode`, `shellId` | | `Read` | `file_path`, `offset`, `limit` | `content`, `total_lines` | | `Write` | `file_path`, `content` | `bytes_written` | | `Edit` | `file_path`, `old_string`, `new_string`, `replace_all` | `replacements` | | `Glob` | `pattern`, `path` | `matches`, `count` | | `Grep` | `pattern`, `path`, `glob`, `output_mode` | `matches` or `files` | | `WebFetch` | `url`, `prompt` | `response`, `status_code` | | `WebSearch` | `query`, `allowed_domains` | `results`, `total_results` | | `Agent` | `description`, `prompt`, `subagent_type` | `result`, `usage`, `total_cost_usd` | | `Monitor` | `command`, `description`, `timeout_ms`, `persistent` | `taskId` | | `TodoWrite` | `todos[]` with `content`, `status` | `stats` | --- ## Type System Notes - `@dataclass` types (e.g. `ResultMessage`, `TextBlock`) → attribute access: `msg.result` - `TypedDict` types (e.g. `ThinkingConfigEnabled`, `McpStdioServerConfig`) → dict access: `config["budget_tokens"]` --- ## Error Types | Exception | When | |-----------|------| | `CLINotFoundError` | Claude Code CLI not installed | | `CLIConnectionError` | Connection to Claude Code failed | | `ProcessError` | Claude Code process failed (`exit_code`, `stderr` attrs) | | `CLIJSONDecodeError` | JSON parsing failed (`line`, `original_error` attrs) | --- ## Key Takeaways - `query()` = stateless one-shot; `ClaudeSDKClient` = stateful multi-turn conversation - After `interrupt()`, drain the buffer with `receive_response()` before sending a new query - `disallowed_tools` overrides everything including `bypassPermissions` - `allowed_tools` auto-approves but does NOT restrict — use `disallowed_tools` to block - `setting_sources=[]` disables all filesystem settings (good for CI and SDK-only apps) - `ThinkingConfig` and `McpServerConfig` variants are `TypedDict` (dict at runtime, not objects) - `SdkBeta: "context-1m-2025-08-07"` is retired after 2026-04-30; Claude Sonnet/Opus 4.6+ have 1M context natively - Sandbox + `allowUnixSockets` for Docker socket grants full host access — high risk - Hook callbacks use `continue_` and `async_` (with underscores) in Python; SDK converts to `continue`/`async` for CLI --- ## Sources - [raw/Agent SDK reference - Python.md](raw/Agent%20SDK%20reference%20-%20Python.md) ## Related - [[wiki/agent-sdk/overview|Agent SDK Overview]] - [[wiki/tech-patterns/ai-patterns|AI Patterns]] - [[wiki/architecture/multi-agent-ai|Multi-Agent AI Architecture]]