Claude Code Agent Loop 详细设计文档

本文基于 claude-code/src/QueryEngine.tsClaude-code-open-explain/02-agentic-loop/README.md,从软件架构视图、运行时流程视图、状态视图和关键逻辑视图分析 Claude Code 的 Agent Loop 设计。

1. 设计定位

Claude Code 的 Agent Loop 不是一个简单的 while 循环,也不是单个类完成所有事情。它采用了两层职责拆分:

  • QueryEngine.ts:会话级控制器,负责一轮请求前后的状态管理、参数组装、上下文准备、事件归一化、持久化与结果封装。
  • query.ts:请求级执行引擎,负责模型调用、工具执行、继续或终止判断、上下文压缩、错误恢复等真正的循环推进。

可以把 QueryEngine 理解成“会话控制面”,把 query() / queryLoop() 理解成“执行数据面”。前者决定本轮怎么开始、如何记账、如何把内部事件转成 SDK 消息;后者决定本轮如何一跳一跳地推进,直到模型不再需要工具或触发退出条件。

2. 总体架构视图

User / SDK Caller

ask convenience wrapper

QueryEngine

processUserInput

fetchSystemPromptParts

sessionStorage / transcript

AppState

query

queryLoop

Claude API streaming

Tool orchestration / StreamingToolExecutor

snip / microcompact / autocompact / context collapse

post-sampling hooks / stop hooks

Built-in tools / MCP tools / Agent tools

tool_result messages

assistant content blocks

SDKMessage stream

2.1 分层说明

层次 主要代码 核心职责
对外入口层 ask()QueryEngine.submitMessage() 暴露 AsyncGenerator,支持流式输出、中断、SDK 结果封装
会话状态层 QueryEngine 字段与 submitMessage() 保存消息历史、权限拒绝、token 用量、文件状态、技能发现记录
输入处理层 processUserInput() 解析用户 prompt、slash command、附件、模型切换、允许工具规则
上下文构建层 fetchSystemPromptParts()asSystemPrompt() 生成 system prompt、userContext、systemContext,并拼接自定义 prompt
循环执行层 query()queryLoop() 模型流式调用、工具执行、上下文整理、终止条件判断
工具执行层 runTools()StreamingToolExecutor 识别 tool_use、执行工具、生成 tool_result
记账与持久化层 recordTranscript()flushSessionStorage()、usage tracker 会话恢复、token/cost 统计、错误诊断
输出适配层 normalizeMessage()、SDK result messages 将内部 message / stream event 转换成 SDK 可消费消息

3. 关键对象视图

3.1 QueryEngineConfig

QueryEngineConfig 是会话控制器的依赖注入边界。它把环境、工具、命令、模型配置、状态读写函数、权限函数以及 SDK 选项集中传入。

QueryEngineConfig

cwd

tools

commands

mcpClients

agents

canUseTool

initialMessages

readFileCache

customSystemPrompt

appendSystemPrompt

userSpecifiedModel

fallbackModel

thinkingConfig

maxTurns

maxBudgetUsd

taskBudget

jsonSchema

replayUserMessages

includePartialMessages

abortController

orphanedPermission

snipReplay

getAppState()

setAppState()

QueryEngine

config

mutableMessages

abortController

permissionDenials

totalUsage

readFileState

discoveredSkillNames

loadedNestedMemoryPaths

submitMessage()

interrupt()

getMessages()

getReadFileState()

getSessionId()

setModel()

3.2 QueryEngine 持有的长期状态

状态 生命周期 作用
mutableMessages 跨多轮 submitMessage() 保存会话消息历史,是后续上下文、持久化和恢复的基础
abortController 引擎生命周期 支持用户中断或外部取消
permissionDenials 引擎生命周期 汇总被拒绝的工具调用,最终通过 SDK result 返回
totalUsage 引擎生命周期 汇总 token usage,用于成本和结果统计
readFileState 引擎生命周期,ask() finally 写回 缓存文件读取状态,减少重复读取并保持文件状态一致
discoveredSkillNames 每轮清空 记录本轮发现的 skill,供工具调用标记 was_discovered
loadedNestedMemoryPaths 跨轮保留 避免重复加载嵌套 memory

这些状态说明 QueryEngine 不是无状态函数,而是一个会话对象。它每次处理一条用户消息时,都会基于已有状态构建本轮请求。

4. 运行时流程视图

4.1 一轮 submitMessage() 的主流程

是且未处理

submitMessage(prompt)

清空本轮 skill 发现记录

设置 cwd 与 session 持久化策略

包装 canUseTool 以记录 permissionDenials

