Claude Code Agent SDK: Build Custom Agents Guide

Agent SDK is the programmatic gateway to building production AI agents with the same engine that powers the Claude Code CLI. Instead of typing prompts into a terminal, you write Python or TypeScript code that drives Claude autonomously through file reads, shell commands, and tool calls. This guide covers installation, configuration, streaming, custom tools, MCP integration, and real deployment patterns.

Claude Code Agent SDK: Build Custom Agents Guide — title card

agent sdk: What You’ll Learn

The agent sdk lets you embed Claude’s agentic capabilities directly into applications, pipelines, and internal tools. You will learn how to install the library in Python and TypeScript, authenticate with Anthropic or cloud providers, configure the agent loop with permissions and budgets, define custom tools, integrate MCP servers, and handle streaming conversations programmatically.

This tutorial assumes you have used Claude Code interactively and understand basic agent concepts like tool use and context windows. If you are new to Claude Code itself, start with the getting started guide and then return here to learn the agent sdk for programmatic control.

What Is the Claude Agent SDK?

The Claude Agent SDK, formerly known as the Claude Code SDK, is a library that exposes the full agent loop of Claude Code to developers. When you use the Claude Code CLI, an internal engine manages conversation turns, decides which tools to call, executes those tools against your filesystem and shell, and feeds the results back to the model. The agent sdk packages that same engine into importable functions you can call from your own code.

This means you get identical behavior whether you are driving Claude from the terminal or from a script. The same tool implementations for reading files, running bash commands, searching code, and editing text are available. The same context management, permission system, and MCP integration work under the hood. The difference is that you control the session programmatically, which opens up use cases the interactive CLI cannot serve.

Consider what becomes possible. You can build a service that automatically reviews pull requests by spawning an agent for each diff. You can create a custom developer tool that scans a codebase for security issues and files structured reports. You can orchestrate multiple agents that collaborate on a complex refactoring task. All of these run without human interaction, governed by the permissions and budgets you define in code.

The sdk also powers batch processing scenarios that would be impractical interactively. Imagine needing to add type annotations to two hundred Python files. Running this as a single interactive session would exhaust the context window long before finishing. With the agent sdk, you write a loop that processes each file in its own short session, accumulating results and logging failures. Each session starts fresh, stays focused on one file, and costs a few cents. The total runs in minutes instead of hours.

Another common pattern is embedding agent capabilities into internal platforms. A company might build a Slack bot that lets engineers query their codebase through natural language, with the agent sdk powering the search and analysis behind the scenes. Or a compliance dashboard could use agents to continuously audit code changes against security policies, flagging violations before they reach production. The sdk makes these applications straightforward to build because it handles the hard parts: tool execution, context management, and the reasoning loop.

Python and TypeScript First-Class Support

The agent sdk ships as two parallel packages with feature parity. The Python package targets version 3.10 and above, while the TypeScript package requires Node.js 18 or later. Both expose the same core functions: query() for one-off tasks, a client class for multi-turn conversations, and utilities for defining custom tools and in-process MCP servers. Choose the language that fits your stack rather than learning a different API per ecosystem.

The naming changed in recent releases. The packages were previously called claude-code-sdk for Python and @anthropic-ai/claude-code for TypeScript. Those names are deprecated and will not receive updates. If you find old tutorials referencing them, mentally substitute the new names. The API surface also evolved, so always check the current documentation rather than relying on community posts from before the rename.

Installation: Python and TypeScript

Getting started requires a single package install in your preferred language. The agent sdk has minimal dependencies because it communicates with Claude Code through a subprocess rather than embedding the model directly. This architecture means the sdk stays lightweight while delegating the heavy lifting to the Claude Code runtime installed on your system.

Python Installation

Install the Python package with pip. The package name is claude-agent-sdk, and it requires Python 3.10 or newer. Create a virtual environment first to avoid polluting your global packages.

python -m venv .venv
source .venv/bin/activate
pip install claude-agent-sdk

After installation, verify the import works and check that the Claude Code runtime is accessible on your path. The sdk shells out to the claude binary, so if you have been using the CLI, you are already set up.

TypeScript Installation

For TypeScript and JavaScript projects, install the package from npm. The name is @anthropic-ai/claude-agent-sdk and it requires Node.js 18 or later. Use whichever package manager your project already uses.

npm install @anthropic-ai/claude-agent-sdk

