学习ClaudeCode源码之Agent核心循环

最近在研读 ClaudeCode 源码,其中 Agent 核心循环的设计堪称精巧,读完收获很大。这篇文章就从上到下完整梳理一遍核心循环的代码逻辑,既做一次学习记录,也为后续自己做类似项目积累可直接借鉴的设计思路。

全局概览

kotlin 复制代码
                            开始
                              │
                              ▼
                            ┌──────────────────┐
                            │ 1. 初始化        │
                            │    状态/预取      │
                            └────────┬─────────┘
                                     │
                                     ▼
                                ┌────────┐
                            ◀───│ while  │─── 继续  ◀─────┐
                                │ (true) │                 │
                                └────┬───┘                 │
                                     │                     │
                                     ▼                     │
                            ┌──────────────────┐           │
                            │ 2. 上下文管理     │           │
                            │    (多层压缩链)   │           │
                            └────────┬─────────┘           │
                                     │                     │
                                     ▼                     │
                            ┌──────────────────┐           │
                            │ 3. 调用 AI 模型   │           │
                            │    流式处理       │           │
                            └────────┬─────────┘           │
                                     │                     │
                                     ▼                     │
                               ┌───────────┐               │
                               │ 有工具调用?│               │
                               └─────┬─────┘               │
                                是   │   否                │
                                ▼    │    ▼                │
                            ┌──────┐ │ ┌──────┐            │
                            │执行  │ │ │后处理│             │
                            │工具  │ │ │返回  │             │
                            └──┬───┘ │ └──────┘            │
                               │     │                      │
                               └─────┴──────────────────────┘
                                     │
                                     ▼
                            ┌──────────────────┐
                            │ 4. 组装并递归     │
                            │    state = next  │
                            └────────┬─────────┘
                                     │
                                     └── continue ──▶ while(true)

初始化循环状态

state:记录了循环过程中所有可变的上下文信息,确保多轮循环的连贯性。

详情如下:

ts 复制代码
  let state: State = {
    //完整对话历史
    //工具执行上下文
    //最大输出token数
    //自动压缩跟踪状态
    //是否已激活停止钩子
    //最大输出token恢复次数
    //应急压缩标记
    //当前轮数
    //待处理工具使用摘要
    //上一次迭代继续的原因
  }

主循环

就是一个 while(true) 循环、进入循环可以依次看到:

上下文管理

压缩机制:

  1. applyToolResultBudget: 工具结果存储优化,过大结果截断并持久到磁盘
  1. snipCompact :裁剪过旧的工具结果内容,精简历史消息。
  2. microcompact:超时自动卸载缓存的工具结果到磁盘,释放内存。
  3. contextCollapse:如果上下文折叠特性开启,执行折叠(智能折叠代码块、测试文件等)
  4. autocompact:执行自动压缩,生成摘要替换大量历史消息。返回压缩结果和连续失败次数。

上下文压缩后续代码还有一层:reactive compact(应急压缩 ),相关逻辑在无工具调用章节错误处理部分。

构建系统提示词:

系统提示分为两部分拼接,结构非常清晰:

  • systemPrompt:AI 身份、能力、MCP 工具规则、输出风格、语气等;
  • systemContext:动态的环境信息,此上下文会添加到每段对话开头,并在对话期间进行缓存。包括 Git 状态、缓存破坏器(仅 Ant 环境,用于强制刷新缓存)等。
ts 复制代码
const fullSystemPrompt = asSystemPrompt(
  appendSystemContext(systemPrompt, systemContext),
);

LLM 调用与流式处理

处理完上下文之后,就该调用LLM了。

调用 LLM:callModel(一大堆参数)