确定初始模型和 thinkingConfig

fetchSystemPromptParts 获取 prompt/context

组合 systemPrompt

构造 ProcessUserInputContext

是否有 orphanedPermission

handleOrphanedPermission 并 yield 消息

processUserInput

追加用户消息到 mutableMessages

提前 recordTranscript 支持 resume

更新 AppState 中的 alwaysAllowRules

重新构造 ProcessUserInputContext

加载 skills / plugins

yield system init message

shouldQuery?

输出本地命令结果和 success result

进入 query()

消费 query 产生的内部消息和流事件

更新 mutableMessages / transcript / usage / stop_reason

预算、turn、structured output 等是否触发错误出口

yield error result 并 return

query 正常结束

判断最终 result 是否成功

yield success 或 error_during_execution

4.2 时序图

Transcript Storage Tool Executor Claude API query/queryLoop queryContext processUserInput QueryEngine SDK Caller Transcript Storage Tool Executor Claude API query/queryLoop queryContext processUserInput QueryEngine SDK Caller alt [assistant contains tool_use] [no tool_use] loop [until no tool_use or terminal condition] alt [local command only] [requires model query] submitMessage(prompt) fetchSystemPromptParts() defaultSystemPrompt, userContext, systemContext processUserInput(prompt) messages, shouldQuery, allowedTools, model recordTranscript(user messages) system init SDKMessage replay command output result(success) query(messages, prompt, context, canUseTool) compact / normalize / prepare messagesForQuery streaming request stream events + assistant content blocks stream / assistant messages normalized SDK messages permission check + execute tool tool_result user message with tool_result recordTranscript(updated messages) terminal assistant message result(success/error)

5. query.ts 循环执行视图

介绍文档里提到的关键点是:query() 本身只是生成器入口,真正的持续推进发生在 queryLoop()while (true) 中。

5.1 query()queryLoop() 的关系

query(params)

创建 consumedCommandUuids

yield* queryLoop(params, consumedCommandUuids)

queryLoop 正常返回 terminal

notifyCommandLifecycle completed

return terminal

query() 的职责很薄,主要是包住 queryLoop(),并在循环正常结束后通知被消费的命令完成。异常或 generator 被 .return() 关闭时,不会走完成通知,从而保留“已开始但未完成”的语义。

5.2 queryLoop() 单次迭代逻辑

while true iteration

从 State 解构 messages / toolUseContext / turnCount 等

启动 skill discovery prefetch

yield stream_request_start

建立 queryTracking chainId/depth

getMessagesAfterCompactBoundary

applyToolResultBudget

snip compact 可选

microcompact

context collapse 可选

append systemContext 到 systemPrompt

autocompact

prepend userContext / normalizeMessagesForAPI

queryModelWithStreaming

收集 assistantMessages 与 toolUseBlocks

是否有 tool_use

执行 stop hooks / 判断最终结束

return terminal

执行工具并生成 tool_result

追加 assistant + tool_result 到 messages

maxTurns 是否超限

yield max_turns_reached attachment; return

更新 State, transition=next_turn

5.3 循环状态对象

queryLoop() 内部维护一个跨迭代 State,用不可变参数 + 可变状态的组合降低复杂度。

State

messages

toolUseContext

autoCompactTracking

maxOutputTokensRecoveryCount

hasAttemptedReactiveCompact

maxOutputTokensOverride

pendingToolUseSummary

stopHookActive

turnCount

transition

设计要点:

  • messages 是每轮传给模型的基础,但进入模型前会经过 compact boundary、tool result budget、microcompact、context collapse、autocompact 等多层整理。
  • toolUseContext 会在迭代中加入 queryTracking,用于区分当前 loop chain 的深度。
  • turnCount 用于 maxTurns 控制,工具回流后会进入下一 turn。
  • transition 记录上一轮继续的原因,便于测试和诊断恢复路径。

6. 消息与数据流视图

6.1 消息闭环

User prompt

user message

queryLoop prepares messagesForQuery

Claude API

assistant message

扫描 content blocks

包含 tool_use?

assistant text / end_turn

tool_use block

canUseTool permission check

allow?

记录 permissionDenials

生成错误 tool_result

执行工具

生成 tool_result

user message with tool_result

6.2 内部消息到 SDK 消息的转换

query() 产生的是内部 MessageStreamEventRequestStartEventAttachmentMessage 等。QueryEngine.submitMessage() 会按类型处理,再转换为 SDK 语义:

