Agent Loop——AI的"大脑"是怎么运转的?

你有没有注意过一个细节:当你让Claude Code做一件复杂的事,比如"帮我重构这个模块",它不会一次性做完。它会先分析代码,然后执行一些工具(比如读文件、搜索),停下来看看结果,然后再决定下一步做什么。
这个过程会重复好几次,直到它觉得"完成了"。
这个"想→做→看→再想"的循环,就是Agent Loop——Claude Code最核心的机制。今天咱们就拆开这个"大脑",看看它是怎么运转的。
图:Agent Loop像餐厅后厨,接单→配菜→炒菜→上菜→问还要不要加菜
一个完整的生命周期
Agent Loop处理一次用户请求的完整流程是这样的:
用户输入
↓
组装上下文(系统提示词 + 历史消息 + 工具定义)
↓
调用模型API
↓
解析模型响应(可能有多个tool_use块)
↓
并行执行工具
↓
等待所有工具完成
↓
组装工具结果
↓
决定:继续循环 或 返回给用户
这个循环可能执行一次(简单的问答),也可能执行多次(复杂的重构任务)。关键是最后一步的"继续判定"——模型怎么知道"我还没完"?
query.ts:状态机的三个状态
Agent Loop的核心实现在query.ts,它是一个状态机,有三个主要状态:
IDLE(空闲):等待用户输入。这是初始状态,也是每次循环结束后的状态。
AWAITING_TOOL_RESULTS(等待工具结果):模型返回了tool_use请求,正在执行工具。这个状态下,系统会并行执行所有请求的工具,并收集它们的结果。
CONTINUING(继续):收到了工具结果,准备组装成新的上下文,再次调用模型。这个状态是循环的"连接器"。
图:Agent Loop的三状态机及其转换
状态转换的触发条件:
- IDLE → AWAITING_TOOL_RESULTS:用户输入后,模型返回了tool_use请求
- AWAITING_TOOL_RESULTS → CONTINUING:所有工具执行完成
- CONTINUING → IDLE:模型没有返回tool_use,直接给出文本响应
- CONTINUING → AWAITING_TOOL_RESULTS:模型返回了新的tool_use请求
为什么需要"继续"
你可能想问:为什么不能一次性把所有工具都执行完?为什么要循环?
因为模型需要看到工具的结果,才能决定下一步做什么。
举个例子:你说"帮我找出这个bug"。Claude Code可能先搜索相关代码(GrepTool),看到结果后发现需要看某个具体文件(FileReadTool),读完发现需要理解函数调用链(AgentTool的find_references),最后才能定位问题。
每一步的结果都影响下一步的决策。这不是预先能规划好的,而是需要根据中间结果动态调整。
这就像你找东西:你先打开抽屉看看,发现没有,再打开柜子看看,还是没有,最后才发现原来在沙发上。你没法一开始就知道"先开抽屉→再开柜子→最后看沙发",你得一步步来。
流式输出:让用户看到"思考过程"
Claude Code的一个重要特性是流式输出——模型的响应不是等全部生成完了才显示,而是一边生成一边显示。
这在技术上是怎么实现的?
Anthropic API支持流式响应(SSE,Server-Sent Events),模型生成的内容会分块发送。Claude Code的前端(React Ink)会实时接收这些块,并更新UI。
但流式输出不只是"显示文字"那么简单。在Agent Loop的语境下,流式输出包括:
文本内容的流式:模型的自然语言响应,一个字一个字地冒出来。
工具参数的流式:模型生成tool_use块时,参数是JSON格式,也是流式生成的。这意味着UI可以在参数还没完全生成时就开始渲染工具调用——用户看到"正在调用GrepTool搜索…",而不需要等整个JSON解析完成。
工具结果的流式:长时间运行的工具(如BashTool执行耗时命令)会通过onProgress回调实时报告进度,UI会显示stdout的实时输出。
这种渐进式的信息展示,对用户体验至关重要。用户不需要盯着空白屏幕等待,而是能看到AI在"动",在"想"。
工具执行编排:并行与串行
当模型在一个回合中请求多个工具时,这些工具是并行执行的。但执行顺序受到两个因素制约:
isConcurrencySafe:只有标记为并发安全的工具才能与其他工具并行执行。BashTool如果不是只读命令,就不能并发;GrepTool总是可以并发。
依赖关系:如果工具B的参数依赖于工具A的结果,它们必须串行执行。但这种情况在Claude Code中比较少见——模型通常会等收到结果后再发起新的工具请求。
实际执行流程:
模型返回 [toolA, toolB, toolC]
↓
分类:
- toolA: isConcurrencySafe=true → 立即执行
- toolB: isConcurrencySafe=false, 队列空 → 立即执行
- toolC: isConcurrencySafe=false, 队列有B → 等待B完成
↓
并行执行A和B,C等B完成后执行
↓
所有工具完成 → 组装结果 → 继续循环
“继续"是怎么决定的?
这是最核心的问题:模型怎么知道"我还要继续"还是"我说完了”?
答案是:模型自己决定。
在Claude Code的系统提示词中,有一段专门指导模型何时停止、何时继续。大致逻辑是:
- 如果你已经完成了用户的请求,直接回答,不要调用工具
- 如果你需要更多信息才能完成任务,调用工具
- 如果你调用了工具,等待结果,然后决定下一步
这不是硬编码的规则,而是通过提示词引导模型的行为。这也解释了为什么有时候模型会"过度热心"——它觉得还没完,但用户觉得已经够了。
中断与取消:用户可以随时喊停
Agent Loop支持用户中断。当你按Ctrl+C或者在UI上点击取消时,会发生:
正在进行的工具执行会被取消。BashTool会发送SIGTERM给子进程,AgentTool会通知子Agent停止。
状态机回到IDLE。已完成的工具结果被丢弃,未完成的工具调用被取消。
模型收到通知。系统会发送一条消息告诉模型"用户取消了操作",模型可以在下次响应中处理这个情况。
这种中断机制让用户保持控制权——AI可以自主行动,但用户随时可以接管。
嵌套Agent:Loop里的Loop
Claude Code支持一个特殊工具叫AgentTool,它可以启动子Agent来执行子任务。这形成了嵌套的Agent Loop:
主Agent Loop
↓
用户请求:重构这个模块
↓
模型决定使用AgentTool
↓
启动子Agent(新的Loop)
↓
子Agent完成重构 → 返回结果
↓
主Agent继续(可能验证结果、做其他事)
子Agent有自己的上下文、自己的工具权限、自己的循环。它对外部来说就像一个普通工具,但内部是一个完整的Agent。
这种设计让复杂任务可以分而治之。主Agent负责高层规划,子Agent负责具体执行。
超时与错误处理
Agent Loop需要处理各种异常情况:
工具执行超时。每个工具都有超时限制(如BashTool默认30秒),超时后会返回错误结果,模型决定是重试还是换一种方式。
模型API错误。网络问题、API限流、模型暂时不可用等。Claude Code有重试逻辑,但连续失败后会向用户报告。
工具执行错误。命令返回非零退出码、文件不存在等。这些错误会作为tool_result的一部分返回给模型,模型需要处理(重试、换方法、或向用户解释)。
错误处理的原则是:让模型决定。系统不把错误当成"终结",而是当成信息,让模型决定下一步怎么做。这符合"on distribution"的理念——模型参与决策,而不是被动执行。
性能优化:让循环转得更快
Agent Loop的性能直接影响用户体验。Claude Code做了几方面的优化:
并行工具执行。能并行的绝不串行,减少等待时间。
提示词缓存。第8篇会详细讲,但核心是把不变的上下文(如系统提示词、工具定义)缓存起来,避免每次循环都重复发送。
增量更新。在循环之间,只有新增的消息需要发送给模型,历史消息的缓存键可以复用。
流式响应。不等模型完全生成响应就开始解析和处理,减少感知延迟。
这对你意味着什么
理解Agent Loop,能让你更好地与Claude Code协作:
理解"停顿"。当Claude Code停下来"思考"时,它其实是在等模型API响应。这不是卡住了,是正常的工作流程。
善用子Agent。对于复杂任务,明确告诉Claude Code"先用子Agent分析,然后再做决定",可以让它更有效地工作。
控制循环深度。如果你发现Claude Code在一个问题上反复兜圈子,直接告诉它"够了,先停在这里",避免无限循环。
理解流式输出的价值。看到AI"在想"比看到最终结果更有信任感——你知道它在工作,而不是死机了。
如果你想构建自己的AI Agent,Agent Loop的设计给了你这些启示:
- 把"思考"和"行动"分离,让模型决定什么时候继续
- 支持并行执行,但要考虑安全性和依赖关系
- 流式输出提升用户体验
- 让用户保持控制权,随时可以中断
Agent Loop是AI Agent的"心跳"。每一次循环,都是一次"感知-决策-行动"的完整周期。Claude Code的心跳设计得很快、很稳、很灵活,让它能在复杂的编码任务中自主运转,同时保持用户的最终控制。
下一篇,咱们深入工具执行编排的细节,看看权限、并发、流式和中断是怎么实现的。
文章写到这儿,希望对你理解Agent Loop有所帮助。如果觉得有收获,欢迎点赞转发。关注梦兽编程,咱们下篇继续深入。