ts 复制代码
    for await (const message of deps.callModel({  // ... 一大堆参数
            messages: prependUserContext(messagesForQuery, userContext),  //把用户上下文加到消息列表前面
            systemPrompt: fullSystemPrompt,  // 系统提示
            thinkingConfig: toolUseContext.options.thinkingConfig,  // 思考配置
            tools: toolUseContext.options.tools,  // 工具列表
            signal: toolUseContext.abortController.signal,  // 终止信号
            options: {  // 模型调用选项
              // 获取工具权限上下文(异步)
               // 当前使用的模型
              // 快速模式(如果启用)
              // 工具选择策略
              // 是否非交互式会话
              // 回退模型 :当主模型不可用 时(比如 Claude Opus 负载过高),自动切换到的 备用模型 (比如 Claude Sonnet)
              // 查询来源
              // Agent 定义
              // 追加系统提示
              // 最大输出 token 数
              // MCP 工具列表
              // 查询追踪
              // 努力值和建议模型
              // 跳过缓存写入  是否跳过缓存写入
              // 任务预算
          }))

消息处理逻辑

  1. if (streamingFallbackOccured):如果发生流式回退,将之前生成的消息标记为"墓碑"(从界面移除),清空所有收集的消息,重置状态;
  2. 为工具调用块的输入字段;
  3. 检查消息是否是可恢复的错误(提示过长、最大输出 token 等)。
  1. 收集助手消息和工具调用块;
  2. 处理模型回退错误 切换到回退模型,处理已生成的消息;

回退模型 :当主模型不可用 时(比如 Claude Opus 负载过高),自动切换到的 备用模型 (比如 Claude Sonnet)。

  1. 处理模型调用错误: 记录错误,返回模型错误状态(退出循环)

后处理与恢复:

  1. 执行后采样钩子executePostSamplingHooks,比如会话记忆(sessionMemory)/技能改进(skillImprovement);

  2. 处理流式中断: 用户按 Escape 时,尽可能优雅地停止一切正在做的事情,给用户一个清晰的反馈,然后退出这一轮对话;

  3. 从上一轮对话中生成工具使用总结pendingToolUseSummary。实现方法:这里使用的是 Claude 轻量级模型(Haiku),快速生成摘要。Haiku(约1秒),而主模型的流式响应需要 5-30 秒。

无工具调用时的处理

如果没有工具调用,处理最后一条消息,阻断式编程,先进行多种错误拦截与恢复:

  1. 处理 isWithheld413:prompt-too-long 错误,

    • 首先尝试 context-collapse 恢复(上下文折叠)

    • 如果 context-collapse 无法恢复,尝试 reactive compact(应急压缩 )

      • 如果成功,更新状态并继续
      • 如果失败,产出错误消息并退出return { reason: 'prompt_too_long' }
  2. 处理isWithheldMaxOutputTokens:max_output_tokens 错误:首先尝试升级重试(从 8k 提升到 64k),如果不行,进行多轮恢复(最多 3 次),如果恢复耗尽,产出错误消息

  3. 执行 stop hooks:

  4. Token 预算控制机制:

    • 预算未用完:添加提示消息 → 重置恢复计数 → 继续下一轮
    • 预算已用完:记录完成事件 → 退出循环
    • 边际收益递减:提前停止 → 记录日志 → 退出循环

如果没有错误,就正常完成 return { reason: 'completed' }, 退出循环

有工具调用时的处理

两种执行模式 ,根据是否使用了流式工具执行,选择不同的工具执行路径:

  1. 流式执行 vs 批量执行 :
执行模式 触发条件 执行逻辑 优势
流式执行(Streaming) 存在 streamingToolExecutor 多工具并行执行,谁先完成谁先返回结果 速度快,响应流畅,适合多工具调用场景
批量执行(Batch) 无流式执行器 工具按顺序执行,所有工具完成后统一返回结果 逻辑简单,适合工具间有依赖的场景
  1. 批量工具执行完成后生成工具使用摘要,并将该摘要传递至下一次递归调用

    • 提取最后一条助手消息的文本作为上下文
    • 收集工具信息用于生成摘要
    • 提取结果内容
    • 生成工具使用摘要generateToolUseSummary(异步,不阻塞下一轮 API 调用):生成摘要后保存到 nextPendingToolUseSummary,在下一轮使用之前就可以生成.
  2. 处理工具执行中断:

    • 清理计算机使用状态
    • 产出中断消息
    • 如果达到最大轮数,产出提示
    • 返回工具中断状态 return { reason: 'aborted_tools' } // 退出循环

上下文组装与迭代

最后的阶段,确保了 Agent 的上下文能够无缝传递给下一轮:

  1. 获取命令队列快照,按优先级筛选 。主线程只取无 agentId 的命令,子 Agent 只取针对自己的任务;

  2. 产出附件消息(文件变更、任务通知、记忆、技能等)

  3. 记忆预取和消费pendingMemoryPrefetch,保证记忆不被重复使用;

  4. 注入 skill :注入技能发现(Skill Discovery)的结果;

  5. 从命令队列中移除已消费的命令;

  6. 刷新 mcp 工具:在每次轮次之间刷新工具,使新连接的MCP服务器可用;

  7. 轮次计数:每执行一轮工具调用,轮次计数 +1;

  8. 主 Agent 生成任务摘要(用于 claude ps);

  9. 检查是否达到最大轮数限制

  10. 构建下一轮循环状态 state:

    • 合并所有消息messages,toolUseContext等;
    • 重置恢复计数器;
    • 传递待处理的工具摘要,把本轮生成的摘要传给下一轮: pendingToolUseSummary: nextPendingToolUseSummary;
    • 设置迭代原因为"下一轮",回到循环开头。

到这里整个循环逻辑就从上到下走了一遍。

循环关键返回值总结

返回值 含义
{ reason: 'completed' } 正常完成
{ reason: 'aborted_streaming' } 流式中断
{ reason: 'aborted_tools' } 工具执行中断
{ reason: 'blocking_limit' } 超过阻塞限制
{ reason: 'prompt_too_long' } 提示过长
{ reason: 'max_turns' } 达到最大轮数
{ reason: 'stop_hook_prevented' } stop hook 阻止
{ reason: 'hook_stopped' } hook 停止

借鉴设计思想

整个代码看下来,不愧是"宇宙最强",觉得可以借鉴一二:

  1. 多层上下文压缩:层层压缩,保证多轮对话质量与成本平衡;
  2. 流式工具执行:调用工具极快,在 LLM 生成的过程中,有些工具已经执行完毕。 体验极快,只是工程复杂度更高;
  3. 错误处理与恢复:多种错误处理与兜底、重试机制等;
  4. 异步处理: 工具摘要、记忆预取、技能刷新都异步做,不阻塞主交互流程。

最后也分享个彩蛋。在源代码开头有一段Claudecode开发者的注释,可以看到其精神状态,也是相当幽默,估计也踩过很多坑。取了其中一段翻译一下:

年轻的巫师,请谨记这些规则。它们是thinking的法则,而thinking的法则即是宇宙的法则。倘若你不遵守这些规则,将会遭受整日调试代码、抓耳挠腮的惩罚。

本人水平有限,文章难免有不严谨之处,后续还会继续深挖学习。

相关推荐
挖稀泥的工人4 小时前
AI聊天界面的布局细节和打字跟随方法
前端·javascript·面试
一个人说晚安4 小时前
课题汇报:基于扩散大模型引导的冷冻电镜原子结构自动化解析
人工智能·数据挖掘
竹林8184 小时前
从“连接失败”到丝滑登录:我用 ethers.js 连接 MetaMask 的完整踩坑记录
前端·javascript
龙文浩_4 小时前
AI梯度下降与PyTorch张量操作技术指南
人工智能·pytorch·python·深度学习·神经网络·机器学习·自然语言处理
颜酱4 小时前
图片大模型实践:可灵(Kling)文生图前后端实现
前端·javascript·人工智能
清空mega4 小时前
动手学深度学习——样式迁移
人工智能·深度学习
yuanzhengme4 小时前
AI【应用 04】FunASR离线文件转写服务开发指南(实践篇)
人工智能·macos·xcode
Reart4 小时前
从0解构tinyWeb项目--(Day:2)
javascript·后端·架构
木斯佳4 小时前
前端八股文面经大全:腾讯CSIG实习面(2026-04-10)·面经深度解析
前端·ai·xss·埋点·实习面经