内部消息 QueryEngine 行为 对外结果
assistant 追加到 mutableMessages,归一化输出 assistant SDK message
user 通常代表 tool_result 回流,追加并输出 user replay / tool result related message
stream_event 累计 usage、捕获 stop_reason;可选透出 partial stream event SDK message 或仅内部记账
progress 追加并记录 transcript normalized progress
attachment 处理 structured output、max turns、queued command result error / user replay / internal state
system compact_boundary 裁剪本地历史,输出 compact boundary SDK message system compact boundary
system api_error 转成 SDK api_retry system api_retry
tool_use_summary 直接转成 SDK tool use summary tool_use_summary
tombstone 作为控制信号,不对外输出

7. 上下文构建与整形视图

7.1 QueryEngine 层的上下文准备

submitMessage

确定 initialMainLoopModel

确定 initialThinkingConfig

fetchSystemPromptParts

defaultSystemPrompt

baseUserContext

systemContext

合并 coordinator userContext

选择 customSystemPrompt 或 defaultSystemPrompt

可选注入 memoryMechanicsPrompt

可选 appendSystemPrompt

asSystemPrompt

传入 query()

QueryEngine 这一层的上下文关注“来源”:

  • defaultSystemPrompt:默认系统规则。
  • customSystemPrompt:SDK 调用方显式覆盖系统提示词时使用。
  • appendSystemPrompt:在基础提示词后追加策略。
  • userContext:用户侧上下文,例如环境、工作区信息、coordinator 信息。
  • systemContext:系统侧上下文,在 queryLoop() 内被追加到 system prompt。

7.2 queryLoop() 层的上下文整形

messages

getMessagesAfterCompactBoundary

applyToolResultBudget

HISTORY_SNIP snipCompactIfNeeded

microcompact

CONTEXT_COLLAPSE applyCollapsesIfNeeded

autocompact

appendSystemContext

prependUserContext

normalizeMessagesForAPI

Claude API request

这说明 Claude Code 不会把内存里的所有消息原样丢给模型。每次 API 调用前都会构造一个“本轮可见视图”,它可能已经丢弃 compact boundary 之前的消息、替换超长工具结果、应用 snip/microcompact/autocompact 或 context collapse。

8. 工具调用逻辑视图

8.1 为什么不只看 stop_reason

query.ts 明确认为 stop_reason === 'tool_use' 不可靠,因此本地会直接扫描 assistant content block 中的 tool_use。这比依赖模型响应的 stop reason 更具体,因为真正决定是否继续循环的是:是否存在尚未回流的工具调用。

assistant message

遍历 content blocks

block.type == tool_use?

加入 toolUseBlocks

继续扫描

needsFollowUp = true

扫描结束

toolUseBlocks.length > 0

执行工具并回流 tool_result

本轮可能结束

8.2 权限包装逻辑

QueryEngine 不直接决定工具权限,而是包装外部传入的 canUseTool。包装层只增加 SDK 需要的审计信息:

wrappedCanUseTool

调用原始 canUseTool

result.behavior == allow?

返回 allow 结果

记录 SDKPermissionDenial

tool_name

tool_use_id

tool_input

返回拒绝结果

这个设计让权限策略与审计记录解耦:权限系统仍然由调用方或 AppState 决定,QueryEngine 只负责把拒绝事件纳入会话结果。

9. 状态机视图

9.1 QueryEngine 会话生命周期

constructor 初始化状态

submitMessage

shouldQuery=false

yield local result

shouldQuery=true

query emits stream/message

tool_result 回流后继续

final assistant/user successful

maxTurns / budget / structured output / execution error

yield success result

yield error result

interrupt()

后续调用可复用或由上层释放

Constructed

Ready

PreparingTurn

LocalOnly

Querying

Streaming

Succeeded

Failed

Interrupted

9.2 queryLoop() 迭代状态

tool_use detected

next_turn

no tool_use

hook requests retry

prompt too long / max output tokens / fallback

PrepareVisibleContext

CallModel

StreamAssistant

ExecuteTools

AppendToolResults

CheckLimits

MaxTurnsReached

StopHooks

TerminalSuccess

Recovery

TerminalError

10. 持久化与恢复视图

QueryEngine 很重视 transcript 写入时机。它会在用户消息被接受后、进入模型请求前提前写 transcript。这样即使进程在 API 返回前被杀掉,--resume 仍然能找到这次用户输入,而不是得到“没有对话”的状态。

processUserInput 返回 messagesFromUserInput

push 到 mutableMessages

persistSession?

继续执行

recordTranscript(messages)

bare mode?

fire-and-forget

await 写入完成

EAGER_FLUSH 或 COWORK?

flushSessionStorage