If you see errors about the claude binary not being found, install the Claude Code CLI globally first. The agent sdk is a wrapper around that runtime, not a standalone implementation.

Authentication and Provider Configuration

The agent sdk needs credentials to reach the model. The simplest path is an Anthropic API key exported as an environment variable. Set it before running your script so the sdk picks it up automatically.

export ANTHROPIC_API_KEY=sk-ant-your-key-here

Beyond direct Anthropic access, the sdk supports routing requests through cloud providers. This matters for organizations that have existing commitments or compliance requirements around specific platforms. Set an environment variable to activate the provider you need.

ProviderEnvironment VariableAdditional Configuration
Anthropic (direct)ANTHROPIC_API_KEYNone required
Amazon BedrockCLAUDE_CODE_USE_BEDROCK=1AWS credentials via standard chain
Google Vertex AICLAUDE_CODE_USE_VERTEX=1GCP credentials and project region
Microsoft FoundryProvider-specific flagsEndpoint URL and token

For Bedrock and Vertex, the sdk respects the standard credential chain, which means it works with IAM roles, service accounts, and local credential files without extra configuration. This makes deployment to cloud environments straightforward since you rarely need to pass secrets explicitly in code.

Basic Usage: The query() Function

The query() function is the entry point for one-off agent tasks. It takes a prompt and an options object, then returns an asynchronous iterator of messages. Each message represents a step in the agent’s execution: initialization, tool calls, tool results, and the final result. You consume the iterator to observe progress and capture the outcome.

Python Example

Here is a minimal Python script that asks the agent to inspect the current directory. Notice how the options object controls which tools the agent may use. Restricting tools is a security best practice: give the agent only what it needs for the task at hand.

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions

async def main():
    options = ClaudeAgentOptions(
        allowed_tools=["Bash", "Glob", "Read"],
        max_turns=10,
    )
    async for message in query(
        prompt="List all Python files and count their total lines",
        options=options,
    ):
        if hasattr(message, "result"):
            print(message.result)

asyncio.run(main())

The query() function yields different message types as the agent works. You typically check for the result attribute to get the final text output. But the iterator also yields intermediate messages showing which tools the agent called and what those tools returned, which is useful for logging and debugging.

TypeScript Example

The TypeScript API mirrors the Python one closely. The query function returns an async iterable, and you consume it with a for await loop. Option names use camelCase instead of snake_case to match JavaScript conventions.

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

const options = {
  allowedTools: ["Bash", "Glob", "Read"],
  maxTurns: 10,
};

for await (const message of query({
  prompt: "List all TypeScript files and count their total lines",
  options,
})) {
  if ("result" in message) {
    console.log(message.result);
  }
}

Both examples produce identical agent behavior. The choice between Python and TypeScript comes down to your existing infrastructure, team skills, and how the agent output will be consumed downstream.

ClaudeAgentOptions: Configuration Deep Dive

The ClaudeAgentOptions class is where you shape the agent’s behavior. It accepts dozens of parameters that control everything from the model choice to permission handling. Understanding the key options helps you build agents that are safe, cost-effective, and tailored to their tasks.

Model and Prompting

Specify the model with the model parameter. You can also inject a custom system prompt that augments or replaces the default one. This is how you give the agent domain-specific instructions, such as coding standards for your project or a persona for customer-facing interactions.

options = ClaudeAgentOptions(
    model="claude-sonnet-4-20250514",
    system_prompt="You are a security-focused code reviewer. "
                  "Flag any potential vulnerabilities.",
    allowed_tools=["Read", "Grep", "Glob"],
    permission_mode="plan",
)

Permission Modes

Permission modes control how the agent handles tool requests that could modify your environment. The default mode pauses for user confirmation on potentially destructive actions, which is appropriate for interactive use but blocks automated pipelines. Choose the mode that matches your deployment context.

Permission ModeBehaviorBest For
defaultPrompts for confirmation on writesInteractive development
acceptEditsAuto-accepts file edits onlyControlled automation
planPlans only, executes nothingAnalysis and review
bypassPermissionsNo confirmation for any toolTrusted CI environments

For production pipelines, bypassPermissions combined with max_budget_usd gives you fully autonomous execution with a cost ceiling. Never use bypass mode without a budget cap in untrusted contexts, because an agent that loops without limits can consume significant resources before failing.

Budget and Turn Limits

