深入学习Claude Code 01: 核心Agent Loop

12 阅读7分钟

昨天 Anthropic 官方 Claude Code CLI 发生了泄漏,GitHub,X冲上热门。我也是快速download下来开始学习分析这个号称最强编程Agent IDE到底是如何实现的。

如果你也感兴趣源码,可以私聊我,我发你。

趁着热情没有消退,今天我们直接来分析最核心的Agent Loop Claude Code 是怎么做的。

Agent Loop 中涉及到的细节 -- 压缩/错误恢复/Token计费/Memory & Skill加载,我们会再拆分单元研究。

我也在做一个基于Claude Code的核心逻辑的Agent SDK。我把核心的Agent Loop 提取出来,包装成了SDK codenano方便大家学习Claude Code的核心逻辑。需要用到Agent的时候也可以直接嵌入Claude Code的Agent。

Claude Code Agent Loop 详细分析

总统架构流程

flowchart TD
    Start([开始]) --> Init[初始化状态]
    
    Init --> Loop{主循环}
    
    Loop --> Compact[压缩检查<br/>Snip/Micro/Collapse/Auto]
    
    Compact --> API[调用 Claude API<br/>流式输出]
    
    API --> HasTools{有工具调用?}
    
    HasTools -->|否| StopHooks[Stop Hooks<br/>质量检查]
    HasTools -->|是| ExecTools[执行工具<br/>并发/流式]
    
    ExecTools --> Attach[添加附件<br/>Memory/Skill]
    
    Attach --> CheckTurns{达到最大轮次?}
    CheckTurns -->|是| End([结束])
    CheckTurns -->|否| Loop
    
    StopHooks --> Blocking{阻塞错误?}
    Blocking -->|是| Loop
    Blocking -->|否| Budget{Token Budget?}
    
    Budget -->|继续| Loop
    Budget -->|完成| End
    
    API -.错误.-> Recovery[错误恢复<br/>Collapse/Reactive/Escalate]
    Recovery --> Loop

概述

Claude Code 的 Agent Loop 是一个复杂的状态机,位于 query.tsqueryLoop 函数中(第 241-1729 行)。

核心结构

1. 循环入口

async function* queryLoop(
  params: QueryParams,
  consumedCommandUuids: string[],
): AsyncGenerator<StreamEvent | Message, Terminal>

特点

  • 异步生成器函数
  • 流式输出事件
  • 返回终止原因

2. 状态管理

type State = {
  messages: Message[]
  toolUseContext: ToolUseContext
  autoCompactTracking: AutoCompactTrackingState | undefined
  maxOutputTokensRecoveryCount: number
  hasAttemptedReactiveCompact: boolean
  maxOutputTokensOverride: number | undefined
  pendingToolUseSummary: Promise<ToolUseSummaryMessage | null> | undefined
  stopHookActive: boolean | undefined
  turnCount: number
  transition: Continue | undefined
}

设计要点

  • 所有状态集中在一个对象
  • 每次 continue 时整体替换
  • transition 字段记录为什么继续

字段详解

  1. messages: Message[] 用途:存储当前对话的所有消息

生命周期

  • 初始化:从 params.messages 复制
  • 更新:每次 continue 时添加新消息
  • 压缩:可能被压缩机制修改

示例

messages: [
  { role: 'user', content: 'Hello' },
  { role: 'assistant', content: 'Hi!' },
  { role: 'user', content: 'tool_result', ... }
]
  1. toolUseContext: ToolUseContext 用途:工具执行的上下文环境

包含内容

  • options: 工具列表、模型配置等
  • abortController: 中断控制器
  • readFileState: 文件读取缓存
  • queryTracking: 查询追踪信息

更新时机

  • 工具执行后可能更新
  • 每次迭代开始时可能添加 queryTracking
  1. autoCompactTracking: AutoCompactTrackingState | undefined 用途:追踪自动压缩状态

结构

{
  compacted: boolean           // 是否已压缩
  turnId: string              // 压缩的唯一 ID
  turnCounter: number         // 压缩后的轮次计数
  consecutiveFailures: number // 连续失败次数
}

