- 本文基于
claude-agent-sdk(Python)0.2.164(仓库 commitf7b0b62,2026-10-07)。安装包里自带 Claude Code CLI 2.1.2921。TS 版@anthropic-ai/claude-agent-sdk的最新版本为 0.3.293,与 Claude Code v2.1.293 对齐2。 - 该 SDK 的版本号仍是 0.x,几乎每天都会随 Claude Code 一起发版,行为细节变化很快。文中代码已用 pyright 对照 0.2.164 做过类型检查;没有 API Key,因此没有实际运行。
- 资料截至 2026-10-09,本文不随官方同步更新,请对照 Agent SDK 文档 以及 Python 和 TS 的 CHANGELOG 使用。
- Claude Agent SDK 就是把 Claude Code 当作一个库来用:它提供与 Claude Code 相同的工具、agent loop 和上下文管理,支持 Python 和 TypeScript3。
- 从架构上看,你的代码调用
query()时,SDK 会启动一个claudeCLI 子进程,通过 stdio 与它通信。这个子进程持有 shell、工作目录和本地磁盘上的会话记录4。 - 开箱即用:Read、Edit、Write、Bash、Glob、Grep、WebSearch、WebFetch 等内置工具,加上权限、hooks、subagents、MCP、sessions、skills、plugins35。
- 它原名 Claude Code SDK,2025-09-29 更名为 Claude Agent SDK,因为同一套 harness 也适用于编码以外的 agent6。
1. 定位:harness,而不是“又一个 API 封装”#
- 分层位置:L4 Harness,运行在你自己的基础设施上。
- 它和 Tool Runner 的区别:Tool Runner 只循环你自己定义的工具;Agent SDK 自带一整套编码和文件系统工具,还有权限系统,见 3.2 Anthropic 客户端 SDK 与 Messages API。
- 它和 Managed Agents 的区别:Agent SDK 由你来部署;Managed Agents 由 Anthropic 托管运行,见 3.4 Claude Managed Agents。
- 官方说明:Agent SDK 是一个运行 Claude Code 二进制的库,具备 Claude Code 的内置工具、权限、会话和 hooks 等能力3。
2. 架构:子进程模型#
官方文档中与托管部署相关的几个事实4:
- 调用
query()时,SDK 会启动一个独立的claudeCLI 进程,通过 stdio 与它通信。这个子进程持有 shell、工作目录,以及本地磁盘上的 JSONL 会话记录。 - 一个会话对应一个子进程。N 个并发会话就是 N 个子进程。不同会话需要隔离文件系统时,给每个会话传入不同的
cwd。 - 默认情况下,会话记录、
CLAUDE.md和工作目录里的产物都存放在本地磁盘上,容器重启后就会丢失。如果要跨主机恢复会话,需要配置SessionStore适配器。
托管 Agent SDK 和托管一个无状态的 API 封装完全不同:每个运行中的 agent 都是一个绑定本地状态的长生命周期进程4。官方给出了四种会话模式(比如每个任务一个临时容器),以及 Docker、Modal、Kubernetes 的部署示例。
3. 安装与第一个 Agent#
pip install claude-agent-sdk # 安装包里已经自带 Claude Code 二进制export ANTHROPIC_API_KEY=sk-ant-...import asyncio
from claude_agent_sdk import ( AssistantMessage, ClaudeAgentOptions, ResultMessage, TextBlock, ToolUseBlock, query,)
async def main() -> None: options = ClaudeAgentOptions( cwd=".", # agent 在哪个目录里工作 allowed_tools=["Read", "Glob", "Grep"], # 列在这里的工具会被自动批准,不弹审批 max_turns=20, # 防止失控 ) async for message in query(prompt="找出这个项目里所有的 TODO 注释,并按文件汇总", options=options): if isinstance(message, AssistantMessage): for block in message.content: # 每个 AssistantMessage 只带一个内容块 if isinstance(block, TextBlock): print("Claude:", block.text) elif isinstance(block, ToolUseBlock): print("调用工具:", block.name) elif isinstance(message, ResultMessage): # 循环结束的标志 print("结果类型:", message.subtype, "| 费用:", message.total_cost_usd) if message.subtype == "success": print(message.result)
asyncio.run(main())单次调用 query() 时,如果循环以错误结束(比如 error_max_turns),SDK 会先把错误结果作为消息输出,然后再抛出异常。如果你的程序需要在出错后继续运行,记得用 try 包住5。
4. 循环与消息类型#
循环的过程5:
- 接收 prompt,SDK 输出一条
SystemMessage(init); - Claude 评估当前状态并给出回应,可能是文本、工具调用,或者两者都有;
- SDK 执行工具;
- 重复第 2、3 步;
- 输出最后的
AssistantMessage(只有文本,没有工具调用),然后输出ResultMessage。
| 消息类型 | 含义 |
|---|---|
SystemMessage | 会话生命周期事件。subtype 有 init、compact_boundary(发生了压缩)、informational 等 |
AssistantMessage | Claude 响应中的每一个内容块都对应一条,比如文本或工具调用 |
UserMessage | 工具执行后的结果,也包括你在循环中途发送的输入 |
StreamEvent | 原始的流式事件,需要开启 include_partial_messages |
ResultMessage | 循环结束。包含最终文本、token 用量、费用和 session ID |
以上取自官方文档5。ResultMessage.subtype 的取值有:success、error_max_turns、error_max_budget_usd、error_during_execution、error_max_structured_output_retries。只有 success 时 result 字段才有值5。
5. 内置工具与权限#
| 类别 | 工具 |
|---|---|
| 文件操作 | Read、Edit、Write |
| 搜索 | Glob、Grep |
| 执行 | Bash |
| Web | WebSearch、WebFetch |
| 发现 | ToolSearch(按需加载工具) |
| 编排 | Agent(subagent)、Skill、AskUserQuestion、TaskCreate、TaskUpdate |
以上取自官方文档5。
5.1 权限是怎么判定的#
工具调用的审批按固定顺序判定,前一步就能做出决定时,后面的步骤不再执行7:
- deny 规则的优先级最高,即使在
bypassPermissions模式下也照样生效。 - hook 返回 allow 并不会跳过后面的 deny 和 ask 规则7。
| 权限模式 | 行为 |
|---|---|
default | 需要审批的调用交给 can_use_tool 回调;没有提供回调时直接拒绝 |
acceptEdits | 自动批准文件编辑和常见的文件系统命令,例如 mkdir、mv |
plan | 只探索和规划;写操作一律交给回调审批 |
dontAsk | 从不询问:没有预先批准的调用一律拒绝 |
auto | 由模型分类器逐个审查每次调用,决定放行或拦截 |
bypassPermissions | 不询问,直接运行所有允许的工具。只建议在 CI、容器等隔离环境中使用 |
5.2 用回调做人工审批#
import asyncio
from claude_agent_sdk import ( ClaudeAgentOptions, HookContext, HookInput, HookJSONOutput, HookMatcher, PermissionResultAllow, PermissionResultDeny, ResultMessage, ToolPermissionContext, query,)
async def can_use_tool( tool_name: str, input_data: dict, context: ToolPermissionContext) -> PermissionResultAllow | PermissionResultDeny: print(f"\n工具:{tool_name},参数:{input_data}") if input("允许吗?(y/n) ").lower() == "y": return PermissionResultAllow(updated_input=input_data) # 也可以修改参数后再放行 return PermissionResultDeny(message="用户拒绝了这个操作") # Claude 能看到这条信息,会换个办法
async def keep_stream_open( input_data: HookInput, tool_use_id: str | None, context: HookContext) -> HookJSONOutput: return {"continue_": True} # 官方给出的变通写法:靠一个 hook 让流保持打开
async def prompt_stream(): yield {"type": "user", "message": {"role": "user", "content": "在 /tmp 里创建一个测试文件,然后删掉它"}}
async def main() -> None: async for message in query( prompt=prompt_stream(), # Python 中使用 can_use_tool 时需要“流式输入” options=ClaudeAgentOptions( can_use_tool=can_use_tool, hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[keep_stream_open])]}, ), ): if isinstance(message, ResultMessage) and message.subtype == "success": print(message.result)
asyncio.run(main())这个例子改编自官方示例8。注意:如果某个工具已经被前面的规则自动批准,回调根本不会被调用8。
6. 自定义工具:进程内 MCP Server#
Agent SDK 用 in-process MCP server 承载自定义工具,也就是一个运行在你自己进程里的 MCP 服务器。工具的完整名称格式是 mcp__{server_name}__{tool_name}9。
import asynciofrom typing import Annotated, Any
from claude_agent_sdk import ClaudeAgentOptions, ResultMessage, create_sdk_mcp_server, query, tool
@tool( "get_weather", "查询城市今天的天气", {"city": Annotated[str, "城市名,例如 北京"]}, # dict 写法:SDK 会转换成 JSON Schema)async def get_weather(args: dict[str, Any]) -> dict[str, Any]: data = {"北京": "晴,25°C", "上海": "小雨,22°C"} return {"content": [{"type": "text", "text": data.get(args["city"], "暂无数据")}]}
weather_server = create_sdk_mcp_server(name="weather", version="1.0.0", tools=[get_weather])
async def main() -> None: options = ClaudeAgentOptions( mcp_servers={"weather": weather_server}, # 这里的 key 就是 server_name allowed_tools=["mcp__weather__get_weather"], # 预先批准,不弹审批 ) async for message in query(prompt="北京今天适合跑步吗?", options=options): if isinstance(message, ResultMessage) and message.subtype == "success": print(message.result)
asyncio.run(main())| 要点 | 说明 |
|---|---|
| 返回值 | 必须包含 content 数组(text、image、resource 等块);可选 structuredContent(机器可读的 JSON)和 isError(标记失败) |
| 可选参数 | dict 写法要求所有 key 都是必填。需要可选参数时,改用完整的 JSON Schema,并在处理函数里用 args.get() 读取 |
| 并行执行 | 自定义工具默认串行执行。设置 readOnlyHint 注解后,可以和其他只读工具并行 |
| 只保留部分内置工具 | 传 tools=[...] 指定要保留的内置工具 |
7. Hooks:在循环的关键节点插入你的代码#
import asyncio
from claude_agent_sdk import ( AssistantMessage, ClaudeAgentOptions, ClaudeSDKClient, HookContext, HookInput, HookJSONOutput, HookMatcher, ResultMessage,)
async def protect_env_files( input_data: HookInput, tool_use_id: str | None, context: HookContext) -> HookJSONOutput: if input_data["hook_event_name"] != "PreToolUse": # HookInput 是按事件名区分的联合类型 return {} file_path = str(input_data["tool_input"].get("file_path", "")) if file_path.split("/")[-1] == ".env": return { "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "不允许修改 .env 文件", } } return {} # 返回空对象表示放行
async def main() -> None: options = ClaudeAgentOptions( hooks={"PreToolUse": [HookMatcher(matcher="Write|Edit", hooks=[protect_env_files])]} ) async with ClaudeSDKClient(options=options) as client: await client.query("创建一个 .env 文件,写入本地开发数据库配置") async for message in client.receive_response(): if isinstance(message, (AssistantMessage, ResultMessage)): print(message)
asyncio.run(main())这个例子改编自官方示例10。
官方文档里的 hook 示例没有写类型注解。用 pyright 检查时会报错,因为 HookMatcher.hooks 要求的类型是 HookCallback,即参数为 (HookInput, str | None, HookContext)、返回 HookJSONOutput 的异步函数。上面的写法按照 SDK 源码中的类型定义补上了注解,并且能通过 0.2.164 的类型检查。
| Hook | 什么时候触发 | 常见用途 |
|---|---|---|
PreToolUse | 工具执行前 | 校验输入、拦截危险命令 |
PostToolUse | 工具返回后 | 审计输出、触发后续动作 |
UserPromptSubmit | 提交 prompt 时 | 注入额外的上下文 |
Stop | agent 结束时 | 校验结果、保存状态 |
SubagentStart / SubagentStop | subagent 启动或结束时 | 汇总并行任务的结果 |
PreCompact | 上下文压缩之前 | 归档完整的对话记录 |
以上取自官方文档5。hooks 在你的进程里运行,不占用 agent 的上下文窗口5。
8. Subagents:隔离上下文、并行处理#
import asyncio
from claude_agent_sdk import AgentDefinition, ClaudeAgentOptions, ResultMessage, query
async def main() -> None: options = ClaudeAgentOptions( allowed_tools=["Read", "Grep", "Glob", "Agent"], # 主 agent 通过 Agent 工具调用 subagent agents={ "code-reviewer": AgentDefinition( description="代码审查专家,负责质量、安全和可维护性审查。", # Claude 根据它决定何时调用 prompt="你是代码审查专家,关注安全漏洞、性能问题和编码规范,给出具体的改进建议。", tools=["Read", "Grep", "Glob"], # 只读:只能分析,不能修改 model="sonnet", ), }, ) async for message in query(prompt="审查 auth 模块的安全问题", options=options): if isinstance(message, ResultMessage) and message.subtype == "success": print(message.result)
asyncio.run(main())- 上下文隔离:中间的工具调用都留在 subagent 内部,只有最终结论返回给父 agent。
- 并行:互不依赖的子任务可以同时执行。
- 专门的指令:每个 subagent 有自己的系统提示词。
- 工具限制:例如只给读权限。
9. 会话:continue、resume、fork#
| 场景 | 做法 |
|---|---|
| 一次性任务 | 调用一次 query() 即可 |
| 同一个进程里的多轮对话 | 使用 ClaudeSDKClient,它会自动沿用同一个会话 |
| 进程重启后接着上次继续 | continue_conversation=True,自动接续当前目录下最近的一次会话 |
| 恢复某个指定的会话 | resume=session_id |
| 在不改动原会话的前提下尝试别的方案 | resume=session_id 加上 fork_session=True |
以上取自官方文档12。
import asyncio
from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient, ResultMessage
async def main() -> None: async with ClaudeSDKClient(options=ClaudeAgentOptions(allowed_tools=["Read", "Glob"])) as client: await client.query("分析一下 utils 目录是做什么的") async for msg in client.receive_response(): if isinstance(msg, ResultMessage): print(msg.result) await client.query("基于刚才的分析,列出三个可以重构的点") # 自动沿用同一个会话的上下文 async for msg in client.receive_response(): if isinstance(msg, ResultMessage): print(msg.result)
asyncio.run(main())会话持久化保存的是对话,而不是文件系统。如果要回滚 agent 对文件做的改动,需要使用 file checkpointing12。
10. 系统提示词与项目配置#
| 选项 | 作用 |
|---|---|
system_prompt | 可以传一个字符串,完全自定义;也可以传 {"type": "preset", "preset": "claude_code", "append": "..."},在 Claude Code 默认提示词的基础上追加内容13 |
setting_sources | 控制加载哪些文件系统配置:user、project、local。默认值 None 表示全部加载,与 CLI 行为一致;传 [] 进入隔离模式;必须包含 project 才会加载 CLAUDE.md13 |
skills | 为主会话启用哪些 skills。它是一个上下文过滤器,不是沙箱:未启用的 skill 文件仍然可以通过 Read 或 Bash 读到13 |
max_turns / max_budget_usd | 限制轮数和花费,subagent 的花费也会计入预算5 |
effort | low / medium / high / xhigh / max5 |
自动压缩会把较早的历史替换成摘要,所以写在初始 prompt 里的规则可能在压缩后丢失。官方建议:需要长期遵守的规则写进 CLAUDE.md,因为它在每次请求时都会重新注入5。
11. 与其他方案的对比#
| 维度 | Claude Agent SDK | OpenAI Agents SDK | pi SDK |
|---|---|---|---|
| 运行方式 | 启动 Claude Code CLI 子进程 | 纯 Python 库,在你的进程内运行 | 纯 TS 库,在你的进程内运行 |
| 内置工具 | 一整套编码工具 | 默认没有,需要另外加 Sandbox agents | read、bash、edit、write |
| 模型 | Claude | OpenAI 为主,可以接入其他厂商 | 数十家提供方 |
| 权限 | 多层权限系统 | needs_approval 加护栏 | 默认不提供,用扩展自己实现 |
| 适合场景 | 需要一个能力完整的编码或办公 agent | 业务流程编排、多 agent 协作 | 需要深度定制、多模型的 agent |
这张表是笔者根据各篇笔记的内容归纳的,并非官方对比。
小结#
- Agent SDK = Claude Code 的 harness 加上一套编程接口。你拿到的是一个“开箱即用的 Claude Code”,而不是一组需要自己组装的原语。
- 理解它的关键在于子进程架构:一个会话对应一个 CLI 进程,状态默认保存在本地磁盘上,这决定了它该怎么部署和扩展。
- 安全相关的工具有:权限判定顺序、
can_use_tool、hooks、disallowed_tools;在生产环境中,还应该放进容器隔离运行。
相关笔记#
- 3.1 Claude 开发者生态全景 · 3.2 Anthropic 客户端 SDK 与 Messages API · 3.4 Claude Managed Agents
- 对照:4.4 pi-coding-agent SDK 与扩展系统 · 5.1 DeepSeek Harness 全景
参考资料#
注释与出处#
-
claude-agent-sdk 0.2.164 安装包中
claude_agent_sdk/_cli_version.py(__cli_version__ = "2.1.292")。仓库地址:https://github.com/anthropics/claude-agent-sdk-python/tree/f7b0b62c2a8d110d4da0eec0aa70cf795ec3afc4 ↩ -
anthropics/claude-agent-sdk-typescript,
CHANGELOG.md,https://github.com/anthropics/claude-agent-sdk-typescript/blob/799dc78749f5568e2965699c6ed4d2542031082d/CHANGELOG.md ↩ -
Anthropic,Agent SDK overview,https://code.claude.com/docs/en/agent-sdk/overview ↩ ↩2 ↩3 ↩4 ↩5
-
Anthropic,Hosting the Agent SDK(The subprocess model 一节),https://code.claude.com/docs/en/agent-sdk/hosting ↩ ↩2 ↩3
-
Anthropic,How the agent loop works,https://code.claude.com/docs/en/agent-sdk/agent-loop ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13
-
Anthropic Engineering,Building agents with the Claude Agent SDK(2025-09-29),https://www.anthropic.com/engineering/building-agents-with-the-claude-agent-sdk ↩
-
Anthropic,Configure permissions(How permissions are evaluated 一节),https://code.claude.com/docs/en/agent-sdk/permissions ↩ ↩2 ↩3
-
Anthropic,Handle approvals and user input,https://code.claude.com/docs/en/agent-sdk/user-input ↩ ↩2
-
Anthropic,Give Claude custom tools,https://code.claude.com/docs/en/agent-sdk/custom-tools ↩ ↩2
-
Anthropic,Intercept and control agent behavior with hooks,https://code.claude.com/docs/en/agent-sdk/hooks ↩
-
Anthropic,Subagents in the SDK,https://code.claude.com/docs/en/agent-sdk/subagents ↩ ↩2
-
Anthropic,Work with sessions,https://code.claude.com/docs/en/agent-sdk/sessions ↩ ↩2
-
anthropics/claude-agent-sdk-python,
src/claude_agent_sdk/types.py中ClaudeAgentOptions的字段文档(system_prompt、setting_sources、skills),https://github.com/anthropics/claude-agent-sdk-python/blob/f7b0b62c2a8d110d4da0eec0aa70cf795ec3afc4/src/claude_agent_sdk/types.py ↩ ↩2 ↩3