AI 编程助手的灵魂:Think-Act-Observe 循环
如果你把 Claude Code 的源码翻一遍,你会发现一个反直觉的事实:
整个程序的核心,是一个 while 循环。
不是复杂的状态机,不是精巧的管道,不是分布式协调——就是一个朴素的 while 循环,反复做三件事:让模型想、让模型动手、把结果喂回去。
while (用户没退出) {
思考 = 调用LLM(对话历史 + 工具定义)
if (思考.要调用工具) {
结果 = 执行工具(思考.工具调用)
对话历史.push(结果)
} else {
输出(思考.回复)
}
}就这?一个 AI 编程助手的全部秘密就藏在这个循环里?
是的。而且这个秘密并不新——它叫 ReAct 模式(Reason + Act),2022 年就被提出来了。但 ReAct 从论文到产品,中间隔着的不是算法突破,而是工程判断:什么时候该让模型想、什么时候该让它动、动了之后怎么把结果塞回去让它继续想。
从 Chatbot 到 Agent:一个循环的距离
ChatGPT 的模式很简单:你说一句,它回一句。模型是被动的——它等你提问,然后生成一段文本。这段文本可能是代码、可能是解释、可能是建议,但它的本质是文本。文本不会改变你的文件系统,不会运行命令,不会读取你项目的实际状态。
Claude Code 做的第一件事,就是把这个单向通道变成双向闭环。
模型不再只是"回答者",它变成了"操作者"。它可以说"让我先看看这个项目结构",然后真的去读文件;它可以说"这个测试失败了,我来跑一下",然后真的执行命令;它可以看到命令输出,然后基于真实结果做下一步判断。
这个转变的关键不是模型变聪明了——模型还是那个模型。关键是多了一个行动通道,以及一个让思考和行动交替发生的循环机制。
Think-Act-Observe:三步拆解
Think(思考)
循环的第一步总是思考。模型接收当前的对话历史——包括用户的请求、之前的工具调用结果、系统提示中注入的项目上下文——然后做出判断:
- 我现在需要什么信息?(→ 调用工具)
- 我现在能做出什么结论?(→ 回复用户)
- 我之前的操作结果是否符合预期?(→ 调整策略)
这一步没有技术门槛。它就是普通的 LLM 调用,只不过 prompt 里多了一组工具定义——每个工具的名字、描述、参数 schema。模型通过自然语言理解来决定"我现在该用什么工具"。
Act(行动)
如果模型决定调用工具,循环就进入行动阶段。这是 Claude Code 源码里最"有实感"的部分:模型生成的不是文本,而是一个结构化的工具调用请求。
{
"type": "tool_use",
"name": "read_file",
"input": { "path": "src/main.ts" }
}Claude Code 的 agent 模块解析这个请求,找到对应的工具实现,执行它。工具可能是读文件、写文件、执行 shell 命令、搜索代码……每个工具都是独立模块,有输入验证、执行逻辑、权限控制和输出格式化。
这一步的核心设计决策是:工具调用是同步阻塞的。模型发出调用请求后,循环暂停,等工具执行完毕、结果返回,才进入下一步。这不是性能最优的选择(异步并行显然更快),但它是认知最清晰的选择——模型一次只做一个动作,看到结果再决定下一步,这和人类编程的工作方式一致。
Observe(观察)
工具执行完毕后,结果被格式化为一段文本,作为"工具响应"注入对话历史。然后循环回到 Think 阶段——模型看到了它行动的结果。
这才是真正的魔法所在。
模型不是"盲操作"。它读了文件,看到了实际内容;它跑了命令,看到了真实输出;它改了代码,然后跑测试,看到了测试结果。基于这些真实世界的反馈,它调整下一步行动。
如果测试失败了,它会读错误信息然后修复代码。如果文件结构和预期不同,它会适应实际情况。如果命令输出揭示了新的问题,它会改变策略。
这个"行动→观察→调整"的闭环,就是 AI 编程助手和代码补全工具的本质区别。 TabAutocomplete 是在猜你接下来要写什么;Claude Code 是在理解你项目的实际状态然后做出判断。
循环的终止条件
一个循环如果不能停下来,就是死循环。Think-Act-Observe 循环的终止条件是什么?
在 Claude Code 里,终止发生在模型决定不调用任何工具的时候。当模型判断"我已经有了足够的信息,可以回复用户了",它生成的就不再是工具调用,而是一段自然语言回复。这段回复被展示给用户,当前回合结束。
用户再发一条消息,循环重新开始。
这里有一个微妙的设计:模型可能在同一个回合里循环多次——读文件、改文件、跑测试、再改文件——直到它觉得自己搞定了,才给用户最终回复。这意味着用户看到的不是过程,而是结果。过程被折叠在循环内部,对用户透明。
从 ReAct 到生产:工程判断
ReAct 论文里的循环和 Claude Code 源码里的循环,看起来一样,差得很远。差在工程判断:
上下文窗口是有限的。 每次循环迭代都在消耗 token——工具调用的请求、工具返回的结果、模型的思考。如果循环跑了 20 轮,上下文可能被撑爆。Claude Code 的解决方案是分层的:消息裁剪(trimming)、对话压缩(compaction)、上下文窗口管理。这些不是 ReAct 论文里写的,是生产环境逼出来的。
工具调用可能失败。 文件不存在、命令执行超时、权限被拒绝——这些在论文里不存在,在生产里是常态。每个工具都有错误处理,错误信息被格式化后注入对话历史,让模型"看到"失败然后自己调整。
模型可能 hallucinate。 它可能调用一个不存在的工具、传入错误的参数、或者在不需要工具的时候硬调。Claude Code 有工具名验证、参数 schema 校验、以及 system prompt 里的行为约束。
用户需要控制权。 不是每次工具调用都应该被执行——删文件、推代码、改配置,这些操作需要确认。权限系统(permission model)在 Act 阶段插入用户确认环节,循环暂停,等人拍板。
这些工程判断加起来,才把一个学术概念变成了每天能用的产品。
延伸阅读:BYF 的 TurnFlow —— 同一个循环的另一种实现
当我用同样的思想去审视自己的开源项目 BYF(Be Your Friend) 时,发现它的 Agent 引擎核心也是同一个循环,但做了自己的工程取舍。
BYF 将 Think-Act-Observe 循环封装在 TurnFlow 模块中,由它驱动无状态的 loop/runTurn()。与 Claude Code 最大的不同在于:BYF 的 TurnFlow 不跨 turn 持有状态——每次循环迭代都是独立的,状态由外层的 Session 容器管理(ContextMemory._history、AgentRecords、Wire Records)。
这个设计决策直接体现在 BYF 的架构约束里:Agent 类必须可独立使用,构造函数不能强制创建 Session 实例。这意味着你可以拉一个 Agent 出来,给它 provider 和 system prompt,它就跑了——不依赖 Session 的生命周期。这种"薄 Agent"设计让 BYF 的 /btw side query(旁路查询)能够复用同一个 Agent.generate() 跑一次只读的 LLM 调用,而不干扰主循环的状态。
BYF 还对循环的并发安全做了精细处理:当主任务正在跑工具调用时,如果用户想插问一句话(/btw),系统会通过 ContextMemory.getStableSnapshot() 拿到一份剔除了"悬空 tool_call"的快照,在快照上跑查询。结果展示在浮层 overlay 里,不进历史、不进 wire records。主循环完全不受干扰。
这是同一个循环思想在不同项目中的差异化落地——核心骨架一样,但肌肉的纹路各不相同。
为什么这个循环如此重要
Think-Act-Observe 循环的重要性不在于它多复杂——它很简单。重要性在于它定义了一种新的交互范式:
在旧的范式里(Chatbot),AI 是被动的应答者。你说什么,它回答什么。能力边界由单次生成的文本长度决定。
在新的范式里(Agent),AI 是主动的探索者。它通过工具感知世界、通过行动改变世界、通过观察调整策略。能力边界由工具数量和循环深度决定。
这就是为什么同样的模型,放在 ChatGPT 里是聊天机器人,放在 Claude Code 里是编程助手。不是模型变了,是模型运行的循环变了。
下一篇
循环是骨架,但骨架本身不会干活。下一篇文章我们要拆解 Claude Code 的工具系统——看看 30 多个工具是怎么被组织、描述、调度的,以及为什么工具描述的质量直接决定了 AI 能干什么、不能干什么。
本系列基于 Claude Code 官方源码项目进行架构分析,聚焦设计思想而非代码实现。