OpenClaw 101|03|Agent Loop

Agent Loop 是 OpenClaw 的执行心跳:从 accepted run 到 context assembly、model stream、tool events、transcript persistence 和 retry。

OpenClaw 101|03|Agent Loop

OpenClaw 101 是一组面向 Agent Engineering 的系统拆解文章。它不把 OpenClaw 当成单一聊天产品,而是把它当作一个长期运行的 Personal Agent OS 来观察。

这一篇讲 Agent Loop。如果 Gateway 是控制面,Agent Loop 就是执行面。它把一条消息变成可观察、可恢复、可持久化的一次运行,而不是一次普通的 LLM call。

Agent Loop diagram

读完本文,你应该能回答

  • Agent Loop 和一次普通 LLM call 的区别是什么?
  • 为什么 accepted run 和 final reply 要分离?
  • tool event、assistant stream、transcript persistence 如何协作?
  • 失败、重试和 compaction 应该插在哪些边界?

本篇在系列中的位置

执行主路径篇,说明 Gateway 接收任务之后,run 如何经过 context、model、tools 和 transcript。OpenClaw 101 的主线是:先看控制面,再看执行面和状态边界,再进入上下文、能力系统、长期记忆、自动化、真实设备、扩展和 QA。下一篇进入 Sessions,解释为什么 run 不能脱离状态边界单独理解。

贯穿案例

后文会反复使用同一个任务来落地抽象机制:用户在手机上对 OpenClaw 说:“帮我看一下这个 repo 的测试为什么失败;如果需要跑命令就先做,修好后在聊天里提醒我。”

在本篇中,重点观察这个任务在 Agent Loop 这一层会遇到的边界:谁接收它,谁拥有状态,谁能触发工具,谁记录结果,以及失败后从哪里恢复。

Run lifecycle 表

环节 读者应该抓住的问题
Accepted Gateway 已接收任务,但不阻塞等待模型完成
Queued 同一 session 的 run 进入串行 lane
Context Ready 系统提示、历史、工具 schema、memory 被组装
Streaming 模型输出 assistant delta 或 tool call
Tooling 工具运行并发出 start/update/end 事件
Persisted 最终消息、工具结果、usage 和错误写回 transcript

核心判断

Agent Loop 的关键不是 while 循环,而是受 session 串行化保护的 run lifecycle。

如果只看表面,很多 Agent 框架都像是“模型 + 工具 + UI”。但真正决定系统稳定性的,是这些边界如何被拆开:谁拥有状态,谁能触发副作用,谁负责恢复,谁暴露观察面,谁承担长期维护成本。

在 OpenClaw 里,Agent Loop 不是一个孤立模块,而是 Gateway、Session、Context、Tools、Plugins、Memory 之间的连接点。理解这个连接点,比记住某个命令或配置项更重要。

运行路径

一条真实消息进入系统后,大致会经过这些步骤:

  • Gateway 接收 agent request,返回 accepted 和 runId。
  • Session lane 保证同一段对话不会并发写坏 transcript。
  • Runtime 解析模型、认证 profile、skills snapshot 和工具暴露面。
  • Context builder 组装 system prompt、history、工具 schema 和必要注入。
  • Model stream 产生 assistant delta、tool call 和最终消息。
  • Tool runtime 执行工具并把 start/update/end 事件发回 Gateway。
  • Transcript 写入消息、工具结果、usage 和 lifecycle。
  • 如果遇到上下文压力或 provider 错误,进入 compaction、retry 或 fallback。

这些步骤看起来很多,但它们解决的是同一个问题:让 Agent 的行为从“模型临场发挥”变成“系统可控制、可观察、可恢复的运行过程”。

可迁移伪实现:Agent Loop

下面的伪代码是机制抽象,不对应 OpenClaw 的真实 API 或文件结构。

type RunEvent =
  | { stream: "lifecycle"; phase: "start" | "end" | "error" }
  | { stream: "assistant"; delta: string }
  | { stream: "tool"; phase: "start" | "update" | "end" }

async function agentLoop(run: AgentRun) {
  await sessionLane(run.sessionKey, async () => {
    emit({ stream: "lifecycle", phase: "start" })
    const context = await buildContext(run.sessionKey)
    for await (const event of model.stream(context)) {
      if (event.toolCall) await executeToolAndEmit(event.toolCall)
      if (event.text) emit({ stream: "assistant", delta: event.text })
    }
    await persistTranscript(run.sessionKey)
    emit({ stream: "lifecycle", phase: "end" })
  })
}

这段伪代码的重点不是函数名,而是边界:输入先被标准化,状态通过明确的 store 或 lane 管理,副作用通过 runtime 或 policy 执行,结果再回到 transcript、event stream 或 delivery target。

设计取舍

  • run accepted 和 run finished 分离,避免把长任务塞进同步 RPC。
  • session lane 牺牲部分并发,换取 transcript 一致性。
  • assistant/tool/lifecycle 事件拆开,让 UI、聊天渠道和自动化都能订阅同一个事实流。

这些取舍解释了 OpenClaw 为什么不像一个最小 demo。最小 demo 追求路径短,长期系统追求边界清晰。路径短会让第一个 demo 很快跑起来;边界清晰才会让系统在多渠道、长会话、多人入口、插件扩展和自动化场景下不崩。

评估清单

评估任何 Agent 框架的 Agent Loop 设计,可以看这几个问题:

  • 这个层级是否有明确 owner,还是散落在多个客户端或插件里?
  • 它是否把状态、权限、运行时副作用和展示逻辑分开?
  • 它是否能被观测、被调试、被回放?
  • 它失败时是否有恢复路径,而不是只给用户一个模型错误?
  • 它是否避免把 provider/channel/tool 的私有细节写死进 core?

如果这些问题没有答案,系统一旦从单用户 demo 走向真实使用,很快就会在上下文污染、重复副作用、权限失控、长任务丢失和调试困难中付出代价。

下一篇

下一篇继续拆 Sessions。OpenClaw 101 的主线会继续沿着 Personal Agent OS 的边界往下走:从运行时,到状态,到能力系统,再到安全、扩展和 QA。

References

  • Agent loop
  • Streaming
  • Retry policy
  • Compaction
  • Session management