ClaudeMap

·SDKs & Tooling

A 2026 field guide to the Claude Agent SDK — the same agent loop that powers Claude Code, exposed as Python and TypeScript libraries. Covers the query() async-generator API, every ClaudeAgentOptions field, MCP server wiring, hooks + audit trails, multi-agent fan-out orchestration with asyncio.gather, five production guardrails (idempotency / cost / secrets / determinism / evaluation), and the three official deployment shapes (managed-agents-observe, self-hosted sandbox, worker dispatch).

Claude Agent SDK Deep Dive: From First query() to Multi-Agent Orchestration (2026)

The Claude Agent SDK is Anthropic's programmable agent framework — the same tools, agent loop, context management, and permission model that power the interactive Claude Code, exposed as Python and TypeScript libraries. This guide, based on the official Agent SDK docs and the claude-quickstarts managed-agents examples, covers: the core query() async-generator API, the full ClaudeAgentOptions field set, MCP server wiring, hooks and audit, multi-agent orchestration, production guardrails, and the most common 2026 pitfalls.

TL;DR

  • Agent SDK = the same agent loop that powers Claude Code, as a programmatic library (Python / TypeScript)
  • Install: pip install claude-agent-sdk or npm install @anthropic-ai/claude-agent-sdk
  • Core API: query(prompt, options) returns an async generator — iterate the message stream (assistant text, tool calls, tool results)
  • Key options: allowed_tools / permission_mode / cwd / model / hooks
  • MCP servers, hooks, subagents, and bypassPermissions are first-class citizens of the Agent SDK
  • 2026 status: three managed-agents deployment shapes — Anthropic-hosted, self-hosted sandbox, worker pool dispatch

What the Claude Agent SDK is

Claude Code is the interactive CLI you run in a terminal; the Claude Agent SDK is the programmable version of the same agent loop. It runs by driving the local Claude Code binary under the hood — the Python and TypeScript SDKs both depend on a working Claude Code CLI — so you don't inherit a simplified reimplementation. You inherit the full Claude Code capability: every built-in tool (Read / Write / Edit / Bash / Glob / Grep / WebSearch and friends), context management, permission model, hooks, subagents.

When to reach for the Agent SDK:

| Scenario | Reach for | |---|---| | Terminal interaction, personal dev | Claude Code CLI directly | | CI / automation scripts / background workers | Agent SDK | | Embed an agent in your own app | Agent SDK | | Direct control over messages / tools / model | Raw anthropic SDK (build the loop yourself) | | Multi-agent orchestration / long-running tasks | Agent SDK + subagents + hooks |

Rule of thumb: if you want the full Claude Code agent loop embedded in your program without rebuilding it, reach for the Agent SDK. If you want to call the model with an exact set of messages and tools, use the raw API.

Install and authenticate

# Python (3.10+)
pip install claude-agent-sdk
# or uv
uv add claude-agent-sdk

# TypeScript / Node.js
npm install @anthropic-ai/claude-agent-sdk

Prerequisite: the Claude Code CLI must be available on the machine. The SDK executes the agent loop by invoking the local Claude Code binary — without it, nothing runs.

Authentication reuses Claude Code's environment variables:

export ANTHROPIC_API_KEY="sk-ant-..."

The CLI walks you through account login on first run; once logged in, the SDK automatically reuses the same credentials. Production recommendation: API keys via environment variables / Vault injection — never hard-code keys.

A minimal working agent

The Python SDK exposes an async generator query() that yields the messages the agent produces:

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions

async def main():
    async for message in query(
        prompt="What files are in this directory? Summarize the project structure.",
        options=ClaudeAgentOptions(allowed_tools=["Bash", "Glob"]),
    ):
        # each message is an event in the agent run:
        # AssistantTextMessage / ToolUseBlock / ToolResultBlock / ResultMessage
        if hasattr(message, "result"):
            print(message.result)

asyncio.run(main())

The TypeScript version follows the same shape:

import { query, ClaudeAgentOptions } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "What files are in this directory? Summarize the project structure.",
  options: new ClaudeAgentOptions({ allowedTools: ["Bash", "Glob"] }),
})) {
  if ("result" in message) {
    console.log(message.result);
  }
}

Key API behaviour:

  • query() is an async generator — you must iterate asynchronously to receive messages one by one.
  • Four message classes: AssistantMessage (text), UserMessage (tool_result), SystemMessage (metadata), ResultMessage (final answer).
  • The agent loop is implicit — you only react to the message stream; the SDK runs the tool, feeds back the result, and re-queries the model.

ClaudeAgentOptions: every field explained

ClaudeAgentOptions is the SDK's single switchboard. Common fields:

