The Agent SDK next to the other ways to run Claude
The Agent SDK puts the loop behind Claude Code into a library: the same built-in tools, permission system, sessions, hooks and MCP support, driven from your own Python or TypeScript code. The GitHub Actions lesson already used it, since the action is built on the SDK, and claude -p from the headless lesson is its command-line form. Anthropic's overview sorts the four options by who runs the agent.
The library runs in Python and TypeScript only. From another language, start the CLI as a subprocess with -p and --output-format json. For a single step in a script, claude -p is the smaller tool. Reach for the SDK when your own code has to answer approval prompts, define tools that run inside your process or keep sessions for several users.
Each language has its own repository on GitHub, where the changelog and issues live. On GitHub, the Python one has the larger following.
8.4k
stars on claude-agent-sdk-python
Source: anthropics/claude-agent-sdk-python on GitHub (read November 5, 2026)
1.4k
forks of the Python SDK
Source: anthropics/claude-agent-sdk-python on GitHub (read November 5, 2026)
1.9k
stars on claude-agent-sdk-typescript
Source: anthropics/claude-agent-sdk-typescript on GitHub (read November 5, 2026)
246
forks of the TypeScript SDK
Source: anthropics/claude-agent-sdk-typescript on GitHub (read November 5, 2026)
| You want to | Use | What you get |
|---|---|---|
| Run Claude Code's agent inside your own app, in a process you operate | Agent SDK | A library with Claude Code's tools, permissions, sessions and hooks |
| Work interactively or run one-off tasks in a terminal | Claude Code CLI | The terminal interface built for daily use |
| Call the Claude API from your own code | Client SDK | Direct API access. You write the tool loop, or let the beta tool runner drive it |
| Have Anthropic host the agent | Managed Agents | Anthropic runs the agent loop for you, with sessions in its cloud sandbox or your own self-hosted one |
Install it and run a first agent
You need Node.js 18.14 or newer, or Python 3.10 or newer, and an API key from the Claude Console. Both packages bundle a native Claude Code binary, so most installs need no separate Claude Code. Anthropic's quickstart builds an agent that fixes two crashing functions in a file named utils.py. This is that agent in TypeScript.
The prompt is the task, options is the configuration and query returns the stream. Claude picks the tools. Here allowedTools pre-approves three of them and acceptEdits approves the file changes. The steps after the script install it and run it.
import { query } from "@anthropic-ai/claude-agent-sdk";
// Agentic loop: streams messages as Claude works
for await (const message of query({
prompt: "Review utils.py for bugs that would cause crashes. Fix any issues you find.",
options: {
allowedTools: ["Read", "Edit", "Glob"], // Auto-approve these tools
permissionMode: "acceptEdits" // Auto-approve file edits
}
})) {
// Print human-readable output
if (message.type === "assistant" && message.message?.content) {
for (const block of message.message.content) {
if ("text" in block) {
console.log(block.text); // Claude's reasoning
} else if ("name" in block) {
console.log("Tool: " + block.name); // Tool being called
}
}
} else if (message.type === "result") {
console.log("Done: " + message.subtype); // Final result
}
}- Make a folder and install:
npm init -y,npm pkg set type=module,npm install @anthropic-ai/claude-agent-sdkandnpm install --save-dev tsx. For Python, create a virtual environment and runpip install claude-agent-sdk, or useuv add claude-agent-sdk. - Export
ANTHROPIC_API_KEYin the shell that runs the agent. The SDK reads the process environment and does not load.envfiles, so load one yourself with a package such as dotenv. - Save the script as
agent.tsbeside autils.pywith a function that crashes on an empty list, then runnpx tsx agent.ts. The agent prints its reasoning and each tool it calls, and ends withDone: success.
| Concept | TypeScript | Python |
|---|---|---|
| Options object | A plain object | ClaudeAgentOptions |
| Pre-approved tools | allowedTools | allowed_tools |
| Permission mode | permissionMode | permission_mode |
| Approval callback | canUseTool | can_use_tool |
| Turn and spend caps | maxTurns, maxBudgetUsd | max_turns, max_budget_usd |
| Settings to load | settingSources | setting_sources |
| Fork a session | forkSession | fork_session |
| Spot the result | message.type === "result" | isinstance(message, ResultMessage) |
On Amazon Bedrock, Google Cloud's Agent Platform or Microsoft Foundry, set CLAUDE_CODE_USE_BEDROCK=1, CLAUDE_CODE_USE_VERTEX=1 or CLAUDE_CODE_USE_FOUNDRY=1 and configure that cloud's credentials. Claude Platform on AWS takes CLAUDE_CODE_USE_ANTHROPIC_AWS=1 and ANTHROPIC_AWS_WORKSPACE_ID, with AWS credentials.
Read the stream, then check the result
One query() call runs the whole loop. Claude answers or asks for a tool, the SDK runs it and feeds the output back, and this repeats until a reply has no tool calls. You receive a system message with the init data, an assistant message for each content block, a user message for each tool result and finally a result. Stream events join them only when you turn on partial messages. Anthropic's agent loop page has the full list.
Iterate to the end instead of breaking on the result, because a few trailing system events can still arrive after it. Then branch on subtype. The loop below prints the text and the tool calls, and says what stopped the run.
import { query } from "@anthropic-ai/claude-agent-sdk";
try {
for await (const message of query({ prompt: "Summarize this project" })) {
if (message.type === "assistant") {
// Each assistant message carries one content block
for (const block of message.message.content) {
if (block.type === "text") {
console.log("Claude: " + block.text);
} else if (block.type === "tool_use") {
console.log("Tool call: " + block.name);
}
}
}
if (message.type === "result") {
if (message.subtype === "success") {
console.log(message.result);
} else {
console.log("Stopped: " + message.subtype);
}
}
}
} catch (error) {
// A single-shot query() throws after yielding an error result.
console.log("Session ended with an error: " + error);
}- Every result carries
total_cost_usd,usage,num_turnsandsession_id, even an error one, so you can log spend and resume.usagecovers the main loop only.modelUsagecovers the whole tree, subagents included. maxTurnscounts each turn that calls tools plus one for the final text-only reply. With a cap of 2, the four-turn test fix in Anthropic's example would stop before the edit.maxBudgetUsdis checked after each response arrives, so spend can pass the cap by one response plus any subagents still running. Subagent spend counts toward it, and spawning another one after the cap fails withBudget limit reached. Those cap behaviors need Claude Code v2.1.217 or later.- A single-shot
query()yields the error result and then raises, so the try and catch above is the normal shape. To catch a refusal, teststop_reasonforrefusal.
| Subtype | What happened | result present |
|---|---|---|
success | Claude finished the task | Yes |
error_max_turns | The maxTurns limit came first | No |
error_max_budget_usd | The maxBudgetUsd limit came first | No |
error_during_execution | An error interrupted the loop, such as a cancelled request | No |
error_max_structured_output_retries | No valid structured output arrived within the retry limit | No |
Permissions: what runs without asking
With Claude Code v2.1.302 or later, an SDK app starts in default mode unless you set a mode or its settings files do. In default, a call that needs approval is denied unless an allow rule, a hook or your callback approves it. A tool call that comes back denied in a headless run usually traces back to this.
Two options do most of the work. allowedTools auto-approves what it lists, and the other tools still exist: an unlisted tool falls through to the mode. disallowedTools is the block. A bare name such as Bash removes the tool from Claude's context, while Bash(rm *) leaves it available and denies matching calls in every mode as written, so /bin/rm slips past it. A scoped allow entry such as Bash(npm test *) works the same way in reverse and approves only matching calls.
For a locked-down agent, list the read tools and set dontAsk, so every call that would prompt is denied. Behind both options sits one fixed order, which the permissions page lists and the steps below repeat.
const options = {
allowedTools: ["Read", "Glob", "Grep"],
permissionMode: "dontAsk"
};- Hooks run first. A hook can deny a call, and a hook that allows it does not skip the deny and ask rules after it.
- Deny rules, from
disallowedToolsor settings files. A match denies the call in every mode,bypassPermissionsincluded. - Ask rules from settings files. A match sends the call to your callback, again even in
bypassPermissions. - The permission mode.
acceptEditsapproves file edits and filesystem commands such asmkdir,mvandcpinside the working directory, andbypassPermissionsapproves what reaches it. - Allow rules, from
allowedToolsand settings files. A match approves the call. - Your
canUseToolcallback decides whatever is left. IndontAskmode this step is skipped and the call is denied.
allowedTools does not constrain bypassPermissions. With allowedTools: ["Read"] and that mode, every tool is approved, Bash included. TypeScript also needs allowDangerouslySkipPermissions: true for the mode, and on Linux and macOS it will not start as root outside a recognized sandbox.
Approvals in your own code
Your callback fires when nothing earlier resolved the call, and when Claude uses the AskUserQuestion tool to put a question to the person. It receives the tool name and its input. It returns an allow, with optional edited input, or a deny with a message that Claude reads as the tool result. Execution waits until you answer. The full contract is on the user input page.
Auto-approved tools never reach canUseTool.
import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({
prompt: "Create a test file in /tmp and then delete it",
options: {
canUseTool: async (toolName, input) => {
// Display the tool request
console.log("Tool: " + toolName, input);
// Ask the user with your own prompt helper
const response = await prompt("Allow this action? (y/n): ");
if (response.toLowerCase() === "y") {
// Allow: the tool runs with the original (or modified) input
return { behavior: "allow", updatedInput: input };
}
// Deny: the tool does not run, and Claude sees the message
return { behavior: "deny", message: "User denied this action" };
}
}
})) {
if ("result" in message) console.log(message.result);
}- Put checks that must see every call in a
PreToolUsehook. Hooks run before every other step, and a hook deny holds even inbypassPermissions. - A callback can stay pending indefinitely. If a person may take longer than your process can live, return the
deferdecision from aPreToolUsehook, let the process exit and resume the saved session later. - In Python the callback has a catch that the docs call a required workaround. Pass the prompt as an async generator that yields user messages, and add a
PreToolUsehook that returns{"continue_": True}so the stream stays open. - The TypeScript SDK prints a process warning with the code
CLAUDE_SDK_CAN_USE_TOOL_SHADOWEDonce per query whencanUseToolsits besidebypassPermissionsor a bare tool name inallowedTools, because the callback will not see those calls.
Give Claude a tool of your own
A custom tool has four parts: a unique name, a description Claude reads to decide when to call it, an input schema and an async handler. The schema is a Zod schema in TypeScript and a dict of types in Python. The handler returns a content array of result blocks. You wrap tools in an in-process MCP server, so nothing runs as a separate process. The custom tools page has the Python version.
Pass the server in mcpServers and list each tool by its full name in allowedTools, here mcp__weather__get_temperature. That name joins the server key and the tool name with double underscores.
import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";
// Define a tool: name, description, input schema, handler
const getTemperature = tool(
"get_temperature",
"Get the current temperature at a location",
{
latitude: z.number().describe("Latitude coordinate"),
longitude: z.number().describe("Longitude coordinate")
},
async (args) => {
const response = await fetch(
"https://api.open-meteo.com/v1/forecast?latitude=" + args.latitude +
"&longitude=" + args.longitude +
"¤t=temperature_2m&temperature_unit=fahrenheit"
);
const data: any = await response.json();
// Return a content array: Claude sees this as the tool result
return {
content: [{ type: "text", text: "Temperature: " + data.current.temperature_2m + " F" }]
};
}
);
// Wrap the tool in an in-process MCP server
const weatherServer = createSdkMcpServer({
name: "weather",
version: "1.0.0",
tools: [getTemperature]
});
for await (const message of query({
prompt: "What's the temperature in San Francisco?",
options: {
mcpServers: { weather: weatherServer },
allowedTools: ["mcp__weather__get_temperature"]
}
})) {
if (message.type === "result" && message.subtype === "success") {
console.log(message.result);
}
}- An uncaught exception inside a handler does not stop the loop. The server turns it into an error result carrying the raw message, and Claude can retry or explain. Catch it yourself and return
isError: truewhen Claude needs a message it can act on, such as which request failed. - Custom tools run one at a time by default. Set
readOnlyHint: truein a tool's annotations to let Claude call it in parallel with other read-only tools.
Attach MCP servers other people wrote
Servers you did not write go in the same mcpServers option. A stdio server takes a command and args and runs as a local process. A remote one takes type: "http" and a url, as in this example that connects Anthropic's documentation server. You can also keep the list in a .mcp.json at the project root, which loads through the project setting source. The MCP page covers authentication and errors.
import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({
prompt: "Use the docs MCP server to explain what hooks are in Claude Code",
options: {
mcpServers: {
"claude-code-docs": {
type: "http",
url: "https://code.claude.com/docs/mcp"
}
},
allowedTools: ["mcp__claude-code-docs__*"]
}
})) {
if (message.type === "result" && message.subtype === "success") {
console.log(message.result);
}
}- MCP tools need explicit permission. Without it Claude sees them and cannot call them.
acceptEditsdoes not cover them, andbypassPermissionsdoes but also drops most other prompts, so list the tools inallowedTools. - A wildcard works only after a literal
mcp__and a server name.mcp__github__*approves that server's tools, while["*"]or["mcp__*"]in an allow list is ignored. - A stdio server, or a remote one without a cached tool list, holds up the first turn until it connects, for 30 seconds by default (
MCP_TIMEOUT). When tools seem missing, read each server's status in the init message. - A dot or a space in a server key becomes an underscore in tool names, so
my.servergivesmcp__my_server__list_issues.
Sessions: continue, resume and fork
A session is the conversation history, the prompt, every tool call and result and the replies, and the SDK writes it to disk without being asked. It stores the conversation and not your files, so use file checkpointing when you need to revert edits. Within one query() call the agent already takes as many turns as it needs, and approval prompts are handled in the loop. Sessions matter once separate prompts should share context, as the sessions page describes.
import { query } from "@anthropic-ai/claude-agent-sdk";
let sessionId: string | undefined;
// First run: read the session ID off the result message
for await (const message of query({
prompt: "Analyze the auth module and suggest improvements",
options: { allowedTools: ["Read", "Glob", "Grep"] }
})) {
if (message.type === "result") sessionId = message.session_id;
}
// Later run: resume it, and the agent keeps the earlier analysis
for await (const message of query({
prompt: "Now implement the refactoring you suggested",
options: {
resume: sessionId,
allowedTools: ["Read", "Edit", "Write", "Glob", "Grep"]
}
})) {
if (message.type === "result" && message.subtype === "success") {
console.log(message.result);
}
}- Forking copies the history into a new session and leaves the original untouched. It does not copy files, so a forked agent's edits are real for every session in that directory.
- Session files are local to the machine that wrote them, under
~/.claude/projectsin a folder named from the working directory. To resume on another host, attach asessionStoreadapter, or pass the results you need into a fresh session's prompt. - After
error_max_turnsorerror_max_budget_usd, resume the session with a higher limit. - The experimental TypeScript V2 session API,
createSession(), was removed in SDK 0.3.142. A tutorial that calls it needsquery()withcontinueorresumeinstead.
| What you build | What to use |
|---|---|
| A one-shot task | One query() call and nothing else |
| A chat in one process | Python ClaudeSDKClient, or continue: true in TypeScript |
| Pick up after a restart | continue_conversation=True or continue: true, which resumes the newest session in the directory |
| One session per user | Capture the session ID and pass it to resume |
| Try another approach and keep the original | Fork it with fork_session or forkSession |
| Nothing written to disk | persistSession: false in TypeScript, or CLAUDE_CODE_SKIP_PROMPT_HISTORY in Python's env |
What the SDK reads from disk
The SDK starts the Claude Code binary, so the files that shape Claude Code shape your agent unless you opt out.
- With
settingSourcesomitted,query()loads user, project and local settings, the same as the CLI. Your.claude/settings.jsonallow, ask and deny rules and a.mcp.jsonapply, which is why the settings lesson matters here. CLAUDE.md files load whenprojectis among the sources. Python narrows the default to user and project when you setskillsand leavesetting_sourcesunset. - Pass
settingSources: []for an agent that ignores all three. Managed policy settings still load, because they are not a source you can switch off. - In TypeScript the default system prompt is minimal. Pass
{ type: "preset", preset: "claude_code" }assystemPrompt, orsystem_promptin Python, to use Claude Code's own, with an optionalappend. - The
envoption replaces the subprocess environment in TypeScript, so pass{ ...process.env, YOUR_VAR: "value" }to keepPATH. In Python it merges on top of the inherited environment. - For several tenants in one service, give each its own
settingSources,envandcwd.
Errors before the first message
CLINotFoundErrorin Python means the SDK launched the CLI as a subprocess and found none. Platform wheels bundle it, but a source install, for example on ARM64 Windows, does not. Install Claude Code natively, confirmclaude --versionworks where your app runs, or setcli_path. A service started outside your shell often has a differentPATH.Refusing to execute batch scripton Windows means the SDK will not run an npmclaude.cmdshim, becausecmd.exere-parses arguments and could run injected commands. Pointcli_pathat aclaude.exeor remove it so the bundled binary is used. The check arrived inclaude-agent-sdk0.2.124.- In TypeScript, a message that the native CLI binary for your platform was not found means the install skipped optional dependencies, as
npm ci --omit=optionaldoes. Reinstall without skipping them, or setpathToClaudeCodeExecutable. - A bare
ProcessErrorin Python prints "Error output: Check stderr output for details", which is fixed text. Pass astderrcallback in the options to capture what the CLI wrote. - Calling a
ClaudeSDKClientmethod before it connects raisesNot connected. Call connect() first.Open the client withasync with, which connects on entry. Not logged inorInvalid API keyusually meansANTHROPIC_API_KEYis not set in the environment of the process that runs the agent. Check that process, not your shell history.
Boris Cherny used Lean to find bugs in the Agent SDK itself
Boris Cherny, who created Claude Code, posted on X in September that he had used Opus 5.5 to formally verify the Agent SDK with Lean, a language for machine-checked proofs. A couple of short prompts produced 16 pull requests that fixed bugs and race conditions. He added that TLA+ works well too, and that he sometimes combines the two to look at data flow, concurrency and state. He said he knows neither language well.
Fixes like these ship in new SDK releases, so keep the package current with npm update @anthropic-ai/claude-agent-sdk or pip install -U claude-agent-sdk, and read the changelog first. The method also works on your own code: ask Claude to model the part of your agent that handles sessions or parallel tool calls, then look for the states your tests never reach.
For a longer walkthrough, Thariq Shihipar of Anthropic gave a workshop on the SDK, recorded by the AI Engineer channel.
I used Opus 5.5 to formally verify the Claude Agent SDK using Lean. A couple short prompts = 16 PRs fixing various bugs and race conditions.

