Agent Engineering 101|04|Agent Loop

Agent Loop 把模型输出变成连续行动:构造上下文、调用模型、解析工具、执行工具、写回结果,并在需要时继续下一轮。

Agent Engineering 101|04|Agent Loop

核心结论

配套代码仓库: github.com/llm-101/mini-agent-harness

读完本文,你应该能回答

  • 为什么 Agent Loop 不是普通 while loop?
  • 模型输出什么时候会变成工具执行?
  • 工具结果为什么必须写回 session?
  • loop 在什么条件下继续、停止或失败?

本篇在系列中的位置

  • 上一篇:03 LLM Call 把 provider 差异变成稳定模型接口。
  • 本篇:本文解释 Harness 如何把模型输出变成连续行动。
  • 下一篇:05 Messages and Events 会拆开持久状态和运行过程。

贯穿例子

本系列会反复使用同一个任务来连接各章:

用户说:“帮我修复这个 repo 里的 failing tests。”

在这个任务里,Agent Loop 负责把“读文件 → 运行测试 → 根据失败修改 → 再运行测试 → 总结结果”串成多轮行动。读者要关注的是:每一轮模型只提出下一步,Harness 必须把真实执行结果写回去,下一轮才有依据。

  • Agent Loop 是 Harness 的运行时推进器。它读取当前 session 分支,构造模型上下文,调用模型,执行工具,把结果写回 session,并通过 AgentEvent 暴露进度。
  • Agent 不是一次 chat completion。只要模型可以请求工具,系统就必须把 tool call、tool result、下一轮模型调用和停止条件组织成可恢复的循环。
  • mini-agent-harness 中,Loop 是把模型、工具、session 和事件连接起来的主路径;它小,但必须承担运行时控制。

定义

Agent Loop 是 Harness 的运行时推进器。它读取当前 session 分支,构造模型上下文,调用模型,执行工具,把结果写回 session,并通过 AgentEvent 暴露进度。

为什么要单独看这一层?

Agent Loop 是连续行动的控制点。它不能只是一段 while loop,因为每次工具执行都会改变世界,也会改变下一轮模型应该看到的事实。

Agent Loop Runtime

边界

这一层的职责可以拆成几个稳定部分:

  • turn_start:每轮开始发出 turn 生命周期事件
  • Compaction Check:超过阈值时先压缩历史分支
  • Context Build:从 session、system prompt、tools 生成请求
  • Model Stream:把模型 delta 转成 message_update / usage
  • Tool Runtime:工具调用开始、执行、结束、写回
  • Stop Condition:没有 toolCall 时结束;否则继续下一轮

这一层的边界可以用一个问题检验:如果它的内部实现变化,模型适配、工具执行、状态存储和产品外壳是否都不需要跟着重写?如果答案是否定的,说明这个边界还没有真正收束变化。

代码锚点

本篇主要对应这些模块:

  • src/core/agent-loop.ts
  • src/core/agent-events.ts
  • src/tools/tool-runtime.ts
  • src/context/context-builder.ts
  • src/session/jsonl-session-store.ts

阅读代码时建议先看类型,再看运行路径。

类型定义告诉你这一层暴露什么 contract;运行路径告诉你这个 contract 在 Agent 执行中何时被消费、何时被写回、何时被产品层看见。

运行流程

One Turn in AgentLoop.runLoop

一次典型执行可以概括为:

  1. User Input:append user message
  2. Build Context:ContextBuilder + hooks
  3. Model Call:stream or complete
  4. Execute Tools:sequential / parallel
  5. Continue / Stop:依据 toolCalls 决定
  • AbortSignal:取消运行
  • Extensions:before/after hooks
  • TraceRecorder:turn 与 usage 记录

这里最容易被忽略的是“中间态”。生产级 Agent 不是只关心最终答案;它还要在运行过程中展示进度、捕获错误、记录 usage、允许取消,并把可恢复状态写回 session。

读者抓手:一次“修 failing tests”的 loop trace

轮次 模型看到什么 模型输出什么 Harness 做什么
1 用户任务、repo 摘要、可用工具 先读取测试说明和 package 配置 执行 read/list,把结果写回 session
2 文件内容、上一轮工具结果 运行测试命令 通过 Tool Runtime 执行 bash,记录 stdout/stderr
3 失败堆栈、相关文件 修改一个实现文件 校验路径和写入权限,执行 edit
4 diff、测试失败信息 再次运行测试 执行 bash,把结果作为 tool result 回灌
5 测试通过、变更摘要 给用户最终说明 停止 loop,输出最终 answer

所以 Agent Loop 的重点不是“循环”本身,而是每一轮都把模型意图、真实执行结果和下一轮上下文连接起来。

可迁移伪实现:Agent Loop

下面的伪代码是机制抽象,不对应真实 API 或文件结构。它只用来说明这一层的控制点:

while (true) {
  yield turn_start();
  const context = buildContext(session.branch());
  const response = await model.stream(context);
  session.append(response.message);
  const calls = extractToolCalls(response.message);
  if (calls.length === 0) break;
  const results = await toolRuntime.executeAll(calls);
  session.appendAll(results);
}

这个草图的价值在于说明控制点,而不是提供可复制的库代码。

真正的工程实现还要处理错误、取消、并发、token 预算、日志、权限、序列化和 provider 差异。

工程原则

将这一层从 Agent 系统中拆出来,通常带来四个直接收益。

第一,可替换。

外部系统、模型 provider、工具集合或产品外壳变化时,核心运行时不必整体重写。

第二,可观测。

边界清晰后,事件、trace、usage、错误和状态迁移都有稳定落点。

第三,可恢复。

只要状态写入 session,运行时就可以在进程重启、工具失败或长任务中断后继续推理。

第四,可治理。

权限、脱敏、审批、路径保护和执行策略可以放在稳定 hook 或 runtime boundary 上,而不是只靠 prompt 约束。

和 Agent Harness 的关系

Agent = Model + Harness 这个公式的重点,不是把模型之外的所有东西都称为“工程杂活”。

相反,它提示我们:模型之外存在一套必须被设计的运行时系统。

Agent Loop 就是这套系统中的一个切面。它不替代模型能力,也不替代产品体验;它让模型能力可以被组织成可执行、可观察、可恢复、可治理的任务流程。

小结

Agent Loop 的核心价值,是把一类容易扩散的复杂性收束到明确边界中。

mini-agent-harness 中,这个边界被刻意写得较小,方便阅读和教学。但它对应的问题并不小:只要一个 Agent 要长期运行、调用工具、管理上下文、支持 UI、保存状态并处理失败,这个边界就会出现。

下一步可以继续沿着系列计划,把这些边界组合成完整 Agent Harness:模型边界、工具边界、状态边界、上下文边界、扩展边界和产品外壳边界。