生命周期

  • 初始:undefined
  • 压缩成功:设置为新的 tracking 对象
  • 压缩失败:更新 consecutiveFailures
  1. maxOutputTokensRecoveryCount: number 用途:追踪 max_tokens 恢复次数

限制:最多 3 次 重置时机:每次成功完成一轮后重置为 0

  1. hasAttemptedReactiveCompact: boolean 用途:标记是否已尝试 Reactive Compact

作用:防止无限循环重试 重置时机:每次正常工具执行后重置

  1. maxOutputTokensOverride: number | undefined 用途:覆盖默认的 max_tokens

场景

  • 8K → 64K 升级
  • 临时调整输出限制
  1. pendingToolUseSummary: Promise<...> | undefined 用途:异步生成工具使用摘要

特点

  • 在工具执行时启动(不阻塞)
  • 在下一轮迭代时消费
  • 使用 Haiku 快速生成
  1. stopHookActive: boolean | undefined stopHook 我们后面会讲解

用途:标记 Stop Hook 是否已激活

作用:防止 Stop Hook 无限循环重试

  1. turnCount: number 用途:当前轮次计数

用途

  • 追踪执行进度
  • 检查是否达到 maxTurns
  1. transition: Continue | undefined 用途:记录为什么继续循环

可能的值

  • collapse_drain_retry - Context Collapse 恢复
  • reactive_compact_retry - Reactive Compact 恢复
  • max_output_tokens_escalate - Token 限制升级
  • max_output_tokens_recovery - Token 恢复消息
  • stop_hook_blocking - Stop Hook 阻塞
  • token_budget_continuation - Token 预算继续
  • next_turn - 正常下一轮

作用

  • 调试和追踪
  • 测试验证
  • 分析循环行为

主循环流程

while (true) 循环结构

while (true) {
  1. 解构状态
  2. 预取和初始化
  3. 压缩检查
  4. API 调用
  5. 错误恢复
  6. 工具执行
  7. Stop Hooks
  8. 继续或退出
}

每次迭代的步骤

Step 1: 解构状态 (第 311-321 行)
let { toolUseContext } = state
const {
  messages,
  autoCompactTracking,
  maxOutputTokensRecoveryCount,
  hasAttemptedReactiveCompact,
  maxOutputTokensOverride,
  pendingToolUseSummary,
  stopHookActive,
  turnCount,
} = state
Step 2: 压缩流程 (第 365-468 行)

执行顺序

  1. Tool Result Budget - 限制工具结果大小
  2. Snip Compact - 剪切历史
  3. Microcompact - 压缩工具结果
  4. Context Collapse - 上下文折叠
  5. Autocompact - 自动压缩

关键点

  • 按顺序执行,每个都可能修改 messagesForQuery
  • snipTokensFreed 传递给 autocompact
  • 压缩后更新 tracking 状态
Step 3: API 调用 (第 653-863 行)

流程

while (attemptWithFallback) {
  try {
    for await (const message of deps.callModel(...)) {
      // 处理流式消息
      // 启动流式工具执行
      // 收集 tool_use blocks
    }
  } catch (FallbackTriggeredError) {
    // 切换到 fallback 模型重试
  }
}

关键特性

  • 流式输出
  • 错误扣留(withheld)
  • Streaming Tool Execution
  • Model Fallback
Step 4: 错误恢复 (第 1062-1183 行)

恢复策略

  1. Prompt Too Long (413)

    • Context Collapse drain
    • Reactive Compact
    • 错误扣留 → 恢复 → 成功则对用户透明
  2. Max Output Tokens

    • 升级:8K → 64K
    • 注入恢复消息(最多 3 次)
  3. Media Size Error

    • Reactive Compact strip-retry
Step 5: 工具执行 (第 1364-1409 行)

流程

const toolUpdates = streamingToolExecutor
  ? streamingToolExecutor.getRemainingResults()
  : runTools(toolUseBlocks, assistantMessages, canUseTool, toolUseContext)

for await (const update of toolUpdates) {
  if (update.message) {
    yield update.message
    toolResults.push(...)
  }
}

特点

  • 流式工具执行器优先
  • 并发执行工具
  • 实时返回结果
Step 6: Stop Hooks (第 1267-1306 行)

什么是 Stop Hooks?

