OpenClaw 的 Agent 循环怎么转:从 agent RPC 到落盘的一整条链路

2026-08-17

用 OpenClaw 的人迟早会撞上同一类问题:消息发出去了什么都没回;或者回了两遍;或者一个任务跑半天不结束,也不知道卡在哪一环。这些现象背后是同一件事——你不知道一次「Agent 运行」内部被拆成了哪些阶段。

官方文档把这条链路叫 agent loop(Agent 循环),定义写得很干脆:按会话串行执行的一次运行,把一条消息变成动作和一个回复,中间包含接收、上下文装配、模型推理、工具执行、流式输出、持久化这几段。注意「串行」两个字,它是后面一堆行为的根因。

这篇顺着 concepts/agent-loopconcepts/agent 两页把链路一段段拆开,只讲机制和配置项——知道哪一段管什么,排查时才能直接跳到对的地方。

入口只有两个,但 RPC 是「立刻返回」的

官方列的入口就两处:

  • Gateway RPC:agentagent.wait
  • CLI:openclaw agent

第一个坑就藏在这儿。agent 这个 RPC 会校验参数、解析会话(sessionKey / sessionId)、持久化会话元数据,然后立刻返回 { runId, acceptedAt }。返回成功只代表「这次运行被受理了」,不代表模型跑完、更不代表回复生成了。真正等结果的是 agent.wait(内部是 waitForAgentRun),它在某个 runId 上等 lifecycle 的 end 或 error 事件,返回 { status: ok|error|timeout, startedAt, endedAt, error? }

还有一条语义值得记住:agent.wait 超时只是「不等了」,并不会停掉底层那次运行。套脚本的人看到 wait 返回 timeout 就当任务死了、重发一遍——原来那次还在跑,你制造的是两次运行。

一次运行被拆成的五步

官方给的运行序列按职责重排成表,方便对号入座:

环节干什么出问题的表现
agent RPC校验参数、解析会话、持久化会话元数据,返回 runId参数/会话解析错误
agentCommand解析模型与 thinking/verbose/trace 默认值、加载技能快照、调用 runEmbeddedAgent;内嵌循环没发生命周期事件时补发 end/error 兜底模型解析、技能加载类
runEmbeddedAgent通过「每会话队列」和「全局队列」串行化运行、解析模型与鉴权配置、构建会话、订阅运行时事件、流式输出助手与工具增量、执行运行超时(到期即中止),返回负载与用量元数据排队等待、超时中止
subscribeEmbeddedAgentSession把运行时事件桥接到 agent 流:工具进 tool,助手增量进 assistant,生命周期进 lifecyclephase: "start" | "end" | "error"流式没数据、事件不齐
agent.wait等 lifecycle end/error 并返回状态只影响「等」,不影响「跑」

runEmbeddedAgent 还有个 Codex 特例:对 Codex app-server 类型的回合,一次已受理的回合若在终止事件之前停止产出 app-server 进度,它也会被中止。

为什么同一个会话不会并行跑:队列 + 写入者认领

运行是按会话键(session lane)串行的,还可以再走一条全局通道,官方给的理由是防止工具和会话竞态。消息渠道会选一个队列模式(steer / followup / collect / interrupt)喂给这套通道系统。

比串行更硬的一道保险是写入者认领(writer claim):一次被准入的运行在开始流式输出之前记录一个持久化的 activeWriterRunId 认领;之后每次转录追加或重写都要带 expectedWriterRunId;同步提交事务校验它是否仍等于当前活跃认领。结果是——一次被取代的运行,没法把过期的转录数据提交进去。 后续的重写、压缩、截断用的都是同一道事务内的栅栏。

再往下还有两层:SQLite 写入队列给每个 agent 的变更排序;Gateway 状态目录锁防止另一个 Gateway 或 openclaw agent --local 进程同时占用同一状态目录。多网关部署靠的就是这把锁。网关本身的层次划分可以对照读 OpenClaw 整体架构

跑之前先准备什么:工作区、引导文件、技能

模型还没被调用之前,这几件事已经做完:工作区被解析并创建(沙箱运行可能重定向到沙箱工作区根目录);技能被加载或从快照复用,并注入环境变量和提示词;引导/上下文文件被注入系统提示词;会话转录目标和写入者认领在流式开始前就准备好。

工作区这块 concepts/agent 页说得更细:每个 agent 用单一一个工作区目录agents.defaults.workspace,或按 agent 配 agents.entries.*.workspace)作为工具和上下文的唯一工作目录(cwd)。启用 agents.defaults.sandbox 后,非主会话可以用 agents.defaults.sandbox.workspaceRoot 下的每会话工作区覆盖它。

工作区里 OpenClaw 期待存在这几个用户可编辑的文件:

文件用途
AGENTS.md操作说明 +「记忆」
SOUL.md人设、边界、语气
IDENTITY.mdAgent 名字/气质/emoji
USER.md用户档案与称呼偏好
BOOTSTRAP.md一次性的首次运行仪式(完成后删除)
MEMORY.md根级长期记忆文件,存在才注入