| Field | Type | Purpose | |---|---|---| | allowed_tools | list[str] | Tool allowlist — e.g. ["Read", "Bash", "Glob"] | | disallowed_tools | list[str] | Tool denylist (layered on top of the allowlist) | | permission_mode | str | default / acceptEdits / plan / bypassPermissions | | cwd | str | Agent working directory | | model | str | Model ID, e.g. claude-sonnet-4-5 | | system_prompt | str | Top-level system prompt | | mcp_servers | dict | MCP server config (project + user + inline) | | hooks | dict | PreToolUse / PostToolUse / Stop / SubagentStop etc. | | max_turns | int | Max tool-call rounds (runaway protection) | | max_budget_usd | float | Dollar budget cap (throws when exceeded) | | fallback_model | str | Fallback model when primary returns 5xx | | extra_args | dict | Extra args forwarded to the Claude Code CLI |

The gap between minimal and production-ready:

# minimal: read-only agent restricted to Read + Grep
options = ClaudeAgentOptions(
    allowed_tools=["Read", "Grep"],
    cwd="/path/to/repo",
)

# production: MCP + hooks + model fallback + budget cap
options = ClaudeAgentOptions(
    allowed_tools=["Read", "Grep", "Bash(git diff:*)"],
    permission_mode="bypassPermissions",
    cwd="/path/to/repo",
    model="claude-sonnet-4-5",
    fallback_model="claude-haiku-4-5",
    max_turns=20,
    max_budget_usd=2.0,
    mcp_servers={
        "github": {
            "command": "docker",
            "args": ["run", "-i", "--rm", "-e", "GITHUB_TOKEN", "ghcr.io/github/github-mcp-server"],
        }
    },
    hooks={
        "PreToolUse": [{
            "matcher": "Bash",
            "hooks": [{"type": "command", "command": "python3 .claude/hooks/audit.py"}],
        }],
    },
)

Wiring MCP servers

The Agent SDK inherits Claude Code's MCP support — any MCP server can be plugged in:

options = ClaudeAgentOptions(
    mcp_servers={
        # stdio server (most common)
        "github": {
            "command": "npx",
            "args": ["-y", "@modelcontextprotocol/server-github"],
            "env": {"GITHUB_TOKEN": "${GITHUB_TOKEN}"},
        },
        # HTTP server (remote)
        "remote-tools": {
            "url": "https://mcp.example.com/sse",
            "headers": {"Authorization": "Bearer ${TOKEN}"},
        },
    }
)

Field-tested pattern: merge project-level .mcp.json config with SDK options — the SDK automatically applies Claude Code's load order (MDM > project > user). The benefit: the same MCP server config works in both interactive Claude Code and your Agent SDK programs — one config shared by the whole team.

Hooks and audit trails

The Agent SDK inherits Claude Code's full hooks system — async for message exposes every event (text, tool_use, tool_result), and hooks add deterministic guardrails:

import json, logging
audit = logging.getLogger("agent.audit")

async def logged_query(prompt: str):
    async for message in query(
        prompt=prompt,
        options=ClaudeAgentOptions(
            allowed_tools=["Bash", "Read", "Write"],
            hooks={
                "PreToolUse": [{
                    "matcher": "Bash",
                    "hooks": [{
                        "type": "command",
                        "command": "python3 .claude/hooks/block_rm.py",
                    }],
                }],
            },
        ),
    ):
        # stream-log every event — this is the audit trail
        audit.info(json.dumps({
            "type": type(message).__name__,
            "data": getattr(message, "data", None),
        }))
        if hasattr(message, "result"):
            yield message.result

Field-tested categories:

  • Deterministic safety guardrails: PreToolUse hook blocks rm -rf / curl | sh / writes to sensitive paths
  • Audit logs: stream every message to a structured (JSON) log; query it directly when CI fails
  • Quality gates: PostToolUse hook runs ruff / tsc / tests; errors flow back to the agent so it self-corrects
  • Stop notifications: webhook / Slack when a long-running task finishes

Multi-agent orchestration

The Agent SDK lets you spin up multiple parallel subagents in the same process for fan-out research. Pair with permission_mode="bypassPermissions" (subagents have no interactive surface — automated tasks must skip prompts):

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions

async def fan_out_research(topics: list[str]) -> list[str]:
    subagents = [
        query(
            prompt=f"Research 2026 best practices for {topic}. Return 3-5 points.",
            options=ClaudeAgentOptions(
                allowed_tools=["Read", "Grep", "WebSearch"],
                permission_mode="bypassPermissions",
                max_turns=10,
            ),
        )
        for topic in topics
    ]
    streams = await asyncio.gather(*subagents, return_exceptions=True)
    results = []
    for stream in streams:
        if isinstance(stream, Exception):
            continue
        async for message in stream:
            if hasattr(message, "result"):
                results.append(message.result)
    return results

