Appearance
Agent SDK 代理循环让 Claude 反复评估提示、调用工具、接收结果,直至任务完成或触发限制。关键配置包括 max_turns 控制工具调用轮数上限、max_budget_usd 控制成本上限、permission_mode 设置工具审批方式、effort 调节推理深度。循环以内从 SystemMessage 开始,经多轮 AssistantMessage 和 UserMessage 交互,最终以 ResultMessage 结束。你可以通过 hooks 拦截工具调用、通过子代理隔离长任务来保持主上下文精简。
Claude Code Agent SDK 代理循环工作原理与配置
理解消息生命周期、工具执行、上下文窗口和架构,帮助你构建和优化 SDK 代理。
Agent SDK 允许你在自己的应用中嵌入 Claude Code 的自主代理循环。它是一个独立包,可编程控制工具、权限、成本限制和输出。你不需要安装 Claude Code CLI 即可使用。
启动代理时,SDK 运行与 Claude Code 相同的执行循环:Claude 评估你的提示,调用工具采取行动,接收结果,并重复直至任务完成。本页解释循环内部发生的过程,以便你高效地构建、调试和优化代理。
循环概览
每次代理会话遵循相同的循环:
- 接收提示。 Claude 收到你的提示,包含系统提示、工具定义和对话历史。SDK 产出一个
SystemMessage具有子类型"init"并携带会话元数据。 - 评估并响应。 Claude 评估当前状态并决定如何继续。它可能返回文本、请求一个或多个工具调用,或两者。SDK 产出一个
AssistantMessage包含文本和工具调用请求。 - 执行工具。 SDK 运行每个请求的工具并收集结果。每组工具结果反馈给 Claude 供下一个决策。你可以使用 hooks 在工具执行前拦截、修改或阻止它们。
- 重复。 步骤 2 和 3 循环重复。每个完整循环为一轮。Claude 持续调用工具并处理结果,直到产生一个不含工具调用的响应。
- 返回结果。 SDK 产出一个最终的
AssistantMessage包含文本响应(无工具调用),随后是一个ResultMessage包含最终文本、token 用量、成本和会话 ID。
一个简单问题("这里有哪些文件?")可能只需要一两轮调用 Glob 并返回结果。一个复杂任务("重构 auth 模块并更新测试")可能跨多轮链式调用数十个工具,读取文件、编辑代码、运行测试,Claude 根据每个结果调整方法。
轮与消息
轮(turn)是循环内的一个往返:Claude 产生包含工具调用的输出,SDK 执行这些工具,结果自动反馈给 Claude。这个过程不将控制权交还给你的代码。轮持续进行,直到 Claude 产生不含工具调用的输出,此时循环结束并交付最终结果。
考虑一个完整会话,提示是"修复 auth.ts 中失败的测试"。
首先,SDK 将你的提示发送给 Claude 并产出一个带有会话元数据的 SystemMessage。然后循环开始:
- 第 1 轮: Claude 调用
Bash运行npm test。SDK 产出AssistantMessage包含工具调用、执行命令,然后产出UserMessage包含输出(三个失败)。 - 第 2 轮: Claude 调用
Read读取auth.ts和auth.test.ts。SDK 返回文件内容并产出AssistantMessage。 - 第 3 轮: Claude 调用
Edit修复auth.ts,然后调用Bash重新运行npm test。三个测试全部通过。SDK 产出AssistantMessage。 - 最后轮: Claude 产生一个纯文本响应,无工具调用:"Fixed the auth bug, all three tests pass now." SDK 产出最终的
AssistantMessage包含此文本,然后是一个ResultMessage包含相同文本、成本和用量。
共计四轮:三轮有工具调用,一轮为最终的纯文本响应。
你可以用 max_turns / maxTurns 限制循环,它只计算工具使用轮数。例如,上述循环中 max_turns=2 会在编辑步骤之前停止。你也可以使用 max_budget_usd / maxBudgetUsd 基于支出阈值限制轮数。
无限制时,循环会运行到 Claude 自行结束,这对于范围明确的任务是好的,但开放式的提示("改进这个代码库")可能会运行很长时间。对于生产环境代理,设置预算是个好默认。参见下方轮与预算的选项参考。
消息类型
循环运行时,SDK 产出一个消息流。每条消息带有类型,告诉你它来自循环的哪个阶段。五种核心类型为:
SystemMessage: 会话生命周期事件。subtype字段区分它们:"init"是第一条消息(会话元数据),"compact_boundary"在压缩后触发。在 TypeScript 中,压缩边界是单独的SDKCompactBoundaryMessage类型,而非SDKSystemMessage的子类型。AssistantMessage: 每次 Claude 响应后发出,包括最终的纯文本响应。包含该轮的文本内容块和工具调用块。UserMessage: 每次工具执行后发出,包含发送回 Claude 的工具结果内容。也用于你中途流入的任何用户输入。StreamEvent: 仅在启用部分消息时发出。包含原始 API 流事件(文本增量、工具输入块)。参见流式响应。ResultMessage: 标记代理循环结束。包含最终文本结果、token 用量、成本和会话 ID。检查subtype字段判断任务是成功还是达到限制。少量尾随的系统事件(如prompt_suggestion)可能在其后到达,因此迭代流直到结束,不要在结果处中断。参见处理结果。
这五种类型覆盖了两个 SDK 中的完整代理循环生命周期。TypeScript SDK 还会产出额外的可观测性事件(钩子事件、工具进度、速率限制、任务通知),提供更多细节,但不是驱动循环必需的。参见 Python 消息类型参考 和 TypeScript 消息类型参考 获取完整列表。
处理消息
你处理哪些消息取决于你在构建什么:
- 仅最终结果: 处理
ResultMessage获取输出、成本和任务是否成功或达到限制。 - 进度更新: 处理
AssistantMessage查看 Claude 每轮在做什么,包括调用了哪些工具。 - 实时流式: 启用部分消息(Python 中
include_partial_messages,TypeScript 中includePartialMessages)实时获取StreamEvent消息。参见实时流式响应。
如何检查消息类型取决于 SDK:
- Python: 使用
isinstance()检查从claude_agent_sdk导入的类(例如isinstance(message, ResultMessage))。 - TypeScript: 检查
type字符串字段(例如message.type === "result")。AssistantMessage和UserMessage将原始 API 消息包装在.message字段中,因此内容块位于message.message.content,而非message.content。
检查消息类型并处理结果示例
python
from claude_agent_sdk import query, AssistantMessage, ResultMessage
async for message in query(prompt="Summarize this project"):
if isinstance(message, AssistantMessage):
print(f"Turn completed: {len(message.content)} content blocks")
if isinstance(message, ResultMessage):
if message.subtype == "success":
print(message.result)
else:
print(f"Stopped: {message.subtype}")typescript
import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({ prompt: "Summarize this project" })) {
if (message.type === "assistant") {
console.log(`Turn completed: ${message.message.content.length} content blocks`);
}
if (message.type === "result") {
if (message.subtype === "success") {
console.log(message.result);
} else {
console.log(`Stopped: ${message.subtype}`);
}
}
}工具执行
工具赋予代理采取行动的能力。没有工具,Claude 只能返回文本。有了工具,Claude 可以读取文件、运行命令、搜索代码以及与外部服务交互。
内置工具
SDK 包含与 Claude Code 相同的工具:
| 分类 | 工具 | 作用 |
|---|---|---|
| 文件操作 | Read, Edit, Write | 读取、修改和创建文件 |
| 搜索 | Glob, Grep | 按模式查找文件,用正则搜索内容 |
| 执行 | Bash | 运行 shell 命令、脚本、git 操作 |
| Web | WebSearch, WebFetch | 搜索 Web,获取并解析页面 |
| 发现 | ToolSearch | 动态发现并按需加载工具,而非预加载所有 |
| 编排 | Agent, Skill, AskUserQuestion, TaskCreate, TaskUpdate | 生成子代理、调用技能、询问用户、追踪任务 |
除内置工具外,你还可以:
工具权限
Claude 根据任务决定调用哪些工具,但你控制这些调用是否允许执行。你可以自动批准特定工具、完全阻止其他工具,或要求所有工具都需审批。三种选项共同决定运行哪些:
allowed_tools/allowedTools自动批准列出的工具。一个只读代理在其允许工具列表中有["Read", "Glob", "Grep"],这些工具无需提示运行。未列出的工具仍然可用但需要权限。disallowed_tools/disallowedTools阻止列出的工具,无论其他设置如何。参见权限了解工具运行前规则检查的顺序。permission_mode/permissionMode控制未被允许或拒绝规则覆盖的工具的行为。参见权限模式了解可用模式。
你还可以用类似 "Bash(npm *)" 的规则限定单个工具,只允许特定命令。参见权限获取完整规则语法。
当工具被拒绝时,Claude 收到一条拒绝消息作为工具结果,通常会尝试不同方法或报告无法继续。
并行工具执行
当 Claude 在一轮中请求多个工具调用时,两个 SDK 可以根据工具类型并发或顺序运行它们。只读工具(如 Read、Glob、Grep 以及标记为只读的 MCP 工具)可以并发运行。修改状态的工具(如 Edit、Write 和 Bash)顺序运行以避免冲突。
自定义工具默认顺序执行。要为自定义工具启用并行执行,在其注解中设置 readOnlyHint。TypeScript 和 Python SDK 都使用此字段名(来自 MCP SDK)。
控制循环运行方式
你可以限制循环的轮数、成本、Claude 的推理深度,以及工具是否需要批准才能运行。所有这些都在 ClaudeAgentOptions(Python)/ Options(TypeScript)的字段中。
轮与预算
| 选项 | 控制内容 | 默认值 |
|---|---|---|
最大轮数 max_turns / maxTurns | 工具使用往返最大次数 | 无限制 |
最大预算 max_budget_usd / maxBudgetUsd | 停止前最大成本 | 无限制 |
当任一限制被触发时,SDK 返回一个 ResultMessage 具有相应的错误子类型(error_max_turns 或 error_max_budget_usd)。参见处理结果了解如何检查这些子类型,以及 ClaudeAgentOptions/Options 查看语法。
努力等级
effort 选项控制 Claude 应用的推理量。较低努力级别每轮使用更少 token 并降低成本。并非所有模型都支持 effort 参数。参见努力等级了解哪些模型支持。
| 级别 | 行为 | 适用场景 |
|---|---|---|
"low" | 最小推理,快速响应 | 文件查找、列出目录 |
"medium" | 平衡推理 | 常规编辑、标准任务 |
"high" | 充分分析 | 重构、调试 |
"xhigh" | 扩展推理深度 | 编码和代理任务;Opus 4.7 推荐 |
"max" | 最大推理深度 | 多步问题,需深度分析 |
如果你不设置 effort,Python SDK 保持参数未设置,由模型的默认行为决定。TypeScript SDK 默认值为 "high"。
effort在每次响应中以延迟和 token 成本换取推理深度。扩展思考 是一个独立特性,会在输出中产生可见的思维链块。它们彼此独立:你可以启用扩展思考的同时设置effort: "low",或不启用扩展思考而设置effort: "max"。
对于做简单、范围明确任务的代理(如列出文件或执行单个 grep),使用较低 effort 以降低成本和延迟。在顶级 query() 选项中为整个会话设置 effort,或者通过子代理的 AgentDefinition 上的 effort 字段覆盖会话级别。
权限模式
权限模式选项(Python 中 permission_mode,TypeScript 中 permissionMode)控制代理在使用工具前是否请求批准:
| 模式 | 行为 |
|---|---|
"default" | 未被允许规则覆盖的工具会触发你的批准回调;无回调则拒绝 |
"acceptEdits" | 自动批准文件编辑和常见文件系统命令(mkdir, touch, mv, cp 等);其他 Bash 命令遵循默认规则 |
"plan" | 只运行只读工具;Claude 探索并生成计划,不编辑源文件 |
"dontAsk" | 从不提示。权限规则预先批准的工具运行,其他全部拒绝 |
"auto" (仅 TypeScript) | 使用模型分类器批准或拒绝每个工具调用。参见自动模式了解可用性和行为 |
"bypassPermissions" | 运行所有允许的工具而无需询问。不能在 Unix 下以 root 运行时使用。仅在隔离环境中使用,确保代理操作不会影响你关心的系统 |
对于交互式应用,使用 "default" 配合工具批准回调,以呈现批准提示。对于开发机器上的自主代理,"acceptEdits" 自动批准文件编辑和常见文件系统命令(mkdir, touch, mv, cp 等),同时将其他 Bash 命令置于允许规则之后。将 "bypassPermissions" 保留用于 CI、容器或其他隔离环境。参见权限获取完整详情。
模型
如果你不设置 model,SDK 使用 Claude Code 的默认模型,这取决于你的认证方式和订阅。显式设置(例如 model="claude-sonnet-4-6")以锁定特定模型,或使用更小的模型获得更快、更便宜的代理。参见模型列表获取可用 ID。
上下文窗口
上下文窗口是会话期间 Claude 可用的总信息量。它不会在会话内的轮之间重置。所有内容不断累积:系统提示、工具定义、对话历史、工具输入和工具输出。跨轮保持不变的内容(系统提示、工具定义、CLAUDE.md)自动进行提示缓存,降低了重复前缀的成本和延迟。
什么消耗上下文
以下是每个组件在 SDK 中如何影响上下文:
| 来源 | 何时加载 | 影响 |
|---|---|---|
| 系统提示 | 每次请求 | 固定小成本,始终存在 |
| CLAUDE.md 文件 | 会话开始,通过 settingSources | 每次请求完整内容(但已提示缓存,只有第一次请求支付全成本) |
| 工具定义 | 每次请求;MCP 模式默认延迟加载 | 内置工具模式每次请求加载。工具搜索默认延迟 MCP 工具模式,在 Vertex AI 或非第一方 ANTHROPIC_BASE_URL 上回退到预先加载。参见配置工具搜索获取完整矩阵 |
| 对话历史 | 跨轮累积 | 每轮增长:提示、响应、工具输入、工具输出 |
| 技能描述 | 会话开始,通过设置源 | 简短摘要;完整内容仅在调用时加载 |
大的工具输出消耗大量上下文。读取大文件或运行输出冗长的命令可能在一轮中使用数千个 token。上下文跨轮累积,因此工具调用多的长会话比短会话建立更多上下文。
自动压缩
当上下文窗口接近其限制时,SDK 会自动压缩对话:它汇总较旧的历史以释放空间,保留最近的交换和关键决策。当发生压缩时,SDK 在流中发出一个消息,type: "system"、subtype: "compact_boundary"(在 Python 中为 SystemMessage;在 TypeScript 中为独立的 SDKCompactBoundaryMessage 类型)。
压缩用摘要替换较旧的消息,因此对话早期的具体指令可能不会保留。持久规则应放在 CLAUDE.md(通过 settingSources 加载)中,而非初始提示,因为 CLAUDE.md 内容在每次请求时重新注入。
你可以通过多种方式自定义压缩行为:
- CLAUDE.md 中的汇总指令: 压缩器像读取其他上下文一样读取你的 CLAUDE.md,因此你可以包含一个部分告诉它在汇总时应保留什么。部分标题是自由格式的(不是魔法字符串);压缩器按意图匹配。
PreCompact钩子: 在压缩发生前运行自定义逻辑,例如存档完整对话记录。钩子接收一个trigger字段(manual或auto)。参见 hooks。- 手动压缩: 将
/compact作为提示字符串发送以按需触发压缩。(以这种方式发送的斜杠命令是 SDK 输入,而非 CLI 独有快捷键。参见SDK 中的斜杠命令。)
CLAUDE.md 中的汇总指令示例: 在项目的 CLAUDE.md 中添加一个部分,告诉压缩器应保留什么。标题名称不是特殊的;使用任何清楚的标签。
markdown# Summary instructions When summarizing this conversation, always preserve: - The current task objective and acceptance criteria - File paths that have been read or modified - Test results and error messages - Decisions made and the reasoning behind them
保持上下文高效
一些针对长时间运行代理的策略:
- 使用子代理处理子任务。 每个子代理从全新的对话开始(没有先前的消息历史,但会加载自己的系统提示和项目级上下文如 CLAUDE.md)。它看不到父级的轮次,只有其最终响应作为工具结果返回给父级。主代理的上下文仅增长该摘要,而非完整子任务记录。参见子代理继承什么了解详情。
- 有选择地使用工具。 每个工具定义占用上下文空间。使用
AgentDefinition上的tools字段将子代理范围限定为所需的最小集合。 - 注意 MCP 服务器成本。 MCP 工具搜索默认延迟 MCP 工具模式并按需加载。当工具搜索关闭、在 Vertex AI 上或使用非第一方
ANTHROPIC_BASE_URL时,每个 MCP 服务器将其所有工具模式添加到每次请求中,因此少数拥有许多工具的服务器可能在代理做任何工作之前就消耗大量上下文。 - 对常规任务使用较低 effort。 对于只需读取文件或列出目录的代理,将 effort 设置为
"low"。这减少了 token 用量和成本。
关于每个特性上下文成本的详细分解,参见理解上下文成本。
会话与连续性
与 SDK 的每次交互都会创建或继续一个会话。从 ResultMessage.session_id 获取会话 ID(两个 SDK 均可用),以便稍后恢复。TypeScript SDK 还在初始 SystemMessage 上将其作为直接字段暴露;Python 中嵌套在 SystemMessage.data 中。
恢复时,之前轮次的完整上下文会被还原:已读取的文件、已执行的分析和已采取的行动。你还可以分叉一个会话,以分支到不同的方法而不修改原始会话。
参见会话管理获取关于恢复、继续和分叉模式的完整指南。
在 Python 中,
ClaudeSDKClient跨多次调用自动处理会话 ID。参见 Python SDK 参考了解详情。
处理结果
当循环结束时,ResultMessage 告诉你发生了什么并给出输出。subtype 字段(两个 SDK 均可用)是检查终止状态的主要方式。
| 结果子类型 | 发生了什么 | result 字段可用? |
|---|---|---|
success | Claude 正常完成任务 | 是 |
error_max_turns | 在完成前达到 maxTurns 限制 | 否 |
error_max_budget_usd | 在完成前达到 maxBudgetUsd 限制 | 否 |
error_during_execution | 错误中断循环(如 API 失败或取消请求) | 否 |
error_max_structured_output_retries | 结构化输出验证在配置的重试限制后失败 | 否 |
result 字段(最终文本输出)仅在 success 变体上存在,因此始终先检查子类型再读取它。所有结果子类型都携带 total_cost_usd、usage、num_turns 和 session_id,因此即使出错后也可以追踪成本和恢复。在 Python 中,total_cost_usd 和 usage 被类型化为可选,在某些错误路径上可能为 None,因此在格式化前要进行防护。参见追踪成本与用量了解解释 usage 字段的详情。
结果还包括一个 stop_reason 字段(TypeScript 中 string | null,Python 中 str | None),指示模型在其最后一轮停止生成的原因。常见值为 end_turn(模型正常结束)、max_tokens(达到输出 token 限制)和 refusal(模型拒绝请求)。在错误结果子类型上,stop_reason 携带循环结束前最后一次助手响应的值。要检测拒绝,检查 stop_reason === "refusal"(TypeScript)或 stop_reason == "refusal"(Python)。参见 SDKResultMessage(TypeScript)或 ResultMessage(Python)获取完整类型。
钩子
钩子是在循环中特定点触发的回调:工具运行前、工具返回后、代理完成时等。一些常用钩子包括:
| 钩子 | 触发时机 | 常见用途 |
|---|---|---|
PreToolUse | 工具执行前 | 验证输入,阻止危险命令 |
PostToolUse | 工具返回后 | 审计输出,触发副作用 |
UserPromptSubmit | 提示发送时 | 向提示注入额外上下文 |
Stop | 代理完成时 | 验证结果,保存会话状态 |
SubagentStart / SubagentStop | 子代理生成或完成时 | 跟踪和汇总并行任务结果 |
PreCompact | 上下文压缩前 | 在汇总前存档完整对话记录 |
钩子在你的应用程序进程中运行,而非代理的上下文窗口内部,因此它们不消耗上下文。钩子也可以短路循环:一个拒绝工具调用的 PreToolUse 钩子可防止其执行,Claude 会收到拒绝消息。
两个 SDK 都支持上述所有事件。TypeScript SDK 包含 Python 尚未支持的额外事件。参见使用钩子控制执行获取完整事件列表、各 SDK 可用性及完整回调 API。
综合示例
以下示例将此页的关键概念结合成一个修复失败测试的代理。它使用允许工具(自动批准使代理自主运行)、项目设置以及轮数和推理努力的安全限制来配置代理。当循环运行时,它捕获会话 ID 以备将来恢复,处理最终结果并打印总成本。
python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
async def run_agent():
session_id = None
async for message in query(
prompt="Find and fix the bug causing test failures in the auth module",
options=ClaudeAgentOptions(
allowed_tools=[
"Read",
"Edit",
"Bash",
"Glob",
"Grep",
], # Listing tools here auto-approves them (no prompting)
setting_sources=[
"project"
], # Load CLAUDE.md, skills, hooks from current directory
max_turns=30, # Prevent runaway sessions
effort="high", # Thorough reasoning for complex debugging
),
):
# Handle the final result
if isinstance(message, ResultMessage):
session_id = message.session_id # Save for potential resumption
if message.subtype == "success":
print(f"Done: {message.result}")
elif message.subtype == "error_max_turns":
# Agent ran out of turns. Resume with a higher limit.
print(f"Hit turn limit. Resume session {session_id} to continue.")
elif message.subtype == "error_max_budget_usd":
print("Hit budget limit.")
else:
print(f"Stopped: {message.subtype}")
if message.total_cost_usd is not None:
print(f"Cost: ${message.total_cost_usd:.4f}")
asyncio.run(run_agent())typescript
import { query } from "@anthropic-ai/claude-agent-sdk";
let sessionId: string | undefined;
for await (const message of query({
prompt: "Find and fix the bug causing test failures in the auth module",
options: {
allowedTools: ["Read", "Edit", "Bash", "Glob", "Grep"], // Listing tools here auto-approves them (no prompting)
settingSources: ["project"], // Load CLAUDE.md, skills, hooks from current directory
maxTurns: 30, // Prevent runaway sessions
effort: "high" // Thorough reasoning for complex debugging
}
})) {
// Save the session ID to resume later if needed
if (message.type === "system" && message.subtype === "init") {
sessionId = message.session_id;
}
// Handle the final result
if (message.type === "result") {
if (message.subtype === "success") {
console.log(`Done: ${message.result}`);
} else if (message.subtype === "error_max_turns") {
// Agent ran out of turns. Resume with a higher limit.
console.log(`Hit turn limit. Resume session ${sessionId} to continue.`);
} else if (message.subtype === "error_max_budget_usd") {
console.log("Hit budget limit.");
} else {
console.log(`Stopped: ${message.subtype}`);
}
console.log(`Cost: $${message.total_cost_usd.toFixed(4)}`);
}
}下一步
既然你理解了循环,以下是取决于你在构建什么的方向:
- 还没运行过代理? 从快速开始开始,安装 SDK 并查看完整的端到端示例。
- 准备挂钩到你的项目? 加载 CLAUDE.md、技能和文件系统钩子,让代理自动遵循你的项目约定。
- 构建交互式 UI? 启用流式以在循环运行时实时显示文本和工具调用。
- 需要对代理能力进行更严格的控制? 通过权限锁定工具访问,并使用钩子在工具执行前审计、阻止或转换它们。
- 运行长任务或昂贵任务? 将隔离工作卸载到子代理以保持主上下文精简。
有关代理循环的更广泛概念性理解(非 SDK 特定),参见 Claude Code 工作原理。
常见问题
代理循环卡住不返回结果怎么办?
检查是否未设置 max_turns 或 max_budget_usd 限制,导致循环无限运行。设置 max_turns=30 并检查 ResultMessage 的 subtype 是否为 error_max_turns。如果代理仍在运行但响应慢,可降低 effort 级别或检查工具输出是否过大占用上下文。
如何设置最大循环次数和成本限制?
在 ClaudeAgentOptions 中设置 max_turns(工具使用轮数上限)和 max_budget_usd(成本上限,美元)。两者默认无限制。达到限制时 ResultMessage.subtype 返回 error_max_turns 或 error_max_budget_usd,可通过 session_id 恢复会话继续。
上下文窗口满了我需要手动清理吗?
不需要。SDK 会触发自动压缩(compact_boundary 消息),汇总较旧历史以释放空间。你可以在 CLAUDE.md 中添加汇总指令,指定保留什么。也可以手动发送 /compact 触发压缩。持久规则应放在 CLAUDE.md 中而非初始提示,因为其内容每次请求都会重新注入。