后续在 query() 输出 assistant、user、compact boundary、progress、attachment 时,QueryEngine 还会继续维护 transcript。对 assistant 消息,它倾向于 fire-and-forget,避免阻塞流式输出;对需要保证顺序和恢复正确性的消息,则会 await。

11. 退出条件与错误出口

11.1 QueryEngine 层的结果封装

query for-await 结束

找到最后一个 assistant 或 user

flush transcript if needed

isResultSuccessful?

yield error_during_execution

提取 assistant 最后一段 text

yield result success

11.2 主要错误出口

出口 触发条件 返回 subtype 说明
本地命令正常结束 shouldQuery=false success 不进入模型循环
最大 turn 数 query.ts 产生 max_turns_reached attachment error_max_turns 防止工具循环无限推进
预算上限 getTotalCost() >= maxBudgetUsd error_max_budget_usd SDK/CLI 成本保护
结构化输出重试过多 jsonSchema 下 SyntheticOutput tool 调用超过限制 error_max_structured_output_retries 防止模型一直无法满足 schema
执行期间异常终态 最终消息不满足成功谓词 error_during_execution 附带 turn-scoped error diagnostics
API retry system 消息 subtype 为 api_error api_retry 系统事件 不是最终 result,而是中间重试事件

12. 并发与顺序性的设计

单个 queryLoop() 对工具调用采取偏顺序、偏保守的推进方式。即使模型一次给出多个 tool_use,系统也会围绕“assistant tool_use -> 本地 tool_result -> 下一轮 messages”的闭环推进。这样做有几个工程收益:

  • 前一个工具结果可以影响下一步模型判断,避免过早并发造成无效工作。
  • 消息链与 transcript 更容易保持一致,便于恢复。
  • UI 可以按顺序展示模型决策和工具结果,用户更容易理解。
  • 权限拒绝、工具失败和中断更容易定位到具体 tool_use_id

Claude Code 并不是没有并发,而是把并发放在更高层:例如 AgentTool 或团队/多 Agent 能力可以启动多个独立 loop。单个 loop 保持可推理,多 Agent 层负责并行。

13. 设计权衡

13.1 会话控制器与执行循环拆分

优点:

  • QueryEngine 可以专注 SDK/会话语义,包括 transcript、usage、权限审计、消息归一化。
  • queryLoop() 可以专注模型和工具之间的执行闭环。
  • 测试边界更清晰:状态管理与循环推进可以分开验证。

代价:

  • 一条用户请求会跨多个抽象层,初学者容易误以为 QueryEngine.ts 就是全部 Agent Loop。
  • 消息在内部格式、API 格式、SDK 格式之间多次转换,需要严格维护配对关系。

13.2 显式 tool_result 回流

优点:

  • 模型不会“自动知道”工具结果,所有结果都在本地显式写回 messages。
  • transcript、resume、debug 都能看到完整因果链。
  • 可以在中断或异常时补齐缺失的 tool_result,避免 API 看到不成对的工具消息。

代价:

  • 工具执行、错误恢复和消息修复逻辑必须非常谨慎,尤其是流式 fallback 或 abort 场景。

13.3 每轮主动整理上下文

优点:

  • 长会话可持续运行,不会无限膨胀。
  • tool result budget、microcompact、autocompact、context collapse 可以分别处理不同类型的上下文压力。

代价:

  • “内存中的完整历史”和“发给模型的可见视图”并不总是一致,调试时需要区分这两个概念。

14. 软件视图总结

Claude Code Agent Loop

会话控制面 QueryEngine

状态持有

mutableMessages

readFileState

totalUsage

permissionDenials

输入准备

processUserInput

fetchSystemPromptParts

thinking/model selection

输出适配

normalizeMessage

SDK result

api_retry

持久化

recordTranscript

flushSessionStorage

执行数据面 queryLoop

上下文整形

compact boundary

tool result budget

microcompact

autocompact

context collapse

模型调用

streaming

usage

stop_reason capture

工具闭环

scan tool_use

canUseTool

execute tool

emit tool_result

退出控制

no tool_use

maxTurns

budget

structured output retry

execution error

产品化能力

流式体验

中断恢复

权限审计

多 Agent 并发

会话 resume

从软件设计角度看,Claude Code 的 Agent Loop 成熟之处不在于 while (true) 本身,而在于它围绕这个循环建立了完整的工程外壳:上下文构建、工具闭环、权限审计、流式输出、持久化恢复、token/成本记账和多种压缩策略。QueryEngine 让一轮请求具备“产品会话”的完整语义,queryLoop() 则让模型和工具能在一个可控、可恢复、可观测的闭环中持续协作。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