注入规则有几条容易忽略:新会话的第一个回合才把这些文件注入系统提示词的 Project Context;MEMORY.md 只有存在于工作区根目录时才注入;空文件跳过;大文件被修剪截断并留标记(想看全文得让它去读文件);除 MEMORY.md 外缺失的文件注入一行「缺失文件」标记,openclaw setup 会为它创建安全的默认模板。

BOOTSTRAP.md 只在全新工作区(没有其它引导文件)时创建。挂起期间它被留在 Project Context 里,并在系统提示词里加引导指引,而不是把内容抄进用户消息;仪式做完删掉它,之后重启也不会重建。

一条运维上很重要的:工作区被观察过之后,设置状态和认证信息存在共享 SQLite 库 ~/.openclaw/state/openclaw.sqlite 里。近期认证过的工作区若消失或被清空,启动时会拒绝静默重新播种 BOOTSTRAP.md——得恢复工作区,或做一次完整 onboard 重置把两边一起清掉。老版本的工作区 JSON 和 .attested 边车文件运行时已不读,用 openclaw doctor --fix 导入。

工作区如果是预先播好种的,可以整个关掉引导文件创建:

{ agents: { defaults: { skipBootstrap: true } } }

提示词装配这一步,系统提示词由基础提示词、技能提示词、引导上下文和按运行的覆盖项拼成,并执行模型专属限制和压缩预留 token——长会话牵扯上下文压缩就是从这儿来的,详见 上下文压缩与会话修剪

钩子挂在循环的哪几个位置

OpenClaw 有两套钩子。内部钩子(Gateway hooks)是事件驱动脚本,管命令和生命周期事件:agent:bootstrap 在构建引导文件、系统提示词定稿之前运行,用来增删引导上下文文件;另有 /new/reset/stop 这类命令钩子。

插件钩子是跑在 agent 循环或网关流水线内部的扩展点,官方列了这些:before_model_resolve(会话前、无 messages,确定性地覆盖 provider/model)、before_prompt_build(会话加载后带 messages,可注入 prependContextsystemPromptprependSystemContextappendSystemContext,支持的运行时上还能用 toolsAllow 收窄本回合工具面)、before_agent_reply(内联动作后、LLM 调用前,插件可接管这一回合并返回合成回复或静默)、agent_endbefore_compaction / after_compactionbefore_tool_call / after_tool_callbefore_installtool_result_persistmessage_received / message_sending / message_sentsession_start / session_endgateway_start / gateway_stop

三条判定规则最好背下来,写错了不报错、只是不生效:before_tool_call{ block: true } 是终止性的、会停掉更低优先级的处理器,而 { block: false } 是空操作、不会清掉之前的 blockbefore_install 语义相同,但官方明确说运营者自己的安装放行/告警/拦截决策该用 security.installPolicy,因为只有它才覆盖 CLI 安装和更新路径;message_sending{ cancel: true } 终止、{ cancel: false } 空操作且不清除之前的 cancel。配置和示例接着看 hooks 怎么用

流式、工具与回复

助手增量以 assistant 事件从运行时流出;块式流可在 text_endmessage_end 上发出部分回复;推理内容可以走单独的流,也可以走块回复。块式流默认是关的agents.defaults.blockStreamingDefault: "off"),边界由 agents.defaults.blockStreamingBreak 调(默认 text_end),软分块由 agents.defaults.blockStreamingChunk 控制(默认 800–1200 字符,优先段落断点,其次换行,句子最后),agents.defaults.blockStreamingCoalesce 用空闲合并减少单行刷屏。非 Telegram 渠道要显式打开 *.streaming.block.enabled: true;QQ Bot 反过来,除非 channels.qqbot.streaming.mode 设成 "off",否则默认流块回复。

工具这边:start/update/end 事件走 tool 流;工具结果在记录和发出前按体积和图片负载做净化;消息类工具的发送会被跟踪,用来抑制重复的助手确认。

最终回复由助手文本(外加可选推理内容)、内联工具摘要(verbose 且被允许时)、模型出错时的助手错误文本拼成,再加三条过滤:精确的静默令牌 NO_REPLY 从外发负载里过滤掉;消息工具的重复项从最终负载列表里移除;如果最后没有可渲染负载而又有工具报错,会发一个兜底的工具错误回复,除非某个消息工具已经发过用户可见的回复了——「回了两遍」和「什么都没回」,答案基本都在这一节。

插话怎么插进正在跑的运行

中途到达的入站提示词默认会被导入当前运行(steer)。运行时在两个检查点上查有没有 steer:未启动的工具调用启动前,以及下一次模型调用前。已经在跑的工具继续跑完;未启动的顺序调用被跳过;并行调用在这批越过启动检查点之后继续。被跳过的调用会收到合成的配对结果,然后模型才看到这次插话。

/queue steer 是活跃运行的默认行为,/queue followup/queue collect 让消息等下一回合,/queue interrupt 直接中止当前运行。边界行为展开在 队列与插话

超时不是一个数字,是一组

这是排查「卡住」时最该先看的表。官方给的默认值如下:

