乐于分享
好东西不私藏

Claude Code源码解析——Agent Loop 深度拆解:上下文组装、工具决策与观察循环

Claude Code源码解析——Agent Loop 深度拆解:上下文组装、工具决策与观察循环

Agent Loop 深度拆解:上下文组装、工具决策与观察循环

上一篇文章追踪了用户输入到 Agent 输出的主执行链路,但有一个核心机制被刻意搁置:当单次大模型调用无法完成任务时,系统如何通过多轮迭代逐步逼近目标? 本篇完全聚焦 Agent Loop 的内部运转——拆解每轮迭代中的上下文组装、模型调用、工具路由、执行反馈,以及循环如何被约束在可控边界内。

架构图

上图对应本文的拆解主线。箭头流向即为一轮迭代的完整数据路径:assistant 发起调度 →query 管控预算与状态 → 模型返回决策 → 工具包执行副作用 → 结果回填对话历史。读者可将后续每个章节对应到图中的一段箭头。


五个核心模块的职责边界

Agent Loop 不是单一文件,而是五个模块在运行时拼合出的虚拟状态机。明确各自边界,才能理解后续协作链路。

模块
目录
层级
在循环中的职责
assistantsrc/assistant/
Application
循环宿主。协调工作流、维持会话状态、在每轮工具执行完成后拉起下一轮模型调用。证据:index.test.ts 注释 "Coordinates core workflows and business logic"。
querysrc/query/
Application
循环控制器。管状态流转(transitions.ts)、 token 预算(tokenBudget.ts)、终止钩子(stopHooks.ts)、运行时配置(config.tsdeps.ts)。不产生副作用,只做决策与约束。
constantssrc/constants/
Application
硬性天花板。apiLimits.ts 定义迭代轮数上限、单次请求上限等不可逾越的常量;tools.test.ts 和promptEngineeringAudit.test.ts 说明此目录还承载工具定义审计与提示词合规检查。
hookssrc/hooks/
Application
循环内外的事件钩子。useScheduledTasks.test.ts 表明 hooks 层处理定时任务调度;replBridgePermissionHandlers.test.ts 和swarmPermissionPoller.test.ts 说明 hooks 同时参与权限回调与 Swarm 模式的轮询协调。在单 Agent Loop 中, hooks 主要负责权限拦截与任务队列管理。
proactivesrc/proactive/
Application
主动行为模块。useProactive.ts 是一个 React 风格的 hook ,基于state.baseline.test.ts 的存在推断它维护独立的状态机,用于在循环的观察阶段主动触发后续动作——例如当工具返回"文件未找到"时, proactive 层可以决定是否主动追加一次搜索操作,而非被动等待模型重新决策。

依赖方向是单向的:assistant → query → constants(配置读取),assistant → hooks(权限与调度),assistant → proactive(主动行为注入),assistant → tools(副作用执行)。没有反向依赖。

模块分层图

上图对应这条依赖链。assistant 位于 Application 层顶部,向下依赖queryhooksproactive 的控制信号,再向下调用builtin-tools 和agent-tools。读者可沿纵向箭头确认:控制流自上而下,数据流自下而上回填。


协作链路一: agent-loop-iteration —— 一轮迭代的完整闭环

这是最核心的链路,按输入 → 处理 → 输出/副作用 展开。

输入:用户消息进入循环

用户请求经过 CLI 或 Bridge 层到达assistant 模块。assistant 做两件事:1. 将用户消息追加到对话历史。2. 调用query/config.ts 获取当前运行时配置,调用query/tokenBudget.ts 检查剩余预算。

如果预算耗尽,循环在此直接终止,返回截断提示。否则进入处理阶段。

处理:上下文组装 → 模型调用 → 意图解析

上下文组装(对应下一节详述的context-assembly 链路):assistant 将系统提示词、项目记忆(来自memdir 模块)、完整对话历史、可用工具定义拼装为一个请求体。query/transitions.ts 将状态机推进到THINKING 状态。

模型调用:请求体发送给大模型。

意图解析:模型返回两种可能:- 纯文本(content)→ 无工具调用,循环准备终止。-tool_use 请求 → 包含工具名和参数,循环继续。

输出/副作用:工具路由 → 执行 → 结果回填

query/transitions.ts 将状态从THINKING 推进到ACTIONassistant 解析tool_use 中的工具名,执行路由(对应第三节tool-call-routing 链路),分发到builtin-tools 或agent-tools

工具执行完成后,结果被封装为ToolResultMessage,追加到对话历史。transitions.ts 将状态推进到OBSERVATION

此时proactive 模块介入:useProactive.ts 检查工具结果,判断是否需要主动追加操作。如果需要,直接注入一条额外的工具调用到当前轮次,无需再等模型决策;如果不需要,transitions.ts 将状态推回THINKINGassistant 拉起下一轮模型调用。

一轮迭代的完整数据流