Two parameters act as safety rails. The max_turns parameter caps how many conversation turns the agent can take before stopping, preventing infinite loops. The max_budget_usd parameter sets a dollar ceiling on the entire session. When either limit is hit, the agent returns a result message explaining why it stopped.

options = ClaudeAgentOptions(
    max_turns=25,
    max_budget_usd=2.00,
    cwd="/path/to/project",
)

The cwd parameter sets the working directory for tool execution. This is important for scoping file operations: set it to the project root so the agent cannot wander into unrelated directories.

Streaming vs Single-Message Input Modes

The agent sdk supports two patterns for feeding prompts to the agent. Understanding when to use each prevents architectural mistakes that waste tokens or break conversational flow.

Streaming Input Mode

Streaming mode is the default when you use the client class. You create a conversation, send an initial message, and then can queue additional messages while the agent is still processing. This enables real-time interaction where you react to the agent’s partial output by sending follow-up instructions or interrupts.

from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions

client = ClaudeSDKClient(ClaudeAgentOptions(
    allowed_tools=["Read", "Bash"],
))
await client.connect()

Send the first message

await client.query("Examine the auth module and summarize its structure")

While processing, you can interrupt or add context

The client queues messages and processes them in order

Retrieve the final result

result = await client.receive_result() await client.disconnect()

Streaming mode also supports partial messages. When you enable include_partial_messages=True, the iterator yields token-level updates as the model generates them. This is how you build responsive user interfaces that display text as it arrives rather than waiting for the full response.

Single Message Input Mode

Single message mode is simpler and fits tasks that do not require mid-stream intervention. You call query() with a prompt and set continue_conversation=True if you want subsequent calls to share context. Without that flag, each query() call starts a fresh session.

for await (const msg of query({
  prompt: "Find all TODO comments in the codebase",
  options: { continueConversation: true },
})) {
  if ("result" in msg) console.log(msg.result);
}

// This second call sees context from the first
for await (const msg of query({
  prompt: "Which of those TODOs mention authentication?",
  options: { continueConversation: true },
})) {
  if ("result" in msg) console.log(msg.result);
}

Use single message mode for batch jobs, one-shot scripts, and CI tasks where you know the full prompt upfront. Use streaming mode for interactive tools, debugging assistants, and any scenario where you might redirect the agent based on early results.

ClaudeSDKClient: Multi-Turn Conversations

The ClaudeSDKClient class, available in the Python package, provides a richer interface for conversations that span many turns. Unlike the stateless query() function, the client retains context between queries, manages a persistent connection, and supports interrupts that can redirect the agent mid-execution.

import asyncio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions

async def conversation():
    client = ClaudeSDKClient(ClaudeAgentOptions(
        allowed_tools=["Read", "Grep", "Edit", "Bash"],
        permission_mode="acceptEdits",
        max_budget_usd=5.00,
    ))
    await client.connect()

    # First turn: explore
    await client.query("Read the main entry point and explain the architecture")
    await client.receive_result()

    # Second turn: act on the explanation
    await client.query("Based on what you found, add input validation "
                       "to the user registration handler")
    result = await client.receive_result()
    print(f"Cost so far: ${result.total_cost_usd:.2f}")

    await client.disconnect()

asyncio.run(conversation())

The client shines when you build interactive tools where a human reviews each step before directing the next. Because context persists, the agent does not need to re-read files or re-establish understanding between turns. This keeps token costs lower than calling query() repeatedly with full context in each prompt.

Interrupts and Queued Messages

One of the most powerful features of the client is the ability to interrupt the agent while it is working. You can send a message that redirects its attention, cancels the current operation, or adds urgent context. This mirrors how you would interact with a colleague: you do not wait for them to finish a long task before telling them something important came up.

# Send an interrupt while the agent is processing
await client.interrupt("Stop searching, the bug is in the payment module")

Queue a follow-up for after the current task completes

await client.query("Once you finish, run the test suite")

Queued messages process in order after the current turn completes. Interrupts take effect immediately, causing the agent to incorporate the new instruction into its current reasoning. Use interrupts sparingly, as frequent redirection can confuse the agent’s plan.

MCP Integration: External and In-Process Servers

The Model Context Protocol, covered in detail in the Claude Code MCP Servers Guide, lets you extend the agent with external tools and data sources. The agent sdk fully supports MCP, giving you two integration paths: connecting to external MCP servers or creating in-process servers that run within your application.