超时项默认值说明
agent.wait30s只管等,timeoutMs 参数可覆盖;不会停掉底层运行
Agent 运行时 agents.defaults.timeoutSeconds172800s(48 小时)runEmbeddedAgent 的中止定时器执行;设 0 为不限预算,但模型流活性看门狗仍生效
CLI 后端无输出看门狗按每次全新/恢复的 CLI 运行计算独立于 agent 运行时,由注册的后端插件拥有
Cron 隔离的 agent 回合由 cron 拥有调度器执行开始时启动自己的计时器,到期中止,有界清理后记录超时,避免过期子会话卡住通道
模型空闲超时云端 120s;自托管 300s空闲窗口内没有响应分片就中止模型请求
提供方 HTTP 请求超时models.providers.<id>.timeoutSeconds覆盖连接、响应头、响应体、SDK 请求超时、受保护 fetch 的中止处理及该提供方的模型流空闲看门狗

两条官方点明的取舍:跑慢的本地/自托管提供方(文档举的例子是 Ollama)应先调 models.providers.<id>.timeoutSeconds,而不是先抬整个 agent 运行时超时,但模型请求确实要跑更久时,运行时超时也得跟着不低于它;提供方级空闲看门狗始终被更低的有限值兜住agents.defaults.timeoutSeconds 或运行专属超时更小时以小的为准。另外,带显式 cron 运行超时时,云端模型流的停滞上限压到 60s,好让配置的模型回退还来得及在外层 cron 截止前跑。

会话「卡住」时,三个诊断标签怎么读

开启诊断后,有一个内建的两分钟阈值,给长时间处于 processing 且没观察到回复、工具、状态、块或 ACP 进度的会话分类:

  • session.long_running:活跃的内嵌运行、模型调用、工具调用。自己拥有的静默模型调用保持这个状态直到中止阈值,免得慢速或非流式提供方被过早判成停滞。
  • session.stalled:有活跃工作但近期无进展。自己拥有的模型调用在到达中止阈值时切到这里;无主的陈旧模型/工具活动不会被藏成 long_running。
  • session.stuck:保留给可恢复的陈旧会话簿记,包括带无主陈旧模型/工具活动的空闲排队会话。

中止阈值至少 5 分钟,且是告警阈值的 3 倍。停滞的内嵌运行要到中止阈值之后才被中止排空——排队的工作能继续,又不会把只是慢的运行一刀切掉。

官方还列了「运行可能提前结束」的四处:agent 超时(中止)、AbortSignal(取消)、Gateway 断连或 RPC 超时、agent.wait 超时(只是不等了,不停运行)。

几条容易忽略的落点

  • 核心工具(读/执行/编辑/写入及相关系统工具)始终可用,但受工具策略约束。apply_patch 对 OpenAI 模型默认开启,由 tools.exec.applyPatchenabledworkspaceOnlyallowModels 管控。
  • AGENTS.md 里的 ## Tools 小节不决定哪些工具存在,它只是你想怎么用它们的指引,这条常被误解。
  • 技能加载优先级从高到低:<workspace>/skills<workspace>/.agents/skills~/.agents/skills~/.openclaw/skills、内置、skills.load.extraDirs
  • 会话行存在 ~/.openclaw/agents/<agentId>/agent/openclaw-agent.sqlite。同级 sessions/ 下的转录 JSONL 只是迁移输入、归档和支持材料。
  • 模型引用按第一个 / 拆分。模型 ID 自身带 / 的(OpenRouter 那种)必须带提供方前缀,例如 openrouter/moonshotai/kimi-k2

另外,Gateway 把生命周期和工具的开始/终止事件投影进一个有界的、只含元数据的审计账本,只记来源和结果码,不复制提示词、消息、工具参数、结果或原始错误。想靠它反查「模型当时说了什么」是查不到的。

什么时候它帮不上你

上面全部内容来自 OpenClaw 官方文档的 concepts/agent-loopconcepts/agent 两页,我们没有安装运行过,不涉及实测耗时、资源占用或界面行为,文档没写的也不做推断。

有几处是文档留白:CLI 后端无输出看门狗的具体计算方式,官方只说「按每次全新/恢复的 CLI 运行计算」,公式未说明;压缩预留 token 的数值和各模型专属限制这页没给;每会话队列与全局通道的容量上限,官方文档未说明;各类钩子的完整 API,这页只列挂载点。

把这条链路记住的收益很朴素:下次「没回复」,先看 lifecycle 有没有 end/error 而不是盲目重发;「回了两遍」,先看是不是消息工具已发过一次、兜底回复又补了一次;「一直转」,先看是哪一级超时该管却没管到。

延伸阅读


本文依据 OpenClaw 官方仓库(github.com/openclaw/openclawdocs/ 下的官方文档整理,核对日 2026-08-17。 我们没有安装或运行过 OpenClaw,因此不涉及界面外观、操作手感与实测耗时的任何描述; 文中的默认值、命令与配置项均为文档口径,不构成对实际运行结果的保证。 该项目迭代很快,请以仓库最新内容为准。接入即时通讯平台前,请自行确认所在平台的规则与合规要求。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。