A screenshot of X's own embed of the post, taken on 5 November 2026, with the time in UTC. X cuts the post short with Show more. Select it to open the post on X.
Try it yourself
- Run the quickstart agent, then set
maxTurnsto 2 and read the result subtype. Raise it and compare. - Switch to
permissionMode: "dontAsk"with onlyReadallowed and ask for an edit. Watch the denial come back as a tool result and see what Claude does next. - Add the weather tool, rename its server key to
my.weatherand work out the allowed tool name you now need. - Capture a
session_idfrom a result, resume it with a follow-up prompt, then fork it and compare the two IDs.
Next course: Open-weight models
This lesson ran the agent on Claude in the cloud. The next course, Open-weight models, explains the models you can download and run yourself, and the Local AI course after it runs one with Ollama. The MCP course comes later. Build an MCP server in Python writes a server that any agent can connect to, including one you run with this SDK.
Subagents work through the same options. The SDK takes an agents option that defines them in code, and the subagents guide covers when to split work that way.
Check yourself
You set allowedTools to ["Read"] together with permissionMode "bypassPermissions". Which tools run without a prompt?
Show answer
C. Every tool, Bash and Edit included allowedTools only pre-approves what it lists, and bypassPermissions approves every call that reaches the mode step, so it does not constrain that mode.
What do you check on the final message of a query() loop before you read its result field?
Show answer
A. That its subtype is success Only the success variant carries result. The error subtypes, such as error_max_turns, have none.
In Python you set can_use_tool and pass a plain string as the prompt. What does the docs' pattern ask for instead?
Show answer
D. Pass the prompt as an async generator and add a PreToolUse hook that keeps the stream open The docs call the streaming prompt with a dummy PreToolUse hook a required workaround for can_use_tool in Python.
A custom tool get_temperature lives in an in-process server keyed weather. Which allowedTools entry pre-approves it?
Show answer
B. mcp__weather__get_temperature MCP tool names join the server key and the tool name with double underscores after an mcp prefix.
Frequently asked questions
Is the Agent SDK the same as the Claude Code SDK?
The Agent SDK is the package you move to. Anthropic has a migration guide for the older Claude Code SDK packages, so a tutorial that imports those needs the guide first.
How do I choose the model?
Set the model option to an alias such as sonnet, opus or haiku, or to a full model name. Without it the default from the CLI applies. A subagent can override it with the model field on its definition, and inherit uses the main model.
Can my app sign users in with their Claude subscription?
Unless Anthropic has approved it in advance, third party developers may not offer claude.ai login or its rate limits in their products, and that includes agents built on the Agent SDK. Use API key authentication, or Bedrock, Vertex or Foundry credentials.
What is the difference between query() and ClaudeSDKClient in Python?
query() starts a new session for each call and handles one exchange, without interrupts. ClaudeSDKClient keeps one session across exchanges and supports interrupts. That suits chat interfaces and work where the next step depends on Claude's answer. TypeScript has no client object and uses continue: true.
How do I stream text as Claude writes it?
Partial messages do it. Once they are on, the stream adds StreamEvent messages that carry the raw API streaming events, such as text deltas. The option is includePartialMessages in TypeScript and include_partial_messages in Python.
Can I call my product Claude Code?
No. Anthropic's branding guidelines allow "Claude Agent", or your own agent name followed by "Powered by Claude", and they do not permit "Claude Code" or "Claude Code Agent". Your product keeps its own branding.
When is the Client SDK a better choice?
When you need one model call, or want to own the tool loop. It gives direct access to the Claude API, and you write the loop or use the beta tool runner. The Agent SDK ships the loop, so your time goes into tools and permissions instead.
Sources
- Claude Code docs: Agent SDK overview https://code.claude.com/docs/en/agent-sdk/overview
- Claude Code docs: Agent SDK quickstart https://code.claude.com/docs/en/agent-sdk/quickstart
- Claude Code docs: How the agent loop works https://code.claude.com/docs/en/agent-sdk/agent-loop
- Claude Code docs: Configure permissions https://code.claude.com/docs/en/agent-sdk/permissions
- Claude Code docs: Handle approvals and user input https://code.claude.com/docs/en/agent-sdk/user-input
- Claude Code docs: Give Claude custom tools https://code.claude.com/docs/en/agent-sdk/custom-tools
- Claude Code docs: Connect to external tools with MCP https://code.claude.com/docs/en/agent-sdk/mcp
- Claude Code docs: Work with sessions https://code.claude.com/docs/en/agent-sdk/sessions
- Claude Code docs: Agent SDK reference, TypeScript https://code.claude.com/docs/en/agent-sdk/typescript
- Claude Code docs: Agent SDK reference, Python https://code.claude.com/docs/en/agent-sdk/python
- anthropics/claude-agent-sdk-python on GitHub https://github.com/anthropics/claude-agent-sdk-python
- anthropics/claude-agent-sdk-typescript on GitHub https://github.com/anthropics/claude-agent-sdk-typescript
- Boris Cherny on X: formally verifying the Claude Agent SDK with Lean https://x.com/bcherny/status/2102543349102338309
- AI Engineer on YouTube: Claude Agent SDK [Full Workshop], Thariq Shihipar https://www.youtube.com/watch?v=TqC1qOfiVcQ

