Agent Loop:为什么最强 AI Agent 的核心只是一个 While 循环?
Claude Code 的大脑叫 QueryEngine——46,000 行代码,但核心循环只有约 1,700 行。这个 while(true) 循环是如何驱动全球最强编码 Agent 的?本文从循环的每一步拆起,带你理解 Agent Loop 的设计哲学。
Agent Loop:为什么最强 AI Agent 的核心只是一个 While 循环?
系列说明:这是「从 Claude Code 泄漏源码学 Agent 工程实践」系列的第 2 期。第 1 期全景篇我们建立了 Claude Code 的完整认知框架。本期深入 Agent Loop——整个系统的心脏。
上一期我们说了一个结论:Claude Code 用不到 2,000 行代码实现了核心循环,用超过 47 万行代码让这个循环变得可靠。
这就引出一个问题:这个"核心循环"到底是什么?它为什么如此重要?为什么全球最强的编码 Agent,核心竟然是一个 while(true)?
今天我们来拆这个循环的每一步。
先建立一个直觉:Agent Loop 是什么?
如果你用过 Claude Code(或者任何 AI coding agent),你会感觉它在"思考"——它读代码、跑测试、改文件、再跑测试、再改文件……直到任务完成。
这种感觉的背后,就是 Agent Loop。
用最通俗的话讲:Agent Loop 就是一个不断重复的决策循环。
- 看——收集当前的上下文信息
- 想——调用模型,让它决定下一步做什么
- 做——如果模型说"用某个工具",就执行那个工具
- 反馈——把执行结果告诉模型,让它再决定
- 重复——直到模型说"我完成了",或者资源耗尽
这个"看-想-做-反馈-重复"的模式,就是几乎所有 AI agent 的基本骨架。不只是 Claude Code,Cursor 的 Agent Mode、GitHub Copilot 的 Workspace、Devin——它们的核心都是某种形式的 Agent Loop。
差别在于:循环本身有多简单,循环周围的系统有多精密。
Claude Code 选择了一个极端的设计哲学:把循环做到极致简单,把所有复杂性推到循环的外围。 这个选择,值得每个 AI builder 仔细思考。
QueryEngine:Claude Code 的大脑
在 Claude Code 的源码里,Agent Loop 的实现叫 QueryEngine。它分为两层:
上层:Session Layer(会话层)
QueryEngine 类负责跨轮次的状态管理。当你和 Claude Code 进行一场持续的对话时,Session Layer 在做这些事:
- 维护整场对话的累计 token 使用量
- 追踪哪些权限请求被用户拒绝过(下次不再重复请求)
- 提供
submitMessage()接口——这是用户每次输入的入口
你可以把 Session Layer 想象成一个"记忆管理者",它记住对话的全局状态,但不负责具体的思考。
下层:Turn Layer(单轮层)
query 函数是真正的执行引擎,包含约 1,730 行代码的核心 while(true) 循环。每次用户输入一条消息,Turn Layer 就启动一轮循环:
- 预处理用户输入(解析斜杠命令、附件等)
- 组装上下文(系统提示词 + 自定义提示 + 记忆 + 当轮附件)
- 调用模型 API
- 如果模型要求使用工具 → 执行工具 → 把结果送回模型 → 继续循环
- 如果触发了任何续行条件 → 处理后继续循环
- 如果模型说"完成了"或触发终止条件 → 退出循环
两层分离的好处是:Session Layer 只需要关心"对话级别"的状态(token 总量、权限记录),Turn Layer 只需要关心"当前这一轮"怎么把事情做完。各管各的,互不干涉。
flowchart TB
subgraph core["🧠 核心 — Agent Loop ~1,296 行"]
QE["QueryEngine: while 循环<br/>调用模型 → 执行工具 → 重复"]
end
subgraph ctx["📋 第一层 Harness — 上下文管理"]
MC["微压缩<br/>规则驱动 · 零成本"]
SM["会话记忆<br/>提取结构化事实"]
FC["完整压缩<br/>LLM 生成摘要"]
TS["工具系统 42+<br/>Zod 校验 · 按需加载"]
end
subgraph sec["🛡️ 第二层 Harness — 安全与约束"]
PM["四层权限模式"]
BC["Bash 分类器<br/>规则匹配"]
YC["YOLO 分类器<br/>LLM-as-Judge"]
HC["23 条硬编码安全规则"]
HK["Hooks 系统 ~8K 行"]
end
subgraph ext["🤝 第三层 Harness — 多 Agent 与扩展"]
SW["Swarm 架构 ~6.8K 行"]
CO["Coordinator Mode"]
MP["MCP 协议 ~11K 行"]
end
subgraph future["🔮 未发布功能 (44 Feature Flags)"]
KA["KAIROS 永远在线"]
SP["Speculation 投机执行"]
UP["ULTRAPLAN 云端规划"]
BD["Buddy 电子宠物"]
end
core --> ctx
ctx --> sec
sec --> ext
ext -.-> future
while(true) 的每一步:比你想象的精密得多
看起来简单的 while(true) 循环,实际执行起来要处理大量的边界情况。让我们放大来看。
循环的主体流程
每一次迭代,循环携带一个类型化的 State 对象,里面包含:
- 当前的消息历史和工具调用上下文
- 自动压缩的追踪状态
- 输出限制恢复的计数器
- 一个
transition字段——记录"为什么这次循环要继续"
这个 transition 字段是一个精妙的设计。它不只是一个调试用的日志,而是整个循环的决策依据。每次循环继续时,系统必须明确说出"我为什么要再跑一轮",这让循环的行为完全可追踪、可审计。
7 种循环续行原因
Claude Code 的 Agent Loop 有 7 种明确的续行原因,每一种都对应一个特定的场景和处理策略:
1. max_output_tokens_escalate(输出限制升级)
模型第一次命中 8K token 的输出上限时触发。系统不报错,而是把限制升级到 64K,然后重试。这意味着 Claude Code 默认用一个较低的输出限制来节省成本,只有当模型真的需要更多空间时才放开。
2. max_output_tokens_recovery(输出限制恢复)
模型达到了输出上限但任务未完成。系统注入一条元消息——"Resume directly"(直接继续)——然后让模型从断点续写。最多重试 3 次。
3. reactive_compact_retry(被动压缩重试)
API 返回 413 错误(请求体太大),说明对话历史超过了模型的上下文窗口。系统执行一次紧急压缩,然后重试。
4. collapse_drain_retry(上下文坍缩重试)
上下文坍缩的多个阶段被逐步消耗完后触发重试。这是一个渐进式的上下文清理机制。
5. stop_hook_blocking(Hook 拦截)
一个 Stop Hook 返回了阻塞错误。Hook 是可插拔的检查点,可以在循环的关键节点拦截执行。
6. token_budget_continuation(Token 预算续行)
预算系统判断任务尚未完成,注入一个提示消息让模型继续工作。
7. 隐式续行:存在工具调用 最常见的情况——模型的响应中包含工具调用请求。执行工具,送回结果,继续循环。
循环的终止条件
循环通过 Terminal 状态退出,具体有 7 种终止原因:
completed——模型正常完成blocking_limit——达到硬性限制model_error——模型调用出错prompt_too_long——压缩后仍然超限aborted_streaming——流式传输中断stop_hook_prevented——Hook 阻止了继续image_error——图像处理出错
7 种续行 + 7 种终止 = 14 种明确的循环状态转换。没有任何模糊的"可能继续也可能停止"。每一次循环迭代,系统都能精确说出"我为什么在这里"。
这就是"简单循环 + 精密控制"的设计哲学的具体体现。
timeline
title 泄漏事件时间线
2026-03-31 凌晨 : Chaofan Shou 发现 source map
: 59.8MB 调试文件指向完整源码
数小时内 : 代码被镜像到多个 GitHub 仓库
2026-03-31 : Anthropic 确认并发布 DMCA
2026-04-01 : GitHub 误删数千个相关仓库
: Claw Code 开源重写获 50K stars
2026-04-01~03 : 社区分析爆发
: 17 章架构拆解发布
flowchart LR
subgraph engine["⚙️ 引擎 = LLM 模型"]
E["~1,296 行<br/>核心推理循环"]
end
subgraph car["🏎️ 整辆车 = Harness (47 万行)"]
direction TB
A["方向盘: 上下文管理"]
B["刹车: 安全与权限"]
C["导航: 工具系统"]
D["安全气囊: 错误恢复"]
F["变速箱: 多 Agent 协作"]
G["仪表盘: 监控与日志"]
end
engine --> car
每次调用模型之前:五级预处理管道
大部分人在思考 Agent Loop 时,注意力都在"调用模型 → 执行工具"这个主循环上。但 Claude Code 在每次调用模型 API 之前,有一个精密的五级预处理管道在默默工作:
第一级:Tool Result Budgeting(工具结果预算) 设置工具输出的总量上限。如果上一轮的工具执行产生了大量输出(比如读了一个很大的文件),这一步会预算控制总量。
第二级:Snip Compact(片段裁剪) 移除对话中已经过时的片段。这是一个 feature-gated 的功能,通过 feature flag 控制开关。
第三级:Microcompact(微压缩) 这就是我们在第 1 期提到的"不调用模型的压缩"。它感知 Anthropic 服务端的 prompt 缓存状态,选择最优的清理路径。零 API 调用成本。
第四级:Context Collapse(上下文坍缩) 把旧的对话轮次"归档"成投影视图。对话的细节被压缩,但结构性信息被保留。
第五级:Autocompact(自动压缩) 当对话接近上下文窗口的天花板时触发。它的触发阈值很精确:
- 对于 200K token 的模型,压缩在约 187K tokens 时触发(93.5% 利用率)
- 预留 13,000 tokens 的缓冲区
- 生成最多 20,000 tokens 的结构化摘要
- 压缩后,工作预算重置为 50,000 tokens
五级管道从轻到重排列:能用规则解决的不调模型,能局部清理的不全局压缩。只有当前四级都不够用时,才启动最昂贵的第五级。
对 Builder 的启示:如果你在做 agent 产品,不要在每次 API 调用时把所有信息"无脑"塞进 context。建立一个分级的预处理管道——哪些信息必须保留,哪些可以摘要,哪些可以丢弃。这个管道的精细程度,直接决定了你的 agent 在长对话中的表现。
flowchart LR
INPUT["对话历史<br/>无限长"] --> L1
subgraph L1["第一级: 微压缩"]
R1["规则驱动 · 零成本<br/>清理旧工具结果<br/>保护 prompt 缓存"]
end
L1 --> L2
subgraph L2["第二级: 会话记忆"]
R2["提取结构化事实<br/>项目结构 · 用户偏好 · 任务进度<br/>持久化到本地目录"]
end
L2 --> L3
subgraph L3["第三级: 完整压缩"]
R3["独立模型调用<br/>生成摘要边界消息<br/>旧消息移出视野"]
end
L3 --> OUTPUT["精简上下文<br/>~200K tokens"]
flowchart TB
L1["1️⃣ 配置规则<br/>权限模式: 逐一确认 → 半自动 → 大部分自动 → YOLO"]
L2["2️⃣ AST 分析<br/>解析命令结构"]
L3["3️⃣ Bash 分类器<br/>纯规则匹配 · 只读命令自动放行"]
L4["4️⃣ YOLO 分类器<br/>LLM-as-Judge · 两阶段架构"]
L5["5️⃣ OS 沙箱<br/>操作系统级隔离"]
L6["6️⃣ Hooks 拦截<br/>可插拔回调 · ~8K 行"]
L7["7️⃣ 硬编码安全检查<br/>23 条规则 · 300KB+ 安全代码"]
L1 --> L2 --> L3 --> L4 --> L5 --> L6 --> L7
L7 --> SAFE["✅ 安全执行"]
流式传输:不是等模型说完,而是边说边做
大部分 AI 应用的工作模式是:发请求 → 等模型生成完 → 拿到完整响应 → 处理。但 Claude Code 不是这样的。
Claude Code 直接消费 原始 SSE(Server-Sent Events)事件流,而不是使用 SDK 封装好的高级流接口。为什么要这么麻烦?因为这样可以实现逐事件级别的控制。
一次模型调用的事件流长这样:
message_start— 消息开始,包含一个空内容的 Message 对象content_block_start→ 多个content_block_delta→content_block_stop— 内容块的生成过程message_delta— 消息级别的变更(usage 和 stop_reason 只有在这个事件中才是最终值)message_stop— 消息结束
一个关键细节:usage(token 使用量)和 stop_reason(停止原因)在最后的 message_delta 事件中才是确定的——它们在流的过程中会被就地修改。这意味着你不能在收到任何一个中间事件时就做最终决策。
流式工具执行:边生成边做
更有意思的是 Streaming Tool Execution。当这个 feature gate 开启时:
StreamingToolExecutor在模型还在生成响应的同时就开始执行工具- 工具调用被分成批次(batch),每批最多 10 个工具并行执行
- 并行执行的工具之间上下文隔离,串行执行的工具立即传播上下文修改
这意味着 Claude Code 不是"想完了再做",而是边想边做。模型在生成后半段回答时,前半段提到的工具调用可能已经执行完毕了。
Tombstone Messages:优雅的失败处理
如果流式传输在中途失败怎么办?Claude Code 不会丢掉已经接收到的部分,而是把它标记为 tombstone 消息——一种"墓碑"标记。UI 层看到墓碑消息就知道"这条消息不完整,在重试前先移除它"。
这个设计确保了即使在网络不稳定的情况下,用户看到的对话历史依然是干净的,不会出现半截话或重复内容。
对 Builder 的启示:流式传输不只是"让用户看到打字效果"的UI优化。在 agent 场景下,流式传输是并行执行的基础设施。如果你能在模型生成响应的同时就开始执行工具,每个循环迭代都能节省几秒到十几秒的延迟。对于需要多轮循环的复杂任务,累计起来就是分钟级的体验差异。
错误恢复:从不轻易放弃
一个在生产环境中运行的 agent,必须能优雅地处理各种错误。Claude Code 的错误恢复系统是它 Harness 工程的一个典范。
三阶段输出限制恢复
当模型的输出命中了 token 限制:
- 第 1 次:默认 8K 限制被触发 → 升级到 64K → 注入"Resume directly"消息 → 重试
- 第 2 次:64K 限制被触发 → 再次注入恢复消息 → 重试
- 第 3 次:仍然超限 → 向用户报告错误
为什么默认限制是 8K 而不是一上来就给 64K?因为大部分回答不需要 64K。用低限制开始可以节省成本,只有真正需要时才放开。这是一种乐观策略——先假设简单情况,再逐步升级。
智能退避重试:withRetry()
网络错误、API 过载、认证失效……Claude Code 有一套完整的重试机制:
- 指数退避:每次重试的等待时间按
2^(attempt-1) * baseDelay + jitter计算 - 最多 10 次重试(可配置)
- 尊重 Retry-After 头:如果 API 返回了"多久后再试"的提示,按提示来
- 529 特殊处理:API 过载时用更长的初始等待
- 检测 stale socket:ECONNRESET / EPIPE 错误触发连接重建
- 401 触发 OAuth 刷新:认证过期时自动续期
- 400 解析 token 溢出:从错误响应中提取 token 数量信息,用于下一次压缩
回退模型机制
一个值得关注的发现:如果 Opus 模型连续出现 3 次 529(过载)错误,Claude Code 会静默切换到 Sonnet 模型作为回退。系统在能力和可用性之间做了一个权衡——宁可用一个稍弱的模型,也不让用户等待或报错。
Transcript-First:先持久化,再调 API
Claude Code 在调用模型 API 之前,就先把用户消息持久化到本地磁盘。这意味着即使进程在 API 调用过程中崩溃,对话历史也不会丢失。下次启动时可以从断点恢复。
这个模式叫 Transcript-First——先写日记,再做事。简单但关键。
对 Builder 的启示:错误恢复不是"出了错就报错"那么简单。一个生产级的 agent 需要分层的恢复策略:先自动重试 → 升级参数 → 切换模型 → 最后才向用户报告。每一步都要有明确的上限,防止无限循环。Claude Code 用 watermark(水位标记)机制来限制每种错误模式的恢复次数——这个设计思路可以直接借用。
flowchart TB
LEADER["🎯 Leader Agent<br/>任务拆解 · 分配 · 收集"]
LEADER -->|"指令"| T1["Teammate 1"]
LEADER -->|"指令"| T2["Teammate 2"]
LEADER -->|"指令"| T3["Teammate 3"]
T1 -.->|"上下文隔离"| T2
T2 -.->|"上下文隔离"| T3
T1 -->|"关键发现"| MEM["📝 Team Memory Sync<br/>重要信息跨 Agent 流动"]
T2 -->|"关键发现"| MEM
T3 -->|"关键发现"| MEM
MEM -->|"同步"| LEADER
COORD["🔄 Coordinator Mode<br/>纯编排: 自己不干活<br/>只拆任务 · 派任务 · 收结果"]
COORD -.->|"模式切换"| LEADER
Token 预算管理:知道什么时候该停
一个 agent 可以无限循环,但 token 不是无限的。Claude Code 的预算管理系统解决了一个微妙的问题:怎么判断模型是"还在高效工作"还是"在空转浪费 token"?
自动续行逻辑
- COMPLETION_THRESHOLD(完成阈值):当 90% 的预算已经被消耗,系统认为工作"基本完成"
- DIMINISHING_THRESHOLD(递减阈值):如果一轮循环只产生了不到 500 个 token 的输出,系统认为"没有实质进展"
- 组合判断:连续 3 次以上续行 + 每次产出低于 500 token = 停止,而不是继续烧钱
这个机制防止了一种常见的 agent 陷阱——空转(thrashing)。模型不断循环,每次只做一点微小的修改,消耗大量 token 但实际上没有推进任务。通过跟踪每轮的实际产出量,系统可以判断"该停了"。
预算跨压缩边界的延续
一个容易被忽略的细节:当对话被压缩时,token 预算不会重置。系统通过 task_budget.remaining 字段,把压缩前已经消耗的预算带到压缩后。这确保了压缩不会成为一种"无限续命"的手段。
对 Builder 的启示:预算管理是 agent 产品的"刹车系统"。没有它,agent 会一直跑到用户的钱花完。设计预算系统时,不要只看总量(花了多少 token),也要看效率(每轮产出了多少有价值的内容)。Claude Code 的 90%/500 token 双阈值设计可以直接参考。
循环结束之后:后处理生命周期
模型说"我完成了"之后,故事并没有结束。Claude Code 还有一套后处理流程:
Stop Hooks(停止钩子)
三类 Hook 会在循环正常结束后触发:
-
Standard Stop Hooks——在 settings.json 中配置,并行运行。它们可以产生阻塞错误(阻止任务被标记为完成),也可以返回需要后续处理的信息。
-
TaskCompleted Hooks——在多 Agent 模式下,当一个子任务完成时触发。
-
TeammateIdle Hooks——当一个 Teammate Agent 转入空闲状态时触发。
后台任务(Fire-and-Forget)
更有意思的是,循环结束后还有三个"静默"的后台任务:
executePromptSuggestion()——生成"顺便说一句..."类型的建议。你有时候会看到 Claude Code 在完成任务后主动提一个相关建议,就是这个在工作。executeExtractMemories()——从刚才的对话中提取结构化事实,写入 MEMORY.md。这就是上一期提到的"会话记忆压缩"。executeAutoDream()——自主的后台探索,和 KAIROS 功能相关。
这些后台任务在使用 -p flag(bare mode,精简模式)时会被跳过。这意味着 Claude Code 其实在每次"正式对话"结束后,都在悄悄地学习和积累——只是你看不到而已。
对比视角:Claude Code 的 Agent Loop vs 其他方案
理解了 Claude Code 的 Agent Loop,再来看它和其他 AI coding tool 的对比,会更清晰。
Claude Code vs Cursor
| 维度 | Claude Code | Cursor |
|---|---|---|
| 运行环境 | 终端原生,直接操作文件系统和 Shell | IDE 原生,在编辑器内运行 |
| Agent Loop | 完整的 while(true) + 无约束工具调用 | 有 Agent Mode,但受 IDE 插件架构约束 |
| 上下文窗口 | 最大 1M tokens | 最大 200K tokens(可降低以加速) |
| 工具执行 | 直接调用 bash、读写文件、操作 git | 通过 IDE API 间接操作 |
| 多文件操作 | 终端级别的并行,速度快约 18% | 受 IDE 事件循环约束 |
核心差异:Claude Code 是"系统级" agent——它拥有和你一样的操作系统访问权限。Cursor 是"编辑器级" agent——它的能力被 IDE 的架构框住了。这不是说 Cursor 更差(它的 IDE 集成体验更流畅),而是说两者的 Agent Loop 在"能调用什么工具"这个维度上有本质区别。
Claude Code vs GitHub Copilot
Copilot 在传统上是一个"反应式"(reactive)的系统——你写代码,它补全。虽然 Copilot 最近推出了 Workspace 模式(跨文件任务),但它的核心仍然更偏向"智能补全"而非"自主执行"。
Claude Code 的 Agent Loop 是一个完全自主的执行循环——你给它一个目标,它会自己决定读哪些文件、跑什么命令、改哪些代码、怎么验证。这两种范式之间的差距,不是功能级别的,而是架构级别的。
底层共识:简单循环 + 精密外围
尽管实现方式不同,所有成功的 AI coding agent 都在趋向同一个架构模式:
- 核心是一个循环(while loop / event loop)
- 循环每次迭代做三件事:组装上下文 → 调用模型 → 执行动作
- 真正的工程投入在循环的"外围":上下文管理、权限控制、错误恢复、预算管理
Claude Code 只是把这个模式做到了最极致——核心循环极简,外围系统极精密。
从 Claude Code 的 Agent Loop 中学到的 5 条设计原则
原则一:循环要简单,状态转换要显式
不要试图在一个复杂的状态图中管理 agent 的行为。Claude Code 证明了 while(true) + 显式的 transition 原因就够了。关键是每次循环都要能回答"我为什么还在跑"。
原则二:预处理比后处理更重要
Claude Code 在调用模型之前有五级预处理管道,但在模型返回之后的处理相对简单。这说明 "模型看到什么"比"模型说了什么"更重要。把工程投入放在 context assembly 上,回报比 output parsing 更高。
原则三:错误恢复要分层,每层有上限
先自动重试 → 升级参数 → 切换模型 → 最后报错。每一层都有明确的次数上限(watermark),防止无限循环。这个分层模式可以直接套用。
原则四:先持久化,再执行
Transcript-First 模式:用户输入先写盘,再调 API。这保证了崩溃恢复的可能性。对于任何有状态的 agent,这是最基本的可靠性保障。
原则五:知道什么时候停
一个 agent 最危险的状态不是"出错了",而是"在空转"。设计双阈值的停止机制——总量阈值(预算消耗了多少)+ 效率阈值(每轮产出了多少),两个条件同时满足才停。
下一期预告
这一期我们拆完了 Agent Loop——从 while(true) 的每一步到五级预处理管道,从 7 种续行原因到错误恢复机制。
但我们只触碰了工具执行的表面。模型说"我要用 Bash 工具执行这条命令",然后呢?谁来决定这条命令能不能执行?42+ 工具是怎么注册和调度的?双层 LLM-as-Judge 的权限分类器到底怎么工作?
第 3 期:Tool System 与安全——让 AI 安全地动手,我们下期见。
觉得有用?订阅 AI 简报,每天 5 分钟掌握 AI 动态。