Agent Engine

引擎把 ReAct 循环真正跑起来——模型想、做、看,一步接一步;它在内存里累积整轮上下文、随之自动压缩,并把每一步实时流式推给客户端。

一次运行,全程记着自己走到哪

公式与 ReAct 循环 已经讲过,agent 的核心是“想→做→看”不断重复的 ReAct 循环Agent 引擎就是这个循环真正运转起来的地方:模型推理当前该做什么,用一次工具调用把决定付诸行动,观察返回的结果,再回到推理——一步接一步,直到任务完成。

它和一次普通的 chat completion 很不一样。chat completion 是一问一答、无状态的:接收一段文本,返回一段文本,交互结束后什么都不留下。而引擎的一次运行会横跨许多轮工具调用,全程记得自己走到哪一步、试过什么、得到过什么结果——正是这份持续累积的上下文,让它能连贯地推进一个长任务。但上下文只增不减,迟早会大到溢出窗口,所以引擎会随它增长自动压缩;与此同时,中间结果也不必等整轮跑完才给出,而是实时流式推给客户端,让用户看着任务一步步向前。

引擎只负责这个循环本身;DB、按任务的锁、额度账本、传输,都在它外层的 Task Orchestrator 里。也正因为把这些挡在外面,这套循环才被抽象成一个 runAgentLoop()@zapvol/backend/src/agent/):server 的 web 服务和 desktop 的桌面应用各自在外面组装好参数、再消费它返回的流,两边调的是同一个引擎函数。

引擎入口:runAgentLoop

Agent Engine — runAgentLoop() @zapvol/backend/agent/ 中的平台无关 ReAct 循环 AgentLoopParams context · config · input · control · compactionRepos · toolServices ① 构建 turn prefix + step compactor engine.buildTurnInput —— 保最近 turn raw,把更早历史折进 rolling summary ② 构建 Agent — new ToolLoopAgent(...) model createModel(modelId, providerKey) instructions instructions-builder —— 四层提示词:策略 · 工具 · 记忆 · 环境 tools coreTools(注册表 + 能力过滤)∪ mcpTools prepareStep createPrepareStep —— step 压缩 + reminders 注入 + cache control stopWhen stopConditions — complete 工具触发终止 context RuntimeContext (runtimeContext) — sandbox, writer, todos, reminders ③ agent.stream() — ReAct 循环 messages modelMessages — Rn 末状态(来自 buildTurnInput) onStepEnd 累积 stepUsages —— 每步 token 用量 timeout buildStreamTimeouts —— chunkMs 120s · stepMs 300s · toolMs 120s 文本流 → agentStream 输出 工具执行 沙箱 → 结果 → 循环 prepareStep 压缩 + reminders AgentLoopResult agentStream · stepUsages 状态机 通过 context.writeTransient() 广播 generating executing completed error aborted generating ⇄ executing 是 ReAct 核心

知道了引擎是个有状态的 ReAct 循环,接下来看它一次运行怎么起步。对外,引擎只有一个入口:runAgentLoop(params)——一次任务运行(一轮,turn)就从这里开始。调用方——server 端的 Task Orchestrator 或 desktop 的 agent handler——先把参数备齐(消息历史、sandbox、MCP 工具等)交进来,再消费它返回的结果。函数体只有四步,而且顺序被数据依赖锁死、不能重排:

  1. 构建输入buildAgentInputs)——先把这一轮要喂给模型的东西凑齐:用哪个 model、四层系统提示词、以及这一轮可用的工具集。其中提示词(instructions)和工具集(tools)谁也不依赖谁,所以并行构建、互不等待。
  2. 接线压缩、渲染起手消息buildCompactionSetup)——压缩要压得准,得先知道这一轮到底占了多少预算,所以这一步先实测:把当前真正下发的 instructions + tools 数一遍 token,据此建起压缩引擎。然后由 engine.buildTurnInput 把存储的历史渲染成这一轮的起手消息——[user: Task Context] + raw 窗口 + tail,同时从持久的 meta:anchor 里种入跨轮校准锚,让这一轮的压缩和上一轮对得齐。最后创建本轮每步要用的 step 压缩器。
  3. 预算快照saveBudgetSnapshot)——把刚测出的预算记一笔,供 admin 观测。它是 fire-and-forget 的(仅在 server 上做,隔离路径直接跳过),写与不写都绝不阻塞本轮——观测得给正事让路。
  4. 装配并启流new ToolLoopAgent + agent.stream)——万事俱备,接上 prepareStep / onStepEnd / onToolExecutionEnd 三个回调,循环就转起来:LLM 生成 → 工具执行 → 追加结果 → 继续生成,直到模型自己调用 complete 或撞上步数上限。有一点要留意:runAgentLoop 返回 AgentLoopResult { agentStream, stepUsages } 的这一刻,流还没被消费——引擎只是把它交出去,由调用方自行驱动;等消费完、丢掉引用,底层的 StreamTextResult 就能被 GC 回收。