Key patterns:

  • Each subagent has its own context window (isolated)
  • return_exceptions=True keeps a single failure from killing the others
  • max_turns is the runaway backstop
  • The main thread only sees distilled reports at the end — subagent internal context never touches the main thread's budget

Production guardrails: five hard rules

Five iron rules for unattended agents in production:

  1. Idempotency. Agents get retried — tool side effects must be idempotent or guarded.
  2. Cost monitoring. max_budget_usd + external token-usage tracking + budget alerts.
  3. Secret protection. Apply deny rules; never let secrets reach a file the agent can read.
  4. Determinism boundaries. Agents for judgement work (triage, summary, exploration); deterministic logic stays in plain code.
  5. Evaluate first. Run the agent against a fixed input set + re-run on prompt / tool / model changes; without this, quality drifts invisibly.
# An idempotent tool pattern
def send_email(to: str, subject: str, body: str) -> str:
    idempotency_key = hashlib.sha256(f"{to}{subject}{body}".encode()).hexdigest()
    return email_client.send(to, subject, body, idempotency_key=idempotency_key)

2026 deployment shapes

The claude-quickstarts managed-agents examples document three official deployment patterns:

  1. managed-agents-observe-tool-calls.py — Anthropic-hosted backend; auto-observation, audit, billing.
  2. managed-agents-self-hosted-sandbox-worker.py — self-hosted sandbox worker; the Agent SDK runs inside a container.
  3. managed-agents-worker-dispatch.py — worker pool + task dispatch; for high-QPS background tasks.

Field-tested selection:

  • Early-stage projects: managed-agents hosted backend; zero ops overhead.
  • Enterprise / compliance: self-hosted sandbox worker; data stays in your network.
  • Production scale: worker pool + task queue; horizontal scale.

Frequently asked questions

How does the Agent SDK relate to Claude Code?

The Agent SDK is the programmable wrapper around the same agent loop that powers Claude Code. The SDK drives the local Claude Code CLI binary under the hood, so you inherit the full Claude Code capability: every built-in tool, context management, permission model, hooks, subagents. The difference: Claude Code is the interactive CLI you run in your terminal; the Agent SDK is what you embed in your programs for CI / custom apps / production automation.

How do I control which tools the agent can use?

Pass an allowed_tools list through ClaudeAgentOptions. Restricting the tool set is the single most effective safety lever — an agent that can only Read and Grep cannot mutate state by construction. Production pattern: allowlist + denylist + permission_mode="bypassPermissions" + max_turns as the runaway backstop. Grant Write / Bash only when the task genuinely needs them, and pair with hooks for policy.

Can the Agent SDK run multiple subagents in parallel?

Yes. asyncio.gather(*queries, return_exceptions=True) fires multiple query() calls concurrently, each running its own subagent with isolated contexts. return_exceptions=True keeps a single failure from killing the rest. Field pattern: fan-out research — three subagents investigate different directions in parallel, the main thread only sees three distilled reports at the end. Subagent internal context never touches the main thread's budget — that's the real token-level payoff.

How do I add deterministic safety guardrails to the Agent SDK?

Wire ClaudeAgentOptions.hooks into Claude Code's hooks system — PreToolUse blocks dangerous tool calls, PostToolUse runs a linter and feeds errors back to the agent, Stop triggers a notification. Audit: async for message in query(...) exposes every event (assistant text, tool calls, tool results); write them to a structured JSON log. Production split: "must never go wrong" lives in hooks (deterministic logic); "needs judgement" lives in the agent (probabilistic logic) — clean separation, zero interference.

How do I wire MCP servers into the Agent SDK?

Pass them through the mcp_servers field of ClaudeAgentOptions: stdio servers take command + args + env; remote HTTP servers take url + headers (e.g. a Bearer token). The SDK reuses Claude Code's load order (enterprise MDM > project > user), so the same MCP config works both in interactive Claude Code and in your SDK program — one shared set of server definitions for the team.

How do I stop an Agent SDK run from running away on cost or loops?

Three built-in knobs plus two external guardrails. Built in: max_turns caps tool-call rounds, max_budget_usd sets a dollar budget that throws when exceeded, and fallback_model downgrades when the primary model 5xxs. External: token-usage monitoring with budget alerts, and idempotent tool side effects (idempotency keys) so agent retries cannot double-charge or double-send.

Official references

This guide is current as of August 2026 (Claude Agent SDK docs + official quickstarts). The SDK API evolves; check the spec version every six months.