OpenClaw 的核心工作,是把来自不同消息平台的输入变成一次可控的 Agent 执行任务。
用户可能在 Telegram 里发一句话,也可能在 Discord 群里 @Bot,还可能通过 Slack 上传附件。对外看,这些入口协议完全不同;对内看,OpenClaw 会把它们统一成同一种消息对象,再交给网关、队列、Agent、工具执行器和存储层处理。
端到端链路可以概括为:
flowchart LR
A[用户在消息平台发送消息] --> B[Channel Adapter<br/>协议适配]
B --> C[FinalizedMsgContext<br/>标准化消息]
C --> D[Gateway<br/>去重 / 拦截 / 路由]
D --> E[SessionKey<br/>会话隔离]
E --> F[Lane + Queue<br/>并发控制]
F --> G[Prompt Builder<br/>上下文组装]
G --> H[Agent Loop<br/>ReAct 执行循环]
H --> I{需要工具?}
I -- 是 --> J[Tool Execution<br/>权限校验 / 参数校验 / 执行]
J --> H
I -- 否 --> K[Outbound Formatter<br/>回复格式化]
K --> L[Channel Adapter<br/>发送到平台]
H --> M[Data Layer<br/>会话持久化 / 记忆更新]
这条链路里最重要的不是模型调用本身,而是模型调用前后的工程治理:消息要能去重,会话要能隔离,同一会话要串行,工具调用要受控,回复还要按不同平台的限制做格式转换。
1. OpenClaw 的五层架构
OpenClaw 采用分层设计,每一层只处理自己的问题。这样做的好处是,Telegram、Discord、Slack 这类渠道可以不断增加,模型服务提供方也可以替换,但中间的会话、队列、记忆和工具调用机制不用跟着重写。
| 层级 | 名称 | 核心职责 | 典型实现 |
|---|---|---|---|
| 第 1 层 | 交互层 Channels | 接入 Telegram、Discord、Slack 等消息渠道,做协议适配 | Node.js 插件 |
| 第 2 层 | 网关层 Gateway | 消息路由、排队、调度、鉴权、会话键生成 | 常驻 Node.js 进程 |
| 第 3 层 | 智能体层 Agent | 组装上下文、管理会话、执行 ReAct 循环 | Agent Runner |
| 第 4 层 | 执行层 Execution | 本地工具、远端节点、技能脚本执行 | 沙箱与工具策略 |
| 第 5 层 | 数据层 Data | 保存会话、配置、日志和记忆 | SQLite + Markdown |
OpenClaw 的几个设计取向会贯穿后面的消息链路:
| 设计取向 | 含义 | 对消息链路的影响 |
|---|---|---|
| 本地优先 Local-First | 会话、记忆、配置尽量保存在本地文件中 | 便于审计、备份和迁移 |
| 消息应用即界面 | 不强制用户打开专门的 Web UI,而是接入常用聊天工具 | Channel 层必须能屏蔽不同平台协议差异 |
| 模型无关 Model-Agnostic | 把模型当成可替换的 Provider | Agent 层不绑定单一模型厂商 |
| 插件化 | Channel、Skill、Provider 都可以扩展 | 核心链路稳定,外围能力可替换 |
2. 入站消息:从平台协议变成内部消息对象
用户输入通常从某个 Channel 进来,例如 Telegram Bot、Discord Bot 或 Slack App。不同平台的事件结构差异很大:字段名不同、附件格式不同、群聊标识不同、回复线程机制也不同。
OpenClaw 不会让后面的 Agent 直接处理这些平台原始消息,而是在 Channel Adapter 里做一次标准化。
flowchart TB
A[Telegram / Discord / Slack 原始事件] --> B[身份验证<br/>Bot Token / Pairing]
B --> C[入站解析<br/>文本 / 图片 / 附件]
C --> D[访问控制<br/>白名单 / 群聊 @mention]
D --> E[标准化]
E --> F[FinalizedMsgContext]
标准化后的对象大致长这样:
const msg: FinalizedMsgContext = {
channel: "telegram",
accountId: "bot123456",
senderId: "user789",
groupId: null,
content: "帮我查一下今天的服务器状态",
rawMessage: {
// 平台原始消息,保留用于追踪和特殊处理
},
attachments: []
}
这一步的关键是把差异收敛到边界层。只要进入 OpenClaw 内部,后续网关、会话、Agent 和工具执行器面对的都是 FinalizedMsgContext,不需要知道消息最初来自哪个平台。
相关实现通常位于:
src/channels/
3. 网关前置治理:去重、拦截、路由
消息进入 Gateway 后,不会立刻交给模型。网关要先解决三个工程问题:
- 这条消息是不是重复推送?
- 这条消息是不是控制命令?
- 这条消息应该交给哪个 Agent?
3.1 去重:用幂等键挡住重复执行
Webhook 重试是消息系统里很常见的问题。平台把消息推给 OpenClaw 后,如果没有及时拿到成功响应,可能会重新推送一次。同一条消息如果执行两遍,轻则重复回复,重则重复调用工具,例如重复执行部署、重复写文件。
OpenClaw 使用幂等键解决这个问题。
幂等键 = channel:accountId:sender:session:messageId
示例:
telegram:bot123456:user789:main:msg_456789
处理流程如下:
flowchart LR
A[收到消息] --> B[生成幂等键]
B --> C{缓存中存在?}
C -- 是 --> D[丢弃或忽略重复消息]
C -- 否 --> E[写入缓存]
E --> F[继续处理]
幂等键会在缓存里保留一段时间,默认窗口可以按 20 分钟理解。这个时间窗口足够覆盖大多数平台的重试周期,同时不会让缓存无限增长。
相关实现位置:
src/channels/inbound-debounce-policy.ts
配置项:
messages.inbound.debounceMs
3.2 拦截:控制命令不进入 Agent 推理
并不是所有输入都应该交给大语言模型。像 /stop 这类命令,本质上是系统控制指令,应该由网关直接处理。
flowchart LR
A[用户输入 /stop] --> B[控制命令识别]
B --> C[触发停止逻辑]
C --> D[跳过 Agent 执行]
这样做有两个原因:
- 控制命令需要确定性行为,不适合交给模型“理解”。
- 停止任务、切换会话、清理状态这类动作通常要尽快执行,绕过 Agent 可以减少延迟。
3.3 路由:把消息交给正确的 Agent
一个 OpenClaw 实例可能同时服务多个渠道、多个账号、多个群组和多个用户。Gateway 需要根据消息来源决定使用哪个 Agent。
路由规则通常按精确度从高到低匹配:
| 优先级 | 路由条件 | 说明 |
|---|---|---|
| 1 | channel + account + user/group | 精确到某个用户或群组 |
| 2 | server + role | 按服务器和角色匹配 |
| 3 | server | 按服务器匹配 |
| 4 | account | 按账号匹配 |
| 5 | channel | 按渠道匹配 |
| 6 | default agent | 没有命中时使用默认 Agent |
这种优先级设计可以同时满足“全局默认助手”和“特定群组专属助手”两种需求。例如,一个默认 Agent 负责普通问答,而某个运维群可以绑定到拥有服务器检查技能的 Agent。
相关实现位置:
src/gateway/server-session-key.ts
核心函数:
resolveAgentRoute()
4. SessionKey:会话隔离和并发控制的基础
路由决定“谁来处理”,SessionKey 决定“这次对话属于哪个会话”。
SessionKey 通常会包含 Agent、渠道、会话类型和用户或群组标识。
私聊:
assistant:telegram:direct:user789
群聊:
support:telegram:group:-100123456789
它承担两个职责:
| 职责 | 作用 |
|---|---|
| 会话隔离 | 不同 SessionKey 的历史消息、上下文和记忆互不混淆 |
| 并发控制 | 相同 SessionKey 的消息串行执行,避免上下文错乱 |
如果同一个用户连续发两句:
第 1 条:帮我查一下服务器状态
第 2 条:顺便看看磁盘空间
这两条消息如果同时进入 Agent,第二条可能在第一条还没写入会话历史时就开始组装上下文,导致模型看不到完整上下文。SessionKey 让 OpenClaw 能把同一会话里的消息排成有序队列。
5. Lane + Queue:同一会话串行,不同会话并行
OpenClaw 使用 Lane 和 Queue 处理并发。可以把 Lane 理解成“车道”,Queue 理解成“排队系统”。
flowchart TB
A[消息进入 Gateway] --> B[全局 Lane<br/>main / cron / subagent]
B --> C[Session Lane<br/>按 SessionKey 分组]
C --> D[BullMQ 队列]
D --> E[Agent Runner 执行]
并发规则很直接:
| 情况 | 执行方式 | 原因 |
|---|---|---|
| 相同 SessionKey | 串行执行 | 保证同一会话的上下文顺序 |
| 不同 SessionKey | 可以并行 | 不同会话之间没有上下文依赖 |
| 不同全局 Lane | 按 Lane 策略限制 | 防止主任务、定时任务、子 Agent 互相挤占资源 |
BullMQ 是基于 Redis 的 Node.js 队列库,适合处理任务排队、重试和并发限制。OpenClaw 把消息包装成任务后放入队列,由 Lane 策略控制什么时候执行。
相关实现位置:
src/gateway/server-lanes.ts
新消息到来时的队列策略
当某个会话已有任务正在运行,新消息不一定只能等待。OpenClaw 支持多种处理模式:
| 模式 | 行为 | 适合场景 |
|---|---|---|
interrupt | 中断当前任务,立刻处理新消息 | 用户发出紧急修正或停止类需求 |
steer | 把新消息注入当前任务上下文 | 用户补充参数,例如“改成查昨天” |
followup | 当前任务完成后,把收集到的消息一起处理 | 连续补充信息,适合批量归并 |
collect | 只收集到队列,等待后续轮次处理 | 离线消息或低优先级消息 |
这些模式解决的是“人在聊天时会连续输入”的问题。真实用户不会等 Agent 完全回复后才补充信息,因此队列层必须支持中断、转向和合并。
6. 上下文组装:模型看到的不是一句话
大语言模型(LLM,Large Language Model)本身没有 OpenClaw 的系统状态。它能做什么、该扮演什么角色、有哪些工具、过去聊过什么,都要在调用模型前拼进上下文。
OpenClaw 的 Prompt Builder 会按固定顺序组装上下文:
flowchart TB
A[系统提示词 System Prompt] --> E[最终 Prompt]
B[技能提示 Skills Prompt] --> E
C[对话历史 Conversation History] --> E
D[当前消息 Current Message] --> E
A1[AGENTS.md<br/>职责声明] --> A
A2[SOUL.md<br/>人格设定] --> A
A3[TOOLS.md<br/>工具白名单] --> A
B1[已安装技能描述] --> B
B2[技能能力声明] --> B
C1[sessions/*.sqlite<br/>历史消息] --> C
C2[MEMORY.md<br/>长期记忆] --> C
C3[memory/YYYY-MM-DD.md<br/>每日日志] --> C
组装顺序通常是:
| 顺序 | 上下文部分 | 作用 |
|---|---|---|
| 1 | 系统提示词 | 定义 Agent 的职责、边界和工具规则 |
| 2 | 技能提示 | 告诉模型当前可用技能以及调用方式 |
| 3 | 对话历史与记忆 | 提供会话背景、长期偏好和历史事实 |
| 4 | 当前消息 | 用户刚刚发送的输入 |
相关实现位置:
src/gateway/agent-prompt.ts
核心函数:
buildPrompt()
三级记忆系统
OpenClaw 的记忆可以分成三类:
| 记忆类型 | 存储位置 | 用途 |
|---|---|---|
| 长期记忆 Long-term | MEMORY.md | 保存长期有效的信息,例如用户偏好、项目背景 |
| 近端记忆 Proximal | sessions/*.sqlite | 保存会话历史,支持回溯和检索 |
| 短期记忆 Short-term | 当前 Prompt 上下文 | 支撑本轮模型推理 |
记忆注入不是简单把所有历史都塞给模型。会话变长后,Token 成本会快速上升,模型上下文窗口也会被耗尽。更合理的做法是通过 RAG(Retrieval-Augmented Generation,检索增强生成)取回相关片段,再把必要信息注入 Prompt。
flowchart TB
A[用户当前问题] --> B[语义检索]
B --> C[长期记忆 MEMORY.md]
B --> D[会话历史 SQLite]
C --> E[相关记忆片段]
D --> E
E --> F[注入 Prompt]
F --> G[模型调用]
7. Agent 执行循环:ReAct 让模型边想边做
OpenClaw 的 Agent 执行采用 ReAct 模式。ReAct 是 Reasoning and Acting 的缩写,意思是模型不只是一次性生成答案,而是在“推理”和“行动”之间循环。
一次典型循环如下:
flowchart LR
A[接收输入] --> B[组装上下文]
B --> C[调用模型]
C --> D{模型判断}
D -- 直接回答 --> E[生成回复]
D -- 需要工具 --> F[发起工具调用]
F --> G[执行工具]
G --> H[工具结果写回上下文]
H --> C
C --> I{达到最大步数?}
I -- 是 --> E
举个例子,用户输入:
帮我查一下今天的服务器状态
Agent 可能会经历这样的内部过程:
第 1 步:判断需要检查服务器指标
第 2 步:调用 check_server_status 工具
第 3 步:拿到 CPU、内存、磁盘结果
第 4 步:判断磁盘使用率偏高,需要进一步检查
第 5 步:调用 check_disk_usage 工具
第 6 步:整理结果并回复用户
为了避免 Agent 无限循环,OpenClaw 会限制最大执行步数,默认可以按 10 步理解。超过限制后,系统应停止继续调用工具,返回当前可用结果或错误信息。
相关实现位置:
src/agents/pi-embedded-runner.ts
8. 工具调用:权限、参数和执行边界都要检查
Agent 真正能改变外部世界的地方在工具调用。比如查看磁盘、访问浏览器、调用远端节点、运行技能脚本,这些动作都比普通文本回复更危险,所以不能只依赖模型输出。
工具调用链路可以拆成五段:
flowchart TB
A[模型决定调用工具] --> B[解析工具调用]
B --> C[权限验证 Policy Check]
C --> D[参数验证 Param Validation]
D --> E[执行 Execution]
E --> F[结果返回 Result Return]
F --> G[写回上下文,继续 Agent 循环]
模型输出的工具调用大致会被解析成结构化对象:
{
"tool_name": "check_disk_usage",
"parameters": {
"path": "/"
}
}
执行前要检查三类问题:
| 检查项 | 需要回答的问题 |
|---|---|
| 工具白名单 | 当前 Agent 是否允许调用这个工具? |
| 用户权限 | 当前用户或会话是否有执行权限? |
| 审批策略 | 这个工具是否需要人工审批? |
| 参数类型 | 参数类型是否符合工具定义? |
| 参数范围 | 参数值是否在允许范围内? |
执行方式通常分为三种:
| 执行方式 | 示例 | 说明 |
|---|---|---|
| 本地执行 | system.run、browser.action | 在本机或本地沙箱中执行 |
| 远端执行 | node.invoke | 调用远端节点执行任务 |
| 技能执行 | Skill 脚本 | 调用用户、插件或系统提供的技能 |
工具结果不会直接原样发给用户,而是先注入上下文,让模型继续推理和组织语言。这样可以让模型把多个工具结果整合成一段更适合聊天平台展示的回复。
9. 安全边界:入口、执行、审计三层都不能省
OpenClaw 的风险点主要有两个:谁能发消息,以及 Agent 能执行什么。安全边界也围绕这两个问题展开。
flowchart TB
A[入口边界] --> B[执行边界]
B --> C[审计追踪]
A1[allowFrom 白名单] --> A
A2[Token 验证] --> A
A3[Pairing 配对机制] --> A
B1[Sandbox 沙箱隔离] --> B
B2[Tool Policy 工具策略] --> B
B3[Approvals 执行审批] --> B
C1[runId 链路追踪] --> C
C2[日志记录] --> C
C3[doctor 诊断工具] --> C
更完整的安全防线可以整理成四层:
| 层级 | 防护措施 | 解决的问题 |
|---|---|---|
| 网络层 | 默认绑定 127.0.0.1、TLS 1.3、IP 白名单 | 限制服务暴露面 |
| 认证层 | Token 验证、Pairing 配对、设备 Token 轮换 | 确认调用方身份 |
| 授权层 | allowFrom、Tool Policy、执行审批 | 限制用户和 Agent 的可执行能力 |
| 审计层 | 日志、runId、doctor 诊断工具 | 出问题时能追踪完整链路 |
TLS 是 Transport Layer Security 的缩写,即传输层安全协议。对于 WebSocket 连接,TLS 可以防止中间人窃听和篡改。
10. 回复处理:流式输出、分块和平台格式化
Agent 生成回复后,还不能直接把文本丢给消息平台。不同平台对消息长度、Markdown 方言、附件上传、回复线程都有自己的限制。
OpenClaw 的出站处理分为两步:
flowchart LR
A[模型输出 Token 流] --> B[流式分块]
B --> C[渠道格式化]
C --> D[平台 API 发送]
10.1 流式输出
如果开启流式输出,模型生成 Token 时,OpenClaw 会边生成边推送到渠道。这样用户不用等完整答案生成完才看到响应。
相关配置项:
agents.defaults.blockStreaming
流式分块需要处理几个细节:
| 配置或策略 | 含义 |
|---|---|
minChars | 小于这个字符数时先不发送,避免碎片太多 |
maxChars | 达到最大字符数后必须拆分 |
breakPreference | 优先在文本结束或消息边界处拆分 |
humanDelay | 模拟输入延迟,避免输出过于机械 |
| 代码块保护 | 避免把 Markdown 代码围栏拆坏 |
代码块保护很重要。比如下面这段 Markdown:
```bash
df -h
```
如果中途把三个反引号拆开,Telegram、Slack 这类平台可能无法正确渲染,甚至把后续内容都当成代码块。
相关实现位置:
src/gateway/server-chat.ts
10.2 出站格式化
统一回复对象要转换成平台兼容格式:
| OpenClaw 内部格式 | 平台侧处理 |
|---|---|
| Markdown | 转成 Telegram MarkdownV2 或 Slack mrkdwn |
| 图片 / 附件 | 上传到平台,换取平台 URL 或文件 ID |
| 回复线程 | 设置 reply_to_message_id 或 thread timestamp |
| 长文本 | 按平台长度限制拆成多条消息 |
Channel Adapter 在入站时负责“把平台消息变成统一对象”,出站时则反过来,负责“把统一回复变成平台消息”。
11. 会话持久化与记忆更新
一次对话完成后,OpenClaw 会把会话状态写回数据层。这个动作不能省,因为下一轮上下文组装要依赖历史消息和记忆。
flowchart TB
A[一轮对话完成] --> B[追加会话历史]
B --> C[sessions/*.sqlite]
B --> D[更新记忆索引]
D --> E[RAG Embedding]
B --> F{有重要信息?}
F -- 是 --> G[同步到 MEMORY.md]
F -- 否 --> H[仅保留会话历史]
C --> I{上下文过长?}
I -- 是 --> J[Compaction 上下文压缩]
I -- 否 --> K[等待下一轮消息]
持久化包含几类数据:
| 数据 | 存储方式 | 用途 |
|---|---|---|
| 当前轮用户消息 | SQLite 会话库 | 还原对话上下文 |
| Agent 回复 | SQLite 会话库 | 支撑后续追问 |
| 工具调用记录 | 日志或会话记录 | 审计和调试 |
| 长期事实 | Markdown 记忆文件 | 跨会话复用 |
| 向量索引 | Embedding 索引 | 语义检索 |
当会话历史超过模型上下文窗口时,OpenClaw 可以进行 Compaction,也就是上下文压缩。压缩不是简单删除旧消息,而是保留关键信息,把冗余对话压成摘要,后续再按需检索。
12. 端到端流程图
OpenClaw 的主链路可以压缩到一张端到端流程图中:
图里的关键结构是三段:入口侧负责把渠道消息标准化并完成治理,中间侧通过 SessionKey、Lane 和 Queue 保证会话顺序,执行侧由 Agent 循环、工具调用和记忆持久化组成。理解这三段之后,排查问题时就能快速定位:消息没进来要查 Channel,进来但没执行要查 Gateway 和 Queue,执行结果不对要查 Prompt、工具策略和记忆注入。
13. WebSocket 协议:Gateway 与客户端如何通信
Gateway 使用自定义 WebSocket 协议。WebSocket 适合这类场景,因为 Agent 执行过程中会不断产生事件:文本片段、工具调用、工具结果、错误信息、最终回复。
协议消息可以分成三类:
| 类型 | 用途 | 示例 |
|---|---|---|
req/res | 请求-响应 | sessions.list、config.get |
event | 单向事件推送 | agent.text、agent.tool_call |
stream | 流式数据 | 实时文本输出 |
相关实现位置:
src/gateway/protocol/
一个简化的时序如下:
sequenceDiagram
participant Client as 客户端
participant Gateway as Gateway
participant Agent as Agent Runner
participant Tool as Tool Executor
Client->>Gateway: req: sessions.run
Gateway->>Agent: 启动 Agent 执行
Agent-->>Gateway: event: agent.text
Gateway-->>Client: 推送文本片段
Agent->>Tool: tool_call
Tool-->>Agent: tool_result
Agent-->>Gateway: event: agent.done
Gateway-->>Client: res: done
这种设计让客户端不必轮询任务状态。Agent 一边执行,Gateway 一边把事件推给客户端或 Channel。
14. 模型路由与故障转移
模型无关不只是“能配置多个 API Key”。更关键的是,OpenClaw 要能根据任务类型选择不同 Provider,并在失败时切换备用 Provider。
Provider 是模型服务提供方,例如 OpenAI、Anthropic、DeepSeek 等。配置可以抽象成:
{
"providers": [
{
"name": "anthropic",
"model": "claude-sonnet"
},
{
"name": "openai",
"model": "gpt-4"
},
{
"name": "deepseek",
"model": "deepseek-chat"
}
]
}
模型路由可以按任务复杂度拆分:
| 任务类型 | 可选模型策略 |
|---|---|
| 主 Agent 编排 | 使用推理能力更强的模型 |
| 子任务总结 | 使用成本更低、速度更快的模型 |
| 简单分类 | 使用轻量模型 |
| Embedding 检索 | 使用专门的向量模型 |
故障转移通常包含三种动作:
flowchart TB
A[模型调用失败] --> B[指数退避重试]
B --> C{仍失败?}
C -- 否 --> D[返回结果]
C -- 是 --> E[切换备用 Provider]
E --> F{认证异常?}
F -- 是 --> G[轮换认证配置]
F -- 否 --> H[继续执行]
这样可以避免单个模型服务抖动导致整个 Agent 不可用。
15. Skills 技能系统
Skills 是 OpenClaw 扩展执行能力的方式。一个技能通常包含技能描述文件和执行脚本。
目录结构示例:
~/.openclaw/skills/my-skill/
├── SKILL.md
└── script.ts
SKILL.md 用来描述技能能力、使用方式和工具定义:
---
name: check_disk_usage
description: Check current disk usage of server
usage:
- "check disk space"
- "disk usage"
---
## Tools
### get_disk_usage
- Description: Returns output of `df -h`
- Command: `df -h`
技能优先级从高到低通常是:
flowchart LR
A[工作区技能] --> B[插件技能]
B --> C[用户技能]
C --> D[系统技能]
| 优先级 | 技能来源 | 特点 |
|---|---|---|
| 最高 | 工作区技能 | 针对当前项目定制 |
| 较高 | 插件技能 | 随插件安装 |
| 较低 | 用户技能 | 用户全局可用 |
| 最低 | 系统技能 | OpenClaw 默认能力 |
这种优先级让项目级能力可以覆盖通用能力。例如某个仓库里定义了专门的部署检查脚本,Agent 在这个工作区内就应该优先使用它,而不是使用泛化的系统技能。
16. Token 成本优化:少塞无关上下文,按任务分层用模型
Agent 系统最容易失控的成本来自 Token。对话历史、记忆、工具结果、系统提示词都会占用上下文。OpenClaw 的优化方向主要有三类:
| 优化项 | 做法 | 节省的来源 |
|---|---|---|
| 上下文压缩 | 长会话用摘要和 Compaction | 减少历史消息 Token |
| 模型分层 | 主任务用强模型,子任务用轻量模型 | 避免所有任务都走高价模型 |
| 记忆检索 | 用 RAG 取相关片段 | 避免全量注入历史记忆 |
在日均 2000 万 Token 的高频使用场景里,优化前后可以按下面的量级估算:
| 优化项 | 优化前 | 优化后 | 降低幅度 |
|---|---|---|---|
| 上下文压缩 | 不压缩 | RAG + 摘要 | 约 60% |
| 模型分层 | 全部使用高价模型 | 主/副模型分离 | 约 70% |
| 记忆检索 | 全量历史注入 | 语义检索相关片段 | 约 50% |
| 总成本 | 约 200 美元/月 | 约 20 美元/月 | 约 90% |
这些数字的核心含义不是固定成本,而是优化方向:上下文越长、任务越多,Prompt 构造和模型路由越重要。
17. 排查问题时按链路定位
OpenClaw 的消息流转是分层的,排查问题也应该沿链路定位。
| 现象 | 优先检查位置 |
|---|---|
| 用户发了消息但系统没反应 | Channel Adapter、Token、白名单、Webhook |
| 重复回复 | 幂等键、debounce 配置、平台重试 |
| 群聊里不响应 | @mention 过滤、群组 allowFrom |
| 回复上下文错乱 | SessionKey 生成规则、Session Lane |
| 后发消息插队 | Queue 模式、Lane 配置 |
| 工具没有执行 | Tool Policy、工具白名单、审批配置 |
| 工具结果错误 | 参数解析、执行脚本、沙箱环境 |
| 回复格式坏掉 | 出站 Markdown 转换、分块策略 |
| 成本异常升高 | Prompt 注入、记忆检索、模型路由 |
把问题映射到链路上的某一层,可以避免直接从模型开始猜。很多看似“模型不聪明”的问题,实际是上下文没组装好、会话键生成错了,或者工具结果没有正确写回 Agent 循环。
18. 把整套机制串起来
OpenClaw 的消息链路可以用一句话概括:Channel 负责适配外部世界,Gateway 负责把消息治理成有序任务,Agent 负责推理和调用工具,Execution 负责安全执行,Data 负责把结果沉淀成可复用上下文。
真正支撑这套系统稳定运行的,是几个关键机制的组合:
| 机制 | 解决的问题 |
|---|---|
FinalizedMsgContext | 屏蔽不同消息平台的协议差异 |
| 幂等键 | 防止 Webhook 重试导致重复执行 |
| Agent 路由 | 把不同用户、群组、渠道映射到正确 Agent |
| SessionKey | 隔离会话,并作为串行执行的依据 |
| Lane + Queue | 让同一会话有序,不同会话并行 |
| Prompt Builder | 把系统设定、技能、历史和当前消息组装给模型 |
| ReAct Loop | 让模型在推理和工具调用之间循环 |
| Tool Policy | 限制 Agent 可执行动作 |
| 流式分块 | 适配消息平台的长度和格式限制 |
| 记忆持久化 | 让下一轮对话继承必要上下文 |
如果要继续扩展,比较自然的方向包括 ACP(Agent Communication Protocol,智能体通信协议)支持多 Agent 协作、MCP(Model Context Protocol,模型上下文协议)统一外部上下文接入、更细粒度的 Context Engine 插件,以及面向 GPU(Graphics Processing Unit,图形处理器)资源的弹性调度。对现有链路来说,这些扩展都应该继续遵守同一个原则:入口标准化,中间可治理,执行有边界,状态可追踪。