每步 prepareStep 做什么

四步装配是开场一次性的;prepareStep 则是每一步都要重跑一遍的那部分。它在每次 LLM 调用之前执行,按固定顺序做四件事:

先跑一遍 in-loop step 压缩replay → 门槛 → reduce → project),在真正下发前把上下文压回预算内;再打一个 prefix cache 断点(markPrefixCacheBoundary),让断点以前的稳定前缀能被缓存命中、省下重复计费;接着把瞬态 reminders 追加在缓存前缀之外——它们每步都在变,混进缓存区会把缓存打脏;最后按 activeTools 把这一步真正暴露给模型的 MCP 工具面收窄。每步收尾时,onStepEnd 再累积一条 StepUsageData,记下这一步的开销。

什么会跨轮传递

引擎跑完一轮就把自己清空了,这一轮结束时不留任何状态。那下一轮怎么接得上?靠两样特意留存下来的东西。

一样是那条校准锚:它要等本轮消息落库之后,才由 saveTurnAnchor 写进快照存储的 meta:anchor,下一轮开场再由 buildTurnInput 种回去。另一样是内容寻址的快照存储——各段的摘要、各 part 的削减态——它们在循环途中就已写入,自然也留到了下一轮。

AI SDK 底层机制prepareStep 的消息引用语义、onStepEnd / onEnd 的双层触发、stopWhen 的默认值兜底等反直觉点,详见 AI SDK → 运行生命周期消息引用模型

状态机

一次运行往往要跑上好几分钟,不能让用户对着黑屏干等。所以引擎一边跑一边广播自己的状态,让客户端 UI 实时把进度画出来:状态事件经 agent-state data part、通过 context.writeTransient() 发出(transient——只作实时广播,不进消息历史)。

Agent Engine 状态机 一个 turn 的各步 · 经 agent-state data part 广播 generating executing 工具调用 结果 → 继续 compacting reduce 触发(瞬态) 终止 completed error aborted 完成 · 步数上限 · 停止 / 中止

其中 generating ⇄ executing 这一对来回,就是 ReAct 的核心——LLM 在生成文本和调用工具之间交替,直到发出完成信号或达到步数上限。compacting 只在某一步真的越过压缩触发线时才出现;它的意义是让 operator 看到“正在压缩上下文”,而不是误以为卡死。

:初始化状态(agent_building / context_building / mcp_connecting / agent_running)由 Task Orchestrator 的流构建阶段发射,不属于引擎内部。完整状态集见 AgentStateKey@zapvol/common)。

循环驱动的子系统

引擎真正的角色,是把下列子系统缝进同一个 ReAct 循环——输入侧(提示词、工具、MCP、Skills)、状态管理(记忆、压缩)、输出侧(流式)。每个子系统都有自己的页面,下面这张表是它们的地图。

子系统在循环中的角色
提示词系统四层提示词组装(策略 → 工具 → 记忆 → 环境),每轮构建提示词系统
工具注册表自描述工具配置,含能力过滤与压缩钩子工具
MCP 集成把运行时发现的 MCP 工具桥接进工具集,含凭证作用域MCP
Skill 加载view_skill(name, path?) 按需加载 SKILL.md / L3 资源Skill 加载
记忆系统跨会话持久记忆——显式保存 + 后台自动提取记忆系统
上下文压缩append-only raw 流 + part 寻址 checkpoint,在 token 压力下每步 reduce上下文压缩
流式data-part 协议 + 双传输(SSE / WebSocket)+ Redis 支持的 SSE 续传流式架构
规划 · 子代理write_todos 规划;task 生成隔离子代理做委派任务规划 · 子代理

属编排器,不属引擎后台任务队列(流结束后的扣费、摘要、记忆提取)由 Task Orchestrator 驱动,不在循环里。

相关阅读

这页有帮助吗?