1. 前言
随着2022年底以ChatGPT为代表的LLM(大语言模型)爆发式发展,AI Agent(智能体)迅速从概念走向工程实践。从早期简单的单轮对话工具,到如今能够自主规划、调用工具、持续迭代的编程Agent(如Cursor、Claude Code、Codex CLI),Agent已经成为大模型应用落地的核心形态。正如OpenAI在其官方博客中所说,Agent Loop是连接用户、模型和工具的枢纽。
作为Agent系统的核心机制,Agent Loop(智能体循环)决定了Agent如何思考、如何行动、如何与外部环境交互。这个循环的本质是:Agent接收用户输入,查询模型进行推理,模型要么产生最终回复,要么请求工具调用;Agent执行工具后将结果回灌到prompt中,再次查询模型——如此反复,直到模型停止发出工具调用、产出面向用户的assistant message为止。这个assistant message不仅是对用户的回应,更标志着Agent Loop的一个终止状态:从Agent的视角看,它的工作已经完成,控制权交还用户。
它看似简单——不过是一个"调模型、执行工具、再调模型"的循环——但要在真实产品中稳定、高效、可扩展地运行,却涉及大量精妙的工程设计和架构取舍。比如,随着对话增长,prompt长度会不断增加,而每个模型都有上下文窗口(context window)限制,因此上下文窗口管理是Agent的核心职责之一。此外,Agent执行的工具调用会修改本地环境(如读写文件),所以Agent的"输出"远不止assistant message,还包括它实际产生的代码变更等副作用。
本文是Agent实现原理系列的第一篇,将从工程实现的角度,深入剖析Agent Loop的核心原理。我们将参考Codex CLI、Claude Code、nanobot、pi等主流开源项目的实现,抽象出Agent Loop的通用模型,分析其关键设计点和工程权衡。
2. Agent Loop的本质
2.1 从"会调LLM的程序"到"Agent Runtime"
一个最朴素的Agent实现,可以写成下面这样:
while true:
response = call_llm(messages, tools)
if response has tool_calls:
results = execute_tools(response.tool_calls)
messages += [response, results]
else:
return response.content
这段代码确实构成了Agent Loop的核心骨架,但它距离一个"能在真实产品中运行的Agent Runtime"还有很大差距。真实场景需要回答的问题包括:
- 工具执行出错或产生副作用时,如何优雅处理?
- 用户中途插入新指令,如何不破坏当前执行上下文?
- 上下文窗口超限,如何压缩而不丢失关键信息?
- 多轮迭代后历史越来越长,如何保持"上下文卫生"?
- 流式输出如何与工具调用无缝衔接?
- 子Agent如何复用主循环的能力?
这些问题促使Agent Loop从一个简单的while循环,演进为一组边界清晰的组件协作系统。
2.2 Agent Loop的通用抽象
无论具体实现如何差异,所有成熟的Agent Loop都共享同一套核心抽象:
flowchart TD
A[用户输入] --> B[上下文准备]
B --> C[LLM调用]
C --> D{是否包含工具调用?}
D -- 是 --> E[工具执行]
E --> F[结果回灌]
F --> B
D -- 否 --> G[输出结果]
G --> H{是否继续?}
H -- 是 --> B
H -- 否 --> I[结束]
这个循环的本质是:让LLM在一个持续更新的上下文中,反复进行"思考-行动-观察"的迭代,直到达成目标或满足停止条件。
类比来看,这很像一个经验丰富的工程师解决问题的方式:先理解问题(思考),然后查资料、写代码、运行测试(行动),观察结果(观察),再根据结果调整方案(再思考)。Agent Loop就是把这种模式自动化、结构化、工程化。
3. 主流项目的Agent Loop设计
3.1 Codex CLI1:平台型Runtime
Codex CLI是OpenAI发布的官方编程Agent工具,其Agent Loop设计最像完整的平台级Runtime。它的核心特点是分层调度:外层负责会话级任务调度,内层负责单轮Turn执行,最内层处理模型采样。
Codex的Loop可以概括为:
Submission
-> submission_loop() // 会话级调度器
-> user_input_or_turn() // 输入路由器
-> new_turn_with_sub_id() // 生成Turn快照
-> spawn_task() // 任务化执行
-> run_turn() // 单轮主循环
-> run_sampling_request() // 采样控制器
Codex最突出的设计是TurnContext——它不是全局Session配置,而是"本轮执行的快照"。Session是长生命周期状态,Turn Context是短生命周期、冻结的执行视图。这样每一轮都能做到可重放、可追踪、可隔离,避免执行过程中被外部配置变化污染。
此外,Codex把工具可见性做成按Turn动态计算的:每轮根据当前启用的app connectors、MCP servers、skills等因素,动态决定模型能看到哪些工具。这直接解决了"prompt过大"和"模型在无关工具上胡乱调用"两个实际问题。这个思想非常重要,Turn成为Agent Loop的核心原子单元,和上层的Thread或者Session隔离开。
从OpenAI官方博客2文章来看,Codex CLI使用Responses API驱动Agent Loop,其推理过程基于SSE(Server-Sent Events)流式返回。一个典型的推理事件流如下:
data: {"type":"response.reasoning_summary_text.delta","delta":"ah ", ...}
data: {"type":"response.reasoning_summary_text.delta","delta":"ha!", ...}
data: {"type":"response.reasoning_summary_text.done", "item_id":...}
data: {"type":"response.output_item.added", "item":{...}}
data: {"type":"response.output_text.delta", "delta":"forty-", ...}
data: {"type":"response.output_text.delta", "delta":"two!", ...}
data: {"type":"response.completed","response":{...}}
这种基于结构化事件流的响应协议,让Codex能够在流式输出的同时,精确识别工具调用边界、文本增量和推理过程,而不依赖脆弱的文本约定。这也是Codex Loop设计里最清晰的一条边界线:
run_sampling_request()负责"一次采样请求应该怎么发、失败了怎么重试"try_run_sampling_request()负责"一旦请求发出,如何逐事件消费模型流,并把它变成历史、事件和工具执行"。
3.2 Claude Code3:统一消息流与Task托管
Claude Code(即Anthropic官方CLI工具)的Agent Loop设计,最强调统一消息流和可恢复执行。通过分析它泄露出来的源码,可以概括它的核心为:
以
query()为内核、以ToolUseContext为能力边界、以统一消息流为协议、以Task为生命周期外壳。
Claude Code的Loop伪码如下:
while true:
messages = prepareMessages(state.messages)
messages = applyBudgetsAndCompaction(messages)
stream = callModel(messages, tools)
for event in stream:
yield event // 流式输出
collect toolUseBlocks
if no toolUseBlocks:
run stopHooks
if stopHooks block:
append blocking feedback, continue
return completed
toolUpdates = runTools(toolUseBlocks)
for update in toolUpdates:
yield tool_result / progress / attachment
merge context changes
state.messages += assistantMessages + toolResults
state.turnCount += 1
Claude Code有几个非常鲜明的设计特点:
第一,统一消息流承载一切。 Loop的输入输出都被统一成消息流:assistant、user、attachment、progress、system、tombstone、tool_use_summary等。同一条执行链既能喂给模型,也能展示给UI,还能写transcript,还能恢复。这让UI渲染、持久化、恢复、子Agent协议天然对齐。
第二,ToolUseContext作为能力容器。 它不是全局单例,而是每个Agent/Task/Session独立持有的运行时能力集合,包含可用工具、abort controller、AppState访问、权限模式、UI回调等。这让不同Agent在共享代码的同时保持隔离,子Agent可以通过createSubagentContext()做非常细的共享/隔离选择。
第三,恢复分支是显式状态转移。 当遇到上下文超限、max_output_tokens、stop hook blocking等情况时,Claude Code不是简单retry,而是构建下一个State,标注transition reason,然后continue。这让行为可测试、可追踪,也便于避免恢复死循环。
第四,工具执行允许并发,但上下文提交保序。 纯读操作(搜索、读文件)可以并发跑减少时延,但contextModifier仍按原始tool_use顺序串行提交。这是一种典型的"并发side-effect production,顺序state commit"设计。
但也可以看出,Claude Code的循环粒度非常粗,实现更像是在一个原型项目中不断堆叠的产物,并不像是一个严密的设计后再开始实现的产品。这和前面的Codex形成鲜明对比。
3.3 nanobot4:最小执行内核与长期运行稳定性
nanobot是一个开源的AI编程助手框架,其Agent Loop设计最克制、干净,像一套消息驱动的Agent框架。它的核心分层是:
- 编排层:
AgentLoop接消息、查会话、构造上下文、保存状态、调度后台任务 - 执行层:
AgentRunner只处理messages + tools -> response,是最小内核 - 上下文与状态层:
ContextBuilder、SessionManager、MemoryConsolidator负责prompt、合法历史、长期记忆 - 能力层:
ToolRegistry统一工具注册和调用 - 后台机制:
SubagentManager、HeartbeatService等异步能力
nanobot的主链路非常简洁:
InboundMessage
-> MessageBus
-> AgentLoop
-> session lock + concurrency gate
-> process_message()
-> ContextBuilder
-> AgentRunner.run()
-> persist turn
-> background consolidation
nanobot的上下文设计比较清晰,它把输入材料分成三类:
- 稳定指令:identity、workspace信息、bootstrap files、长期memory、skills摘要
- 运行时元数据:当前时间、channel/chat_id
- 当前消息:文本、图片等多模态输入
并且,运行时元数据只注入当前user message,而不是额外插入一条消息。这样做能避免一些provider对连续同role message的限制,同时也便于保存session时剥离这些瞬时信息。
此外,nanobot的Session历史不是简单的消息列表,它同时承担两个约束:
- 历史要append-only,方便持久化和回放
- 送给模型的窗口必须合法:尽量从user turn开始,不从孤立的tool result开始,tool result必须有匹配的assistant tool_call声明
总体来说,nanobot的实现比较简单,更像是一个实验室产品,充斥着一股学术上的严格性和规范性,但从工程角度而言,缺乏了很多细节设计,尤其是居然还放了一把session lock,性能天然具有瓶颈。
3.4 pi5:交互式状态机与精细工具生命周期
随着OpenClaw的大火,其底层agent框架pi也受到了广泛关注。最新新闻是该项目已经被Earendil 公司收购了。pi的Agent Loop设计最适合交互式产品,它把系统拆成三层:
- 循环内核:
runLoop()管理turn边界、tool batch、steering/follow-up注入 - 状态包装层:
Agent把事件流折叠成稳定AgentState - 产品运行时层:
AgentSession把loop接入session、extension、retry、compaction
pi对Turn的定义最明确:一次assistant响应,加上这一轮产生的整批tool call和tool result。这个定义直接决定了turn_start/turn_end的语义、插队消息何时能进入上下文、UI该把什么视为"一轮完成"。
pi的另一个亮点是工具三阶段执行:
- prepare:找工具、校验参数、执行前置hook
- execute:真正跑工具并接收流式更新
- finalize:执行后置hook,产出最终的toolResultMessage
并且支持"顺序preflight + 并发执行 + 顺序提交",这在性能和可观察性之间取得了很好的平衡。
pi还创新性地把运行中插入的新消息分成两类:
steering:当前turn完成后、下一次LLM调用前插入follow-up:只有agent原本要停时才插入
这说明作者并不追求"任意时刻强插新指令",而是强调"在确定的边界上改变后续轨迹"。这牺牲了一点即时性,但换来了上下文的完整性和可恢复性。
整体而言,pi的设计非常清晰明确,工程上非常成熟,相信这也是为什么OpenClaw选择pi作为其底层框架的原因,这个设计非常值得其他框架学习。
4. Agent Loop的关键设计维度
通过对比四个项目,我们可以提炼出Agent Loop的六个关键设计维度。
4.1 Turn建模
四个项目对"一轮"的定义各不相同:
| 项目 | Turn定义 | 特点 |
|---|---|---|
| Codex | Session派生的执行快照 | 长生命周期session vs 短生命周期turn,隔离性强 |
| Claude | Query iteration,可携带恢复转移理由 | 状态机化,轮次存在但更重要的是为什么继续 |
| nanobot | 一条消息处理闭环 | 轻量,但turn语义相对弱 |
| pi | Assistant回复 + 整批tool call/result | 最符合tool-using agent的真实运行语义 |
pi的定义最值得借鉴:把turn边界定为"assistant + tool batch",比"每次模型请求就是一轮"更符合实际。因为用户和UI关心的是"Agent做完了一件事",而不是"模型被调了一次"。
4.2 并发与插队输入
| 项目 | 并发模型 | 插队机制 |
|---|---|---|
| Codex | 同session单主turn | steer到活跃turn,新turn替换旧task |
| Claude | 工具可并发,状态提交保序 | abort controller + task层托管 |
| nanobot | per-session lock + global gate | 命令在编排层前面处理,不交给模型 |
| pi | 单agent单活动loop,其余排队 | steering/follow-up在turn边界注入 |
这里的关键权衡是:即时性 vs 完整性。Codex和Claude偏向更强的交互控制能力,nanobot和pi偏向更清晰的边界和可恢复性。
4.3 工具执行模型
| 项目 | 工具执行特点 |
|---|---|
| Codex | 工具可见性按turn动态计算,融入平台控制面 |
| Claude | 消息更新 + 上下文变更流水线,并发执行+顺序提交 |
| nanobot | 标准LLM->tool calls->execute->results->LLM闭环 |
| pi | 三阶段生命周期(prepare/execute/finalize),事件驱动 |
Claude的"工具调用单位是invocation pipeline而不是简单函数调用"这一思想非常重要。真实场景中,工具调用涉及权限检查、progress事件、错误格式化、context mutation等,远非tool(input)->output能概括。新项目实现的时候可以参考,但要避免过度设计。
4.4 上下文与历史管理
| 项目 | 核心策略 |
|---|---|
| Codex | Per-turn快照,动态注入skills/plugins/MCP |
| Claude | 统一消息流,ToolUseContext能力容器 |
| nanobot | 合法历史约束,append-only session,memory consolidation |
| pi | 内部消息模型与provider消息解耦,partial assistant实时写回 |
每个项目在这方面能力都差不多,大同小异。
4.5 压缩、恢复与后台机制
| 项目 | 策略 |
|---|---|
| Codex | Compaction进turn loop,pre-sampling + auto compact |
| Claude | 多级策略叠加(snip/microcompact/collapse/autocompact),显式恢复状态转移 |
| nanobot | 请求前token预算判断,请求后异步consolidation,子agent复用主内核 |
| pi | Retry/compaction放在session runtime,loop内核保持纯粹 |
Claude的多级压缩策略最完整:先减细碎噪声,再收缩局部结果,再做结构化压缩,最后做失败后恢复。但从另外一个角度来说,我强烈怀疑这是Claude一直不断打补丁之后的结果,缺乏一个顶层设计。
4.6 子Agent与递归复用
四个项目都支持子Agent,但实现方式不同:
- Codex:多session树状编排,每个agent都是完整独立的运行时
- Claude:子Agent复用同一个
query()内核,通过createSubagentContext()做共享/隔离选择 - nanobot:子Agent复用主执行内核但缩小工具集,结果必须回到主loop
- pi:通过extension hook和session runtime接入
多Agent设计是未来Agent程序的高级特性,也是能决定能否实现集群作业的关键能力。各个项目实现不同,但Claude Code同一个loop同时服务主会话和子Agent,采用了单循环、多上下文的架构设计,每个 Agent 拥有独立的上下文,但共享一个事件循环。这样的设计也的确是个迷,越来越像是一个原型项目不断打补丁了。
5. 设计共识
尽管风格各异,但四个项目共享一组非常明确的工程共识:
- Agent Loop不是一个裸
while(true),而是一组分层边界清晰的组件协作。 - Turn和Session/Thread的分层,是Agent Loop设计的关键。
- Turn的定义,简单来说就是Agent做完了一件事,包括多轮对话、思考,以及对工具和mcp等调用。对Turn定义的清晰,决定了Agent Loop设计的质量
- 同一会话或同一agent内默认不鼓励多条主执行链并发推进。
- 历史、工具结果和上下文边界必须可恢复、可回放,不能留下半残状态。
- 扩展能力应该通过hooks、上下文容器、task runtime等明确边界接入,而不是直接侵入执行核心。
这些共识本质上都在解决同一个问题——如何把"LLM的一次调用"升级成"可长期运行的Agent Runtime"。
6. 总结
Agent Loop是Agent系统的核心机制,它决定了Agent如何思考、行动和与外部世界交互。从朴素的while(true)到成熟的Runtime,Agent Loop的演进体现了软件工程的核心思想:通过清晰的边界和分层,管理复杂度,保证可靠性。
四个主流项目虽然风格各异,:
- Codex设计重心的是:平台型Agent Runtime如何调度和推进Turn
- Claude不断打的补丁是:可恢复、可流式、可前后台托管的Query Runtime如何工作
- nanobot从学术上定义:消息驱动Agent系统如何用最小内核长期稳定运行
- pi从实践的角度实现:交互式Coding Agent Runtime如何保证消息边界、工具和状态收敛
Agent Loop的设计没有银弹,但显而易见的工程原则是:在确定边界上推进状态,在边界之外通过hook和运行时层扩展能力。下一篇,我们将深入探讨Agent的 工具系统(Tool System) 设计,分析如何让Agent安全、高效、可扩展地调用外部能力。
参考文献
OpenAI, “Codex CLI,” https://github.com/openai/codex ↩︎
Michael Bolin, “Unrolling the Codex agent loop,” OpenAI Blog, 2026, https://openai.com/index/unrolling-the-codex-agent-loop/ ↩︎
Anthropic, “Claude Code,” https://code.claude.com/docs ↩︎
nanobot, https://github.com/HKUDS/nanobot ↩︎