本章节深入解析 Claude Code 的核心执行引擎——Agentic Loop(智能体循环)。这是 src/query.ts 中 queryLoop() 异步生成器函数实现的状态机,驱动 AI 从接收用户请求到返回最终响应的完整生命周期。Agentic Loop 不仅是技术实现,更是 Claude Code 区别于传统聊天机器人的核心范式:逐步推理、动态执行、错误恢复。
Sources: src/query.ts
核心范式:为什么需要 Agentic Loop
传统聊天机器人的交互模式是一问一答:用户发送消息,AI 生成回复,交互结束。这种模式在简单问答场景足够有效,但无法处理需要多步骤操作的任务——如”找到项目中所有未使用的导入语句并删除它们”,这类任务需要:
- 思考→行动→观察→再思考的反复迭代
- 动态上下文管理:每步操作的结果影响下一步的决策
- 即时错误恢复:工具执行失败时能修正策略继续执行
Agentic Loop 将这种能力封装在一个 while(true) 无限循环中,每次迭代代表一个完整的思考-行动-观察周期,直到任务完成或遇到不可恢复的错误。
Sources: src/query.ts
循环结构:四阶段架构
Agentic Loop 的每次迭代包含四个串行阶段,形成完整的状态机周期:
flowchart TB
subgraph Phase1["阶段 1:上下文预处理"]
A[messagesForQuery 原始消息]
A --> B[applyToolResultBudget]
B --> C[snipCompact 微片压缩]
C --> D[microcompact 工具结果摘要]
D --> E[contextCollapse 上下文折叠]
E --> F[autocompact 自动压缩]
F --> G[处理后的消息]
end
subgraph Phase2["阶段 2:流式 API 调用"]
G --> H[deps.callModel 发起请求]
H --> I[收集 assistantMessages]
I --> J[提取 toolUseBlocks]
J --> K[streamingToolExecutor 并行执行]
end
subgraph Phase3["阶段 3:工具执行"]
K --> L{needsFollowUp?}
L -->|Yes| M[执行工具]
L -->|No| N[检查终止条件]
M --> O[toolResults 累积]
end
subgraph Phase4["阶段 4:终止/继续"]
N --> P{7种终止条件?}
P -->|满足| Q[return Terminal]
P -->|不满足| R[continue 下一轮]
O --> R
end
阶段 1:上下文预处理管道
在调用 API 之前,消息数组依次经过 5 个压缩/优化步骤:
| 步骤 | 函数 | 功能 | 特征 |
|---|---|---|---|
applyToolResultBudget |
src/utils/toolResultStorage.ts |
工具结果按 maxResultSizeChars 截断 |
缓存安全 |
snipCompact |
src/services/compact/snipCompact.ts |
历史 Snip 压缩 | HISTORY_SNIP feature |
microcompact |
src/services/compact/microCompact.ts |
工具结果摘要化 | 缓存编辑支持 |
contextCollapse |
src/services/contextCollapse/index.ts |
上下文折叠 | CONTEXT_COLLAPSE feature |
autocompact |
src/services/compact/autoCompact.ts |
超阈值时自动压缩 | 跨迭代追踪 |
每个步骤的输出是下一步的输入,形成串行管道。Snip 和 Microcompact 释放的 token 数会传递给 autocompact 的阈值计算(snipTokensFreed),避免重复压缩。
Sources: src/query.ts Sources: src/query/deps.ts
阶段 2:流式 API 调用
deps.callModel() 发起流式请求,返回 AsyncGenerator。在流式过程中,关键机制包括:
并行工具执行:当 streamingToolExecution 特性启用时,StreamingToolExecutor 在流式过程中就开始并行执行工具,不等流结束:
// src/query.ts:519-530
const streamingToolExecutor = useStreamingToolExecution
? new StreamingToolExecutor(
toolUseContext.options.tools,
canUseTool,
toolUseContext,
)
: null
错误暂扣机制:可恢复的错误(prompt-too-long、max-output-tokens)被暂扣(withheld),先尝试恢复再决定是否暴露给用户:
// src/query.ts:750-780
if (reactiveCompact?.isWithheldPromptTooLong(message as Message)) {
withheld = true
}
if (isWithheldMaxOutputTokens(message)) {
withheld = true
}
if (!withheld) {
yield yieldMessage
}
流式降级检测:当 streamingFallbackOccured 时,已收集的 assistantMessages 被标记为 tombstone,清空后重试。
Sources: src/query.ts Sources: src/services/tools/StreamingToolExecutor.ts
阶段 3:工具执行
工具执行支持两种模式,通过 streamingToolExecutor 标志切换:
// src/query.ts:1393-1398
const toolUpdates = streamingToolExecutor
? streamingToolExecutor.getRemainingResults() // 流式:获取已完成的+等待中的
: runTools(toolUseBlocks, assistantMessages, canUseTool, toolUseContext)
并行安全分区:partitionToolCalls() 将工具分为并发安全和串行执行批次:
flowchart LR
subgraph "并发安全工具 (并行)"
A[Glob] --> B[Grep]
B --> C[Read]
C --> D[...]
end
subgraph "非并发安全工具 (串行)"
E[Bash] --> F[Write]
F --> G[Edit]
end
Sources: src/services/tools/toolOrchestration.ts
阶段 4:终止条件判定
每次迭代结束时,根据条件决定 return(终止)或 continue(继续):
// src/query.ts:1095-1100
if (!needsFollowUp) {
// ... 恢复路径检查 ...
return { reason: 'completed' }
}
Sources: src/query.ts
状态机:State 对象
循环状态通过 State 类型在迭代间传递,采用不可变更新模式:
// src/query.ts:204-217
type State = {
messages: Message[] // 当前对话消息
toolUseContext: ToolUseContext // 工具上下文(含权限)
autoCompactTracking: AutoCompactTrackingState // 压缩跟踪
maxOutputTokensRecoveryCount: number // 输出截断恢复计数
hasAttemptedReactiveCompact: boolean // 是否已尝试即时压缩
maxOutputTokensOverride: number | undefined // 输出 token 上限覆盖
pendingToolUseSummary: Promise<...> | undefined // 异步工具摘要
stopHookActive: boolean | undefined // Stop hook 是否激活
turnCount: number // 轮次计数
transition: Continue | undefined // 上一次继续的原因
}
每次 continue 创建新的 State 对象(不可变更新),而非就地修改。transition 字段记录继续原因,让后续迭代能检测特定恢复路径避免循环。
Sources: src/query.ts
七种终止条件
| 终止原因 | 触发机制 | 源码位置 |
|---|---|---|
| completed | AI 未发出 tool_use → needsFollowUp = false |
src/query.ts:1380 |
| blocking_limit | Token 超过硬限制(非 autocompact 模式) | src/query.ts:646 |
| aborted_streaming | abortController.signal.aborted |
src/query.ts:1054 |
| model_error | callModel() 抛出异常 |
src/query.ts:999 |
| prompt_too_long | 413 错误且恢复失败 | src/query.ts:1178 |
| image_error | 图片尺寸/大小错误 | src/query.ts:980 |
| stop_hook_prevented | Stop hook 返回 preventContinuation |
src/query.ts:1282 |
Sources: src/query.ts
四种恢复路径
Agentic Loop 不仅处理正常流程,还包含多种错误恢复机制:
1. max_output_tokens 恢复
当 AI 输出被截断时(apiError === 'max_output_tokens'):
// src/query.ts:1200-1255
// 首次:尝试将 maxOutputTokens 提升到 64K,静默重试
if (capEnabled && maxOutputTokensOverride === undefined) {
state = { ...state, maxOutputTokensOverride: ESCALATED_MAX_TOKENS }
continue
}
// 后续:注入恢复消息,最多重试 3 次
if (maxOutputTokensRecoveryCount < MAX_OUTPUT_TOKENS_RECOVERY_LIMIT) {
const recoveryMessage = createUserMessage({
content: "Output token limit hit. Resume directly...",
isMeta: true,
})
// ...
}
Sources: src/query.ts Sources: src/utils/context.ts
2. Prompt-Too-Long 恢复
当遇到 413 错误时,有两个恢复阶段:
flowchart TD
A[413 Prompt Too Long] --> B{CONTEXT_COLLAPSE?}
B -->|Yes| C[Context Collapse Drain]
C --> D{有 staged collapses?}
D -->|Yes| E[提交折叠,释放空间]
E --> F[重试]
D -->|No| G[Reactive Compact]
B -->|No| G
G --> H{压缩成功?}
H -->|Yes| F
H -->|No| I[暴露错误,终止]
Sources: src/query.ts
3. 流式降级恢复
当主模型不可用时(FallbackTriggeredError):
// src/query.ts:897-910
if (innerError instanceof FallbackTriggeredError && fallbackModel) {
currentModel = fallbackModel
attemptWithFallback = true
// 清除已收集的消息,切换模型重试
assistantMessages.length = 0
toolUseBlocks.length = 0
needsFollowUp = false
continue
}
Sources: src/query.ts
4. Stop Hook 阻塞重试
Stop hook 可以注入阻塞错误消息,强制 AI 重新思考:
// src/query.ts:1285-1308
if (stopHookResult.blockingErrors.length > 0) {
state = {
messages: [...messagesForQuery, ...assistantMessages, ...stopHookResult.blockingErrors],
stopHookActive: true,
transition: { reason: 'stop_hook_blocking' },
}
continue
}
Sources: src/query/stopHooks.ts
完整迭代示例
以用户请求”找到项目中所有未使用的导入语句”为例:
┌─────────────────────────────────────────────────────────────────────┐
│ 迭代 1: 思考 → 行动 │
├─────────────────────────────────────────────────────────────────────┤
│ 预处理: 无需压缩(上下文很短) │
│ API: 返回 tool_use(Glob, "**/*.ts") │
│ 工具: 返回 42 个文件路径 │
│ → needsFollowUp = true, continue │
└─────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ 迭代 2: 思考 → 行动 │
├─────────────────────────────────────────────────────────────────────┤
│ 预处理: 42 个文件结果仍在预算内 │
│ API: 返回 tool_use(Grep, "import.*from") │
│ 工具: 在 15 个文件中找到 120 条 import │
│ → needsFollowUp = true, continue │
└─────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ 迭代 3: 多轮微调 │
├─────────────────────────────────────────────────────────────────────┤
│ 预处理: 120 条结果触发 microcompact → 摘要化 │
│ API: 返回 3 个 tool_use(FileEdit, ...) │
│ 工具: 删除 5 条未使用导入 │
│ → needsFollowUp = true, continue │
└─────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ 迭代 4: 总结 │
├─────────────────────────────────────────────────────────────────────┤
│ API: 返回纯文本响应 │
│ → needsFollowUp = false │
│ → Stop hooks 通过 │
│ → return { reason: 'completed' } │
└─────────────────────────────────────────────────────────────────────┘
Sources: src/query.ts
与 QueryEngine 的关系
QueryEngine(src/QueryEngine.ts)是 Agentic Loop 之上的会话编排器,管理跨多轮对话的状态:
| 维度 | QueryEngine | Agentic Loop |
|---|---|---|
| 作用域 | 整个会话 | 单次用户输入 |
| 生命周期 | 整个 REPL 会话 | 一次 submitMessage() 调用 |
| 核心职责 | 会话状态、transcript 持久化、成本追踪 | API 调用、工具执行、错误恢复 |
sequenceDiagram
participant User as 用户
participant REPL as REPL
participant QE as QueryEngine
participant AL as Agentic Loop
User->>REPL: 输入命令
REPL->>QE: submitMessage()
loop 多轮 Agentic Loop
QE->>AL: query()
AL->>AL: 上下文预处理 → API 调用 → 工具执行
alt needsFollowUp = true
AL->>AL: continue
else needsFollowUp = false
AL-->>QE: Terminal
end
end
QE-->>REPL: yield SDKMessage
REPL-->>User: 显示结果
Sources: src/QueryEngine.ts
设计原则
| 原则 | 实现方式 | 价值 |
|---|---|---|
| 流式优先 | deps.callModel() 返回 AsyncGenerator,StreamingToolExecutor 在流式过程中并行执行工具 |
响应延迟从 N×API延迟降至 1×API延迟 |
| 不可变状态 | State 对象采用不可变更新模式 | 可追踪性、测试性、并行安全 |
| 错误隔离 | 可恢复错误暂扣,不立即暴露 | 给 AI 自我修正的机会 |
| 上下文感知 | 每轮迭代前重新评估压缩需求 | 精确的资源管理 |
| 用户可控 | abortController.signal 在多个检查点被检测 |
用户随时可优雅中断 |
Sources: src/query.ts
下一步
了解 Agentic Loop 的核心循环后,建议继续学习:
- QueryEngine 编排机制 – 深入了解会话编排层如何调用 Agentic Loop
- 会话状态管理 – 了解消息持久化和成本追踪
- 工具系统架构 – 了解 AI 的”双手”如何被调用
相关条目
- 6-wu-ceng-jia-gou-she-ji
- 8-queryengine-bian-pai-ji-zhi