它是每个 AI Agent 的核心引擎 —— 让无状态的 LLM 能持续行动、获取反馈、完成任务。
本文从定义到实现、从工程挑战到两家解法,彻底讲透 Agent Loop。
在开始对比之前,先把最基本的概念彻底钉死
LLM 本身只做一件事 —— 接收一段文本,输出一段文本。它没有记忆、没有行动能力、没有持久化。每次调用完就"死"了。下一次调用和上一次没有任何关系。
这意味着 LLM 天然不是 Agent。它是一个极其聪明的顾问,但每次叫它来开会,都要把所有背景从头讲一遍。
| 能力 | LLM 本身 | Agent Loop 赋予 |
|---|---|---|
| 持久性 Persistence | 每次调用互相独立 | 跨多次调用维持状态(对话历史) |
| 行动力 Agency | 只能输出文本 | 可以调用工具、执行命令、修改文件 |
| 接地性 Grounding | 可能产生幻觉 | 工具返回真实世界反馈,不可伪造 |
把 Claude Code 和 OpenAI Codex 的核心循环放在一起,你会发现一个惊人的事实
while (true) {
// 1. 调用 Claude API(流式)
for await (const msg of
deps.callModel({
messages,
systemPrompt,
tools,
toolChoice: undefined,
})
) { /* 收集响应 */ }
// 2. 模型没有调用工具?结束
if (!needsFollowUp) break
// 3. 执行工具,追加结果
yield* runTools(toolUseBlocks)
// → 回到步骤 1
}
while (true) {
// 1. 调用 Responses API(SSE)
response = POST(
"/v1/responses",
{
instructions,
tools,
input: conversationItems,
}
)
// 2. 收到 assistant message?结束
if (response.done) break
// 3. 执行工具,追加结果
result = execute(response.toolCall)
conversationItems.push(result)
// → 回到步骤 1
}
一个只有 happy path 的 while(true) 在生产环境中会失控。两家都在循环中内置了保护机制:
| 退出条件 | Claude Code | Codex |
|---|---|---|
| 正常结束 | 模型返回文本、不再调用工具 | 模型返回 assistant message(done event) |
| 达到上限 | max turns 限制(可配置) | token 超过阈值时自动 compact |
| 用户中断 | 用户随时可以打断循环并修正方向 | 用户可取消当前 turn |
工具执行出错时,错误信息会作为工具返回值追加到对话历史中,模型在下一次推理时能看到错误内容并自行决定如何处理 —— 重试、换一种方式、或报告给用户。这也是"接地性"的体现:真实的错误反馈让模型不会在幻觉中打转。
模型可能陷入反复执行同一操作的死循环。应对手段包括:
知道循环的代码结构还不够 —— 你需要亲眼看到数据是怎么在循环中流动的
用户输入 "修复登录 bug",看看第一次 iteration 中发生了什么:
[
{ "role": "system", "content": "You are Claude Code, an agentic assistant...
To read files use Read instead of cat...
Don't add features beyond what was asked..." // ← ~700 行系统指令
},
{ "role": "user", "content": "修复登录 bug" }
]
// + tools: [Grep, Read, Edit, Bash, Glob, ...共 40 个工具定义]
// 每个工具定义包含名称、描述、参数 schema —— 这是模型的"菜单"
{
"role": "assistant",
"content": [
{ "type": "text", "text": "让我先搜索登录相关的代码" },
{
"type": "tool_use",
"name": "Grep",
"input": { "pattern": "login|auth", "path": "src/" }
}
]
}
{
"role": "tool",
"name": "Grep",
"content": "src/auth/login.ts:23: async function handleLogin(credentials) {
src/auth/login.ts:45: if (!token) throw new AuthError('Token expired')
src/auth/login.ts:52: // BUG: should refresh token before checking
src/api/session.ts:11: import { handleLogin } from '../auth/login'"
}
login.ts:52,有一条注释说"should refresh token before checking"。这个真实反馈让模型能做出正确的下一步判断 —— 而不是幻觉"我已经找到了问题"。
[
{ "role": "system", ... }, // 系统指令(不变)
{ "role": "user", "content": "修复登录 bug" }, // 用户输入(不变)
{ "role": "assistant", ... "tool_use": "Grep" }, // ← 新增:第 1 次的模型响应
{ "role": "tool", "content": "src/auth/..." }, // ← 新增:第 1 次的工具结果
]
// 模型看到完整历史后,决定下一步:Read src/auth/login.ts
继续这个例子。5 次 iteration 后,messages 数组变成了这样:
理解了循环怎么跑,接下来看它跑久了会遇到什么问题
每次 iteration,都要向 API 发送完整的对话历史。随着工具调用次数增加,prompt 持续膨胀:
如果不优化,Agent Loop 在实际使用中会迅速变得昂贵且不可持续 —— 几十次工具调用后就可能接近 context window 上限。
Codex 和 Claude Code 分别用了不同的工程手段来应对这个问题。接下来我们逐一拆解。
同一个问题,两种不同的工程选择
当服务端检测到请求的前缀与上次推理完全一致(exact prefix match),就可以复用上次的 KV Cache,只计算新增部分。
// 权限变更?追加新 developer message,不修改旧的
conversationItems.push({
role: "developer",
content: "User approved: allow shell commands"
})
// 目录切换?追加新 user message,不修改旧的
conversationItems.push({
role: "user",
content: "Working directory changed to /src/api"
})
// MCP 工具列表?枚举顺序必须稳定
// 因为 tools 定义在 prefix 中,顺序变了 = prefix 变了 = 缓存失效
previous_response_id?Responses API 支持有状态模式 —— 传一个 previous_response_id,服务端就能记住上次的上下文,不用重传历史。但 Codex 刻意不用,原因是 Zero Data Retention (ZDR) 合规:企业客户要求 OpenAI 不在服务端保留任何对话数据。
Claude Code 内置了一套由客户端完全控制的多层压缩策略,当 context 接近窗口上限时逐级启用。以下是基于官方文档描述和运行行为的推断(Claude Code 未完全开源压缩模块的具体实现):
单个工具返回内容太多(如 ls 了一个大目录)→ 截断到合理长度
最早的工具执行结果优先被删除 —— 它们对当前任务的参考价值最低
调用模型对历史对话做总结,用摘要替换原文 —— 信息密度提升,token 数下降
用户的原始意图 + 核心代码片段 = 最不可压缩的内容
CLAUDE.md、auto-memory、系统指令 —— 这些定义了 Agent 的行为准则,绝不被压缩
近期上下文对当前任务至关重要
如果以上仍不够 —— 激进裁剪,确保循环不会因为 context 溢出而崩溃
| 网络传输成本 | 计算(推理)成本 | |
|---|---|---|
| 无优化 | O(n²) — 每次发全量 | O(n²) — 每次全量计算 |
| Prefix Caching | O(n²) — 仍然发全量 | O(n) — 缓存命中,只计算新增 |
| 内容压缩 | O(n) — 压缩后发送量受控 | O(n) — 发送量小,计算量也小 |
需要注意:这两种策略不是互斥的。Anthropic 的 API 同样支持 prompt caching,Claude Code 可以同时享受缓存和压缩的双重优化。两家的差异在于工程重心不同。
| 维度 | OpenAI Codex | Claude Code |
|---|---|---|
| 工程重心 | 计算效率 — 不重复计算已有内容 | 内容质量 — 有限窗口塞更多有用信息 |
| Compaction 控制权 | 服务端持有 — 返回加密内容 | 客户端持有 — Harness 完全控制压缩 |
| Prompt 构造约束 | 前缀不可变(为缓存命中服务) | 更灵活,可重组(API 也支持缓存) |
| 核心策略 | "发全量 + 缓存避免重算" | "压缩内容 + 缓存也能用" |
| Trade-off | 网络带宽 → 换 → 计算效率 | 压缩信息损失 → 换 → 窗口利用率 |
Agent Loop 不是万能的 —— 知道什么时候不该用,才算真正理解它
理解什么时候不该用 Agent Loop,才算真正理解了它:
| 代价 | 原因 |
|---|---|
| 成本高 | 多轮推理 = 多次 API 调用 = 前文所述的 O(n²) token 消耗 |
| 错误累积 | 每一步的小偏差在后续步骤被放大 —— 第 3 步基于第 2 步的错误结论继续推理 |
| 不可预测 | 你无法预知 Agent 会做几步、走什么路径,调试困难 |
Anthropic 在 Building Effective Agents 中指出,在使用 Agent Loop 之前,应该先考虑更简单的 Workflow 模式:
读完本文,你应该能回答这些问题
1. Agent Loop 是什么?
一个执行循环,将无状态的 LLM 变成有状态的、能行动的、能从环境获取反馈的系统。核心逻辑:推理 → 判断是否结束 → 执行工具 → 追加结果 → 继续推理。
2. 循环里流动的是什么?
messages 数组。每次 iteration 追加两项:模型的 tool_use 请求 + 工具执行的真实结果。模型通过阅读工具描述("菜单")决定调用哪个工具,通过工具返回的真实结果("接地")纠正自己的判断。这个"决策→行动→反馈→纠正"的循环才是 Agent Loop 的灵魂。
3. 它面临什么核心挑战?
messages 数组持续膨胀,总 token 消耗以 O(n²) 增长。5 次 iteration 发送量就翻倍,100 次就是百万级 token。
4. 两家怎么解决?
Codex:Prefix Caching — prompt 结构刻意排列(静态前、动态后),靠精确前缀匹配复用计算。代价是 prompt 结构受约束(追加而非修改)。
Claude Code:渐进压缩 — 客户端按"过程数据→意图数据→身份数据"的优先级逐层压缩历史。代价是压缩有信息损失。
两种策略不互斥,差异在于工程重心。
5. 什么时候该用 Agent Loop?
只有任务是 open-ended、步骤数无法预判时才用。它有三个固有代价:成本高(O(n²))、错误累积、不可预测。能用更简单的 workflow 解决的,不要用 Agent Loop。