最近在研读 ClaudeCode 源码,其中 Agent 核心循环的设计堪称精巧,读完收获很大。这篇文章就从上到下完整梳理一遍核心循环的代码逻辑,既做一次学习记录,也为后续自己做类似项目积累可直接借鉴的设计思路。
开始 │ ▼ ┌──────────────────┐ │ 1. 初始化 │ │ 状态/预取 │ └────────┬─────────┘ │ ▼ ┌────────┐ ◀───│ while │─── 继续 ◀─────┐ │ (true) │ │ └────┬───┘ │ │ │ ▼ │ ┌──────────────────┐ │ │ 2. 上下文管理 │ │ │ (多层压缩链) │ │ └────────┬─────────┘ │ │ │ ▼ │ ┌──────────────────┐ │ │ 3. 调用 AI 模型 │ │ │ 流式处理 │ │ └────────┬─────────┘ │ │ │ ▼ │ ┌───────────┐ │ │ 有工具调用?│ │ └─────┬─────┘ │ 是 │ 否 │ ▼ │ ▼ │ ┌──────┐ │ ┌──────┐ │ │执行 │ │ │后处理│ │ │工具 │ │ │返回 │ │ └──┬───┘ │ └──────┘ │ │ │ │ └─────┴──────────────────────┘ │ ▼ ┌──────────────────┐ │ 4. 组装并递归 │ │ state = next │ └────────┬─────────┘ │ └── continue ──▶ while(true)
state:记录了循环过程中所有可变的上下文信息,确保多轮循环的连贯性。
详情如下:
let state: State = { //完整对话历史 //工具执行上下文 //最大输出token数 //自动压缩跟踪状态 //是否已激活停止钩子 //最大输出token恢复次数 //应急压缩标记 //当前轮数 //待处理工具使用摘要 //上一次迭代继续的原因
}
就是一个 while(true) 循环、进入循环可以依次看到:
压缩机制:
- applyToolResultBudget: 工具结果存储优化,过大结果截断并持久到磁盘
- snipCompact :裁剪过旧的工具结果内容,精简历史消息。
- microcompact:超时自动卸载缓存的工具结果到磁盘,释放内存。
- contextCollapse:如果上下文折叠特性开启,执行折叠(智能折叠代码块、测试文件等)
- autocompact:执行自动压缩,生成摘要替换大量历史消息。返回压缩结果和连续失败次数。
上下文压缩后续代码还有一层:reactive compact(应急压缩 ),相关逻辑在无工具调用章节错误处理部分。
构建系统提示词:
系统提示分为两部分拼接,结构非常清晰:
- systemPrompt:AI 身份、能力、MCP 工具规则、输出风格、语气等;
- systemContext:动态的环境信息,此上下文会添加到每段对话开头,并在对话期间进行缓存。包括 Git 状态、缓存破坏器(仅 Ant 环境,用于强制刷新缓存)等。
const fullSystemPrompt = asSystemPrompt( appendSystemContext(systemPrompt, systemContext), );
处理完上下文之后,就该调用LLM了。
调用 LLM:callModel(一大堆参数)
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 工具列表 // 查询追踪 // 努力值和建议模型 // 跳过缓存写入 是否跳过缓存写入 // 任务预算 }))
消息处理逻辑:
- if (streamingFallbackOccured):如果发生流式回退,将之前生成的消息标记为"墓碑"(从界面移除),清空所有收集的消息,重置状态;
- 为工具调用块的输入字段;
- 检查消息是否是可恢复的错误(提示过长、最大输出 token 等)。
- 收集助手消息和工具调用块;
- 处理模型回退错误 切换到回退模型,处理已生成的消息;
回退模型 :当主模型不可用 时(比如 Claude Opus 负载过高),自动切换到的 备用模型 (比如 Claude Sonnet)。
- 处理模型调用错误: 记录错误,返回模型错误状态(退出循环)
后处理与恢复:
- 执行后采样钩子
executePostSamplingHooks,比如会话记忆(sessionMemory)/技能改进(skillImprovement); - 处理流式中断: 用户按 Escape 时,尽可能优雅地停止一切正在做的事情,给用户一个清晰的反馈,然后退出这一轮对话;
- 从上一轮对话中生成工具使用总结
pendingToolUseSummary。实现方法:这里使用的是 Claude 轻量级模型(Haiku),快速生成摘要。Haiku(约1秒),而主模型的流式响应需要 5-30 秒。
如果没有工具调用,处理最后一条消息,阻断式编程,先进行多种错误拦截与恢复:
- 处理 isWithheld413:prompt-too-long 错误,
- 首先尝试 context-collapse 恢复(上下文折叠)
- 如果 context-collapse 无法恢复,尝试 reactive compact(应急压缩 )
- 如果成功,更新状态并继续
- 如果失败,产出错误消息并退出return { reason: 'prompt_too_long' }
- 处理isWithheldMaxOutputTokens:max_output_tokens 错误:首先尝试升级重试(从 8k 提升到 64k),如果不行,进行多轮恢复(最多 3 次),如果恢复耗尽,产出错误消息
- 执行 stop hooks:
- Token 预算控制机制:
- 预算未用完:添加提示消息 → 重置恢复计数 → 继续下一轮
- 预算已用完:记录完成事件 → 退出循环
- 边际收益递减:提前停止 → 记录日志 → 退出循环
如果没有错误,就正常完成 return { reason: 'completed' }, 退出循环
两种执行模式 ,根据是否使用了流式工具执行,选择不同的工具执行路径:
- 流式执行 vs 批量执行 :
- 批量工具执行完成后生成工具使用摘要,并将该摘要传递至下一次递归调用
- 提取最后一条助手消息的文本作为上下文
- 收集工具信息用于生成摘要
- 提取结果内容
- 生成工具使用摘要
generateToolUseSummary(异步,不阻塞下一轮 API 调用):生成摘要后保存到nextPendingToolUseSummary,在下一轮使用之前就可以生成.
- 处理工具执行中断:
- 清理计算机使用状态
- 产出中断消息
- 如果达到最大轮数,产出提示
- 返回工具中断状态 return { reason: 'aborted_tools' } // 退出循环
最后的阶段,确保了 Agent 的上下文能够无缝传递给下一轮:
- 获取命令队列快照,按优先级筛选 。主线程只取无 agentId 的命令,子 Agent 只取针对自己的任务;
- 产出附件消息(文件变更、任务通知、记忆、技能等)
- 记忆预取和消费
pendingMemoryPrefetch,保证记忆不被重复使用; - 注入 skill :注入技能发现(Skill Discovery)的结果;
- 从命令队列中移除已消费的命令;
- 刷新 mcp 工具:在每次轮次之间刷新工具,使新连接的MCP服务器可用;
- 轮次计数:每执行一轮工具调用,轮次计数 +1;
- 主 Agent 生成任务摘要(用于
claude ps); - 检查是否达到最大轮数限制
- 构建下一轮循环状态 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 停止
整个代码看下来,不愧是"宇宙最强",觉得可以借鉴一二:
- 多层上下文压缩:层层压缩,保证多轮对话质量与成本平衡;
- 流式工具执行:调用工具极快,在 LLM 生成的过程中,有些工具已经执行完毕。 体验极快,只是工程复杂度更高;
- 错误处理与恢复:多种错误处理与兜底、重试机制等;
- 异步处理: 工具摘要、记忆预取、技能刷新都异步做,不阻塞主交互流程。
最后也分享个彩蛋。在源代码开头有一段Claudecode开发者的注释,可以看到其精神状态,也是相当幽默,估计也踩过很多坑。取了其中一段翻译一下:
年轻的巫师,请谨记这些规则。它们是thinking的法则,而thinking的法则即是宇宙的法则。倘若你不遵守这些规则,将会遭受整日调试代码、抓耳挠腮的惩罚。
本人水平有限,文章难免有不严谨之处,后续还会继续深挖学习。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/264188.html