External MCP Servers

External servers communicate over stdio, HTTP, or SSE transports. You register them in the options object and the sdk handles connection lifecycle. This is ideal for reusing MCP servers you already run for the interactive CLI.

options = ClaudeAgentOptions(
    mcp_servers={
        "github": {
            "command": "npx",
            "args": ["-y", "@modelcontextprotocol/server-github"],
            "env": {"GITHUB_TOKEN": "ghp_your_token"},
        },
        "database": {
            "url": "https://internal-mcp.example.com/sse",
        },
    },
    allowed_tools=["mcp__github__*"],
)

Tool names from MCP servers follow the pattern mcp__servername__toolname. You can allow all tools from a server with a wildcard like mcp__github__* or list specific tools individually. This gives you fine-grained control over which external capabilities the agent can access.

In-Process SDK MCP Servers

For tools that do not warrant a separate process, the agent sdk lets you define MCP tools directly in your code. These run in-process, which means no subprocess overhead and no serialization cost. This is the recommended approach for custom tools that are specific to your application.

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

const fetchWeather = tool(
  "get-weather",
  "Fetch current weather for a city",
  { city: "string" },
  async (args) => {
    const res = await fetch(`https://wttr.in/${args.city}?format=3`);
    const text = await res.text();
    return { content: [{ type: "text", text }] };
  }
);

const weatherServer = createSdkMcpServer({
  name: "weather",
  version: "1.0.0",
  tools: [fetchWeather],
});

for await (const msg of query({
  prompt: "What is the weather in Tokyo?",
  options: {
    mcpServers: { weather: weatherServer },
    allowedTools: ["mcp__weather__get-weather"],
  },
})) {
  if ("result" in msg) console.log(msg.result);
}

In-process servers are perfect for tools that need access to your application state, such as querying an internal database, calling private APIs, or reading from in-memory caches. Because they run in the same process, you can share variables and objects without serialization.

Custom Tools with the @tool() Decorator

Defining custom tools is where the agent sdk becomes genuinely powerful for building specialized agents. The tool() function and create_sdk_mcp_server() combination lets you wrap any Python or TypeScript function as an MCP tool that the agent can call autonomously.

from claude_agent_sdk import tool, create_sdk_mcp_server, query, ClaudeAgentOptions
import asyncio

@tool("calculate", "Perform arithmetic on two numbers", {"a": float, "b": float})
async def calculate(args):
    a, b = args["a"], args["b"]
    return {
        "content": [{
            "type": "text",
            "text": f"{a} + {b} = {a + b}, "
                    f"{a} * {b} = {a * b}, "
                    f"{a} / {b} = {a / b if b != 0 else 'undefined'}",
        }]
    }

@tool("lookup_user", "Find a user by email", {"email": str})
async def lookup_user(args):
    # In practice, query your database here
    users = {"alice@example.com": {"name": "Alice", "role": "admin"}}
    user = users.get(args["email"], {"error": "User not found"})
    return {"content": [{"type": "text", "text": str(user)}]}

calc_server = create_sdk_mcp_server(
    name="internal-tools",
    version="1.0.0",
    tools=[calculate, lookup_user],
)

async def main():
    options = ClaudeAgentOptions(
        mcp_servers={"internal": calc_server},
        allowed_tools=["mcp__internal__calculate", "mcp__internal__lookup_user"],
        max_turns=8,
    )
    async for message in query(
        prompt="What is 42 times 17, and what role does alice@example.com have?",
        options=options,
    ):
        if hasattr(message, "result"):
            print(message.result)

asyncio.run(main())

The decorator captures the tool name, description, and argument schema. The description matters because the model reads it to decide when to call the tool. Write descriptions that explain what the tool does and when it is appropriate, not just its technical interface.

Custom Permission Handlers

For fine-grained security control, the agent sdk accepts a can_use_tool callback that intercepts every tool request. Your handler receives the tool name and arguments, then returns a boolean or a permission decision. This lets you enforce policies like blocking file writes outside a project directory or requiring additional checks before spending above a threshold.

async def permission_handler(tool_name, args):
    # Block any bash command that includes 'rm'
    if tool_name == "Bash" and "rm " in args.get("command", ""):
        return {"behavior": "deny", "message": "Deletion commands blocked by policy"}
    # Allow everything else
    return {"behavior": "allow"}

options = ClaudeAgentOptions(
    allowed_tools=["Bash", "Read", "Edit"],
    can_use_tool=permission_handler,
)

Custom handlers are essential for production deployments where you cannot rely on the agent’s judgment alone. Combine them with budget limits and restricted tool lists for defense in depth.

Session Management: Resume, Continue, Tag

The agent sdk provides a session management layer that mirrors what the --resume flag does in the CLI. Every query returns a session identifier that you can use to continue a conversation later, inspect the message history, or organize sessions with tags and custom names.

from claude_agent_sdk import query, ClaudeAgentOptions, list_sessions, get_session_messages
import asyncio

async def demonstrate_sessions():
    # Start a session and capture its ID
    options = ClaudeAgentOptions(allowed_tools=["Read", "Grep"])
    async for msg in query(
        prompt="Find all API endpoints in the codebase",
        options=options,
    ):
        if hasattr(msg, "session_id"):
            session_id = msg.session_id
        if hasattr(msg, "result"):
            print(msg.result)

    # List all sessions
    sessions = await list_sessions()
    for s in sessions:
        print(f"Session {s.session_id}: {s.summary}")

    # Retrieve messages from a specific session
    messages = await get_session_messages(session_id)
    for m in messages[-3:]:
        print(f"  {m.type}: {getattr(m, 'content', '')[:80]}")

asyncio.run(demonstrate_sessions())

Resuming a session loads the full conversation context back into the agent, which is valuable for long-running tasks that span multiple executions. Instead of re-explaining what you already discussed, the agent picks up where it left off. Use ClaudeAgentOptions(resume=session_id) to reconnect.

You can also tag sessions for organization and rename them for readability. Tags are useful for filtering sessions by project, team, or purpose when you have many running. This metadata helps when building dashboards that monitor agent activity across your organization.

Practical Session Patterns

A typical production workflow uses sessions to implement checkpointing for long tasks. Instead of running one massive session that might fail midway, you break the work into stages, each its own session that resumes from the previous one’s context. If stage three fails, you retry just that stage rather than starting over from scratch. This pattern dramatically improves reliability for multi-hour agent jobs.

Session tagging also enables cost attribution. Tag each session with the initiating user or project identifier, then aggregate costs by tag in your monitoring system. This tells you exactly which features or users are consuming agent resources, which is essential for teams where multiple services share the same API key.

Message Types and Result Handling

As the agent executes, the sdk yields different message types. Understanding this stream lets you build robust handlers that log activity, extract results, and detect failures gracefully. The table summarizes the core message types and when you encounter them.

Message TypeContainsWhen Emitted
SystemMessageInit or compaction eventsSession start and context compaction
AssistantMessageText output and tool call requestsEach model turn
UserMessageTool results fed back to modelAfter each tool execution
ResultMessageFinal result and session metadataSession ends, limit hit, or error
StreamEventPartial token outputDuring generation if enabled

The ResultMessage deserves particular attention because it tells you why the session ended. Check its subtype to handle different outcomes appropriately.

async for message in query(prompt="...", options=options):
    if hasattr(message, "subtype"):
        if message.subtype == "success":
            print(f"Done. Cost: ${message.total_cost_usd:.4f}")
        elif message.subtype == "error_max_turns":
            print("Agent hit the turn limit before finishing")
        elif message.subtype == "error_max_budget_usd":
            print("Agent exceeded the budget cap")
        elif message.subtype == "error_during_execution":
            print(f"Execution error: {message.result}")
        elif message.subtype == "error_max_structured_output_retries":
            print("Agent failed to produce valid structured output")

Handling each subtype lets your application respond intelligently. If the turn limit was hit, you might resume with a higher limit. If the budget was exceeded, you might log it for investigation. If an execution error occurred, retrying after a delay often succeeds.

Working with AssistantMessage Tool Calls

AssistantMessage objects contain more than just text. They include structured tool call requests that show exactly which tools the agent decided to use and the arguments it passed. Inspecting these calls is invaluable for debugging agent behavior and building audit trails. When an agent makes an unexpected decision, the tool call history reveals the chain of reasoning that led there.

async for message in query(prompt="...", options=options):
    if hasattr(message, "content"):
        for block in message.content:
            if hasattr(block, "type"):
                if block.type == "text":
                    print(f"Agent says: {block.text[:100]}")
                elif block.type == "tool_use":
                    print(f"Tool call: {block.name}({block.input})")