想象你在写代码,AI 助手帮你完成了一段工作。在它说"完成了"之前,Stop Hooks 就像一个自动检查员,会先检查一遍:

  • 代码有没有语法错误?
  • 测试通过了吗?
  • 格式规范吗?

如果检查不通过,它会让 AI 重新修改,直到满足要求。

简单来说:Stop Hooks 是在 AI 每次回答结束时自动运行的检查程序。

工作原理

执行时机 AI 完成一轮回答后,如果没有需要调用工具(比如读文件、运行命令),就会触发 Stop Hooks。

返回结果 Stop Hook 检查完后会告诉系统两件事:

  1. 有没有错误需要修复(blockingErrors)- 如果有,AI 会重新工作
  2. 要不要停止继续(preventContinuation)- 如果是,整个流程就结束了

四种执行结果

✅ 成功(Success) 检查通过,一切正常,继续下一步。

⚠️ 非阻塞错误(Non-Blocking Error) 有小问题,但不影响继续,只是记录一下。

例子:代码格式不太规范,但功能正确。

🚫 阻塞错误(Blocking Error) 有严重问题,必须修复才能继续。

例子:测试失败,AI 必须重新修改代码。

🛑 停止继续(Stopped Continuation) 检查发现不应该继续了,直接结束。

例子:发现用户取消了任务。

核心特性

  1. 并行执行 如果配置了多个检查(比如同时检查代码格式和运行测试),它们会同时进行,节省时间。

  2. 实时进度 可以看到每个检查的进度,比如"正在运行测试... 50%"。

  3. 错误汇总 所有检查完成后,会生成一份报告,告诉你哪些通过了,哪些失败了。

两种模式

阻塞模式(blocking: true)

  • 检查失败 → AI 必须重新修改
  • 适合:测试、编译等必须通过的检查

非阻塞模式(blocking: false)

  • 检查失败 → 只记录警告,继续执行
  • 适合:代码格式、文档检查等非关键项

防止死循环

系统会记录是否已经重试过,避免 AI 无限次修改同一个问题。

Step 7: Token Budget (第 1308-1355 行)

自动继续机制

if (decision.action === 'continue') {
  incrementBudgetContinuationCount()
  state = {
    ...state,
    messages: [...messages, createUserMessage({
      content: decision.nudgeMessage,
      isMeta: true,
    })],
  }
  continue
}

特点

  • 追踪 token 使用
  • 接近预算时自动继续
  • 检测收益递减

Continue 触发点

循环通过 continue 语句重新开始,主要触发点:

  1. collapse_drain_retry - Context Collapse 恢复
  2. reactive_compact_retry - Reactive Compact 恢复
  3. max_output_tokens_escalate - Token 限制升级
  4. max_output_tokens_recovery - Token 恢复消息
  5. stop_hook_blocking - Stop Hook 阻塞
  6. token_budget_continuation - Token 预算继续
  7. next_turn - 正常下一轮

退出条件

循环通过 return 退出,主要原因:

  1. completed - 正常完成
  2. aborted_streaming - 用户中断(流式阶段)
  3. aborted_tools - 用户中断(工具执行阶段)
  4. blocking_limit - 达到阻塞限制
  5. prompt_too_long - 提示过长且无法恢复
  6. image_error - 图片错误
  7. model_error - 模型错误
  8. stop_hook_prevented - Stop Hook 阻止
  9. hook_stopped - Hook 停止
  10. max_turns - 达到最大轮次

关键设计模式

1. 状态机模式

  • 所有状态集中管理
  • 每次 continue 整体替换
  • transition 字段追踪状态转换

2. 错误扣留 (Error Withholding)

  • 扣留可恢复的错误
  • 尝试恢复
  • 成功则对用户透明

3. 多层恢复策略

  • Context Collapse → Reactive Compact → Surface Error
  • 每层独立尝试,失败则进入下一层

4. 流式执行

  • 工具在模型流式输出时就开始执行
  • 实时返回结果
  • 提升整体性能

总结

Claude Code 的 Agent Loop 是一个高度优化的状态机,包含:

  • 5 种压缩机制(之后章节详解)
  • 3 层错误恢复(之后章节详解)
  • 流式工具执行(之后章节详解)
  • Stop Hooks 系统
  • Token Budget 管理(之后章节详解)