用户消息assistant(追加历史)query/tokenBudget(预算检查)assistant(组装上下文)大模型(推理)assistant(解析意图)├─纯文本终止└─tool_useassistant(路由)builtin-tools/agent-tools(执行)assistant(结果回填)proactive(主动行为判定)query/transitions(状态OBSERVATIONTHINKING回到"组装上下文",开始下一轮

核心流程图

上图对应这条完整链路。读者可沿流向线追踪:中间的分叉点即模型返回后的分流(直接输出 vs 工具调用),右侧的回环箭头即结果回填后重新进入"组装上下文"的闭环路径。


协作链路二: context-assembly —— 上下文组装与截断

上下文组装是每轮迭代的第一步,也是资源约束最集中的一步。

输入

assistant 从四个来源收集数据:1.系统提示词:硬编码的系统指令,定义 Agent 的行为边界。2.项目记忆:来自src/memdir/ 模块,包含findRelevantMemories.ts(相关记忆检索)和memoryScan.ts(记忆扫描),按相关性注入当前任务的上下文。3.对话历史:包含用户消息、模型回复、工具调用记录、工具返回结果。4.工具定义:当前可用的工具 schema ,告诉模型它可以调用什么。

处理:拼接与截断

四部分拼接后,总 token 数可能远超模型窗口。此时query/tokenBudget.ts 介入执行截断策略。

截断优先级(基于模块职责推断):1.系统提示词不截:这是行为底线,截断后模型可能失控。2.工具定义不截:缺少工具定义,模型无法发起调用。3.项目记忆按相关性裁剪memdir 模块提供memoryAge.ts(记忆老化)机制,低相关性的记忆被优先丢弃。4.对话历史从最早处裁剪:早期消息被丢弃或压缩为摘要(src/components/__tests__/compactMessages.test.ts 的存在证实了消息压缩机制的存在)。

输出

一个符合tokenBudget 约束的请求体,发送给大模型。

设计取舍:截断历史意味着模型会丢失用户早期的指令细节(例如"所有变量名用驼峰")。好处是保证 API 调用不崩;代价是长任务后期可能偏离初始约束;风险边界由constants/apiLimits.ts 的硬上限兜底——即使截断策略有 bug ,单次请求的 token 数也不会超过 API 物理限制。


协作链路三: tool-call-routing —— 双轨工具路由

模型返回tool_use 后,assistant 需要将抽象的工具名映射到具体的执行器。

输入

模型返回的结构化对象,包含tool_name 和parameters

处理:查表路由

项目在package.json 层面将工具物理隔离为两个包:

  • packages/builtin-tools
    :基础工具。推断包含文件读写、内容搜索、字符串替换等高频低风险操作。依赖链短,权限要求低。
  • packages/agent-tools
    :高级代理工具。推断包含外部通信、复杂多步操作链、需要更高权限的操作。依赖链长,可能引入第三方 SDK 。

路由逻辑(基于src/constants/tools.test.ts 的存在推断):系统维护一个工具注册表( Registry ),每个工具注册时声明自己所属的包。assistant 拿到tool_name 后查表,得到目标包和入口函数,分发执行。

输出/副作用

目标工具包执行具体操作,返回结果。

hooks 模块在此介入:根据src/hooks/__tests__/replBridgePermissionHandlers.test.ts 的存在,部分工具调用在执行前需要通过 hooks 层的权限回调确认。高权限操作(如执行 shell 命令)被拦截,等待用户确认或自动拒绝;低权限操作(如读取文件)直接放行。


三重终止防线

循环必须有确定的退出条件。系统在三个层级部署了拦截:

第一层:query/stopHooks.ts —— 语义终止

推断此模块实现业务层面的循环退出判定:- 模型返回纯文本(无tool_use)→ 自然结束。- 连续多轮工具返回相同错误 → 熔断退出。- 用户主动中断( Ctrl+C )→ 强制退出。

stopHooks 在每轮迭代的"意图解析"阶段被assistant 调用。如果命中任一终止条件,transitions.ts 直接将状态推到DONE,不再发起下一轮模型调用。

第二层:query/tokenBudget.ts —— 资源终止

即使语义上没有终止, token 预算耗尽也会强制截断。此时系统会将当前已有的对话历史和部分结果拼装为一条"预算不足"的终止消息返回给用户。

第三层:constants/apiLimits.ts —— 硬性天花板

前两层都是可配置的、有弹性的。apiLimits.ts 中的常量是最后兜底:最大迭代轮数、单次请求最大 token 数。无论前两层是否正常工作,这些硬编码的上限确保循环物理上不可能无限运行

src/constants/__tests__/promptEngineeringAudit.test.ts 的存在说明,这些限制值不仅被使用,还被纳入提示词工程的审计测试中——确保模型在被强制截断时,仍然能产出可用的部分结果。


一个设计取舍的完整分析:双轨工具隔离

决策:将工具拆为builtin-tools 和agent-tools 两个独立包

好处:-依赖隔离builtin-tools 只保留核心依赖,升级周期可控。agent-tools 引入的第三方 SDK 崩溃不会波及基础工具链。-权限分层hooks 层可以根据工具所属包施加不同等级的权限检查。基础操作快速放行,高级操作严格审核。-独立演进:新工具的添加不会触发基础工具包的版本发布和回归测试。

代价:-路由复杂度assistant 必须维护一个跨包的工具注册表,每次新增工具需要同步更新注册逻辑。-测试成本:工具的集成测试需要同时安装两个包, mock 链路更长。

风险边界:- 如果注册表与实际包的导出不一致(例如工具名拼写错误),循环会在运行时抛出"工具未找到"异常。stopHooks 的熔断机制会捕获这类异常,防止循环卡在重复重试上。- 如果未来工具数量膨胀到数百个,单表路由可能成为性能瓶颈。当前架构下,这个上限远未触及。


小结

Agent Loop 的运转依赖五个模块的精密协作:assistant 调度、query 管控、constants 兜底、hooks 拦截、proactive 主动补充。每轮迭代走完"组装 → 推理 → 路由 → 执行 → 观察"的闭环,三重防线确保循环不会失控。

下一篇跳出单 Agent 的循环,拆解多 Agent 协作架构:src/coordinator/ 的 Coordinator 模式、 Swarm 调度机制,以及 Worktree 如何为并行 Agent 提供文件系统隔离。

篇间导航

  • 上一篇:主执行链路追踪:从用户输入到 Agent 输出的完整路径
  • 下一篇:多 Agent 协作架构: Coordinator 模式、 Swarm 调度与 Worktree 隔离