Logging tool calls is especially important in regulated environments where you must demonstrate that an agent did not access sensitive files or execute prohibited commands. Persist the full tool call log alongside the result, and your compliance team has a complete record of what the agent did and why.

Agent SDK vs Anthropic Client SDK

A common source of confusion is the difference between the Agent SDK and the Anthropic Client SDK. Both let you interact with Claude programmatically, but they operate at very different abstraction levels. Choosing the right one depends on how much of the agent loop you want to build yourself.

The Anthropic Client SDK provides raw API access. You send messages, receive responses, and handle everything else yourself. If you want tool use, you must implement the tool execution loop: parse the model’s tool call, execute the tool, format the result, and send it back. This gives maximum control but requires significant code for anything beyond simple chat.

The Agent SDK wraps that entire loop. You give it a prompt and a set of tools, and it autonomously decides which tools to call, executes them, reads the results, and continues until the task is complete or a limit is reached. You do not write a tool loop at all. The agent handles multi-step reasoning, file operations, and command execution the same way the Claude Code CLI does.

AspectAnthropic Client SDKAgent SDK
Abstraction levelRaw API messagesFull agent loop
Tool executionYou implementBuilt-in, autonomous
File and shell accessNoneBuilt-in via tools
Permission systemYou buildBuilt-in modes
Context managementManualAutomatic
Best forCustom chat appsAgentic automation

Use the Client SDK when you need fine-grained control over the conversation, are building a chat interface, or want to integrate Claude into an existing framework without agent behavior. Use the Agent SDK when you want Claude to actually do things: read files, run commands, make edits, and complete multi-step tasks autonomously.

A Worked Example

Let us build a realistic tool: an automated code reviewer that runs on every pull request. The goal is to have the agent read the diff, analyze the changes for bugs and style issues, and post a structured comment back to the pull request. This is a common production use case that demonstrates the agent sdk’s strengths.

We start by defining the configuration. The agent needs read access to the repository and the ability to run git commands, but it should not edit files. We set the permission mode to plan so the agent can only analyze, never modify. We cap the budget at one dollar per review to keep costs predictable across many pull requests.

from claude_agent_sdk import query, ClaudeAgentOptions
import asyncio, json

async def review_pull_request(repo_path, pr_branch, base_branch="main"):
    options = ClaudeAgentOptions(
        model="claude-sonnet-4-20250514",
        allowed_tools=["Bash", "Read", "Grep"],
        permission_mode="plan",
        max_turns=15,
        max_budget_usd=1.00,
        cwd=repo_path,
        system_prompt=(
            "You are a senior code reviewer. Analyze the diff between "
            "branches and report bugs, security issues, and style problems. "
            "Be concise. Cite file and line numbers."
        ),
    )

    prompt = (
        f"Review the changes in branch '{pr_branch}' against '{base_branch}'. "
        f"Run: git diff {base_branch}...{pr_branch} "
        f"Then analyze every changed file for: "
        f"1) Logic bugs  2) Security vulnerabilities  "
        f"3) Missing error handling  4) Style violations. "
        f"Return a JSON array of findings."
    )

    findings = []
    async for message in query(prompt=prompt, options=options):
        if hasattr(message, "subtype") and message.subtype == "success":
            findings.append(message.result)
            print(f"Review cost: ${message.total_cost_usd:.4f}")
        elif hasattr(message, "subtype") and "error" in message.subtype:
            print(f"Review failed: {message.subtype}")

    return findings

Called from a CI pipeline

results = asyncio.run(review_pull_request( repo_path="/repos/myproject", pr_branch="feature/add-auth", ))

The agent reads the diff, examines each changed file for the four categories we specified, and returns structured findings. Because we set permission_mode="plan", it cannot accidentally modify code even if it tries. The budget cap ensures a complex pull request cannot consume more than a dollar.

To make this production-ready, wrap the function in a webhook handler that triggers on pull request events. Parse the branch name from the webhook payload, call review_pull_request, and post the findings back through your platform’s API. Log the cost from each result message to track spending over time. For teams that review dozens of pull requests daily, this pattern saves significant engineer time while catching issues before human review.

The same architecture extends to other automated tasks. Swap the prompt and allowed tools to build a documentation generator, a test writer, a dependency updater, or a security scanner. The agent sdk handles the reasoning and tool execution; you provide the task definition and the integration glue.

agent sdk: Common Mistakes to Avoid

Building agents programmatically introduces failure modes that do not exist in interactive use. These mistakes are common enough that recognizing them upfront saves hours of debugging and prevents costly runaway sessions.

  • Using bypassPermissions without a budget cap: The most dangerous combination in production. An agent with no permission checks and no spending limit can loop indefinitely, reading files and calling tools until your API bill spikes. Always pair bypassPermissions with max_budget_usd and max_turns.
  • Installing the deprecated package names: Tutorials referencing claude-code-sdk or @anthropic-ai/claude-code lead you to unmaintained packages. The current names are claude-agent-sdk and @anthropic-ai/claude-agent-sdk. Old packages may have different APIs that silently break your code.
  • Allowing all tools by default: Passing allowed_tools=[] or a wildcard without thought gives the agent access to every built-in tool, including file deletion and arbitrary shell commands. Restrict the list to exactly what the task requires. A code reviewer does not need Edit or Write.
  • Ignoring result subtypes in error handling: Treating every ResultMessage as success means budget exceeded, turn limit, and execution errors all look like completed work. Check the subtype field and handle each case differently, or your pipeline will report false successes.
Claude Code Agent SDK: Build Custom Agents Guide — key concepts card

agent sdk: Best Practices

  • Always set both max_turns and max_budget_usd on every query, even in development, so a logic error cannot spiral into unbounded spending during testing.
  • Restrict allowed_tools to the minimum set the task requires, treating tool access as a privilege. A documentation generator does not need shell access; a code reviewer does not need write permissions.
  • Use the plan permission mode for analysis-only tasks, acceptEdits for controlled automation, and reserve bypassPermissions for fully trusted CI environments with budget caps.
  • Write detailed tool descriptions when using @tool() or create_sdk_mcp_server(), because the model reads the description to decide when and how to call your tool.
  • Log every ResultMessage subtype and cost to a monitoring system so you can detect spending anomalies, recurring failures, and performance trends across your agent fleet.
Claude Code Agent SDK: Build Custom Agents Guide — best practices card

Agent SDK: Knowledge Check

Test your understanding of the Claude Code Agent SDK.

1 / 5

What is the correct Python package name for the Agent SDK?

2 / 5

Which function handles one-off agent tasks with streaming output?

3 / 5

What does the max_budget_usd option control?

4 / 5

How do you define a custom in-process MCP tool in Python?

5 / 5

How do you resume a previous session with context?

0%

agent sdk: Frequently Asked Questions

What is the difference between the Agent SDK and the Client SDK?

The Client SDK provides raw API access where you build the tool loop yourself. The Agent SDK wraps the full agent loop, autonomously executing tools, managing context, and handling permissions. Use the Client SDK for chat apps; use the Agent SDK for automation that requires Claude to read files and run commands.

Do I need the Claude Code CLI installed to use the Agent SDK?

Yes. The Agent SDK communicates with the Claude Code runtime via a subprocess, so the claude binary must be on your system path. Install the CLI first, verify it works interactively, then add the sdk package to your project.

Can I use the Agent SDK with Amazon Bedrock or Google Vertex?

Yes. Set CLAUDE_CODE_USE_BEDROCK=1 for Bedrock or CLAUDE_CODE_USE_VERTEX=1 for Vertex AI. The sdk respects the standard credential chain for each provider, so IAM roles and service accounts work without extra configuration.

How do I resume a previous conversation in the Agent SDK?

Capture the session_id from any message yielded by query(), then pass it via ClaudeAgentOptions(resume=session_id) on your next call. The agent loads the full prior context and continues the conversation without re-explaining.

What is the difference between query() and ClaudeSDKClient?

The query() function handles one-off tasks as an async iterator. ClaudeSDKClient maintains a persistent connection for multi-turn conversations, supports interrupts, and queues messages. Use query() for batch jobs and the client for interactive tools.

Agent SDK wraps the Claude Code engine into importable Python and TypeScript functions, letting you build autonomous agents that read files, run commands, and complete multi-step tasks programmatically. Master the options object for permissions and budgets, define custom tools for your domain, integrate MCP servers for extensibility, and always handle result subtypes to build reliable production agents.

Continue Learning

For the official reference, see the Claude Agent SDK documentation and the Claude Code MCP Servers Guide for extending agents with external tools.