02 · 一条消息的旅程
拆解一条消息从渠道进入 OpenClaw 到 Agent 回复的完整链路,包括防抖、队列、上下文组装和返回。
——翔宇
要点速览
- 你发的一句话,到收到回复,中间经历 8 个生命阶段——比你以为的多得多
- AI 不是只收到你那句话——它实际收到 2-6 万 token(词元,文本计量单位)的巨大输入包,你的消息只占千分之一
- 防抖(Debounce)机制让用户分 3 条发的消息合并成 1 次处理,避免 Agent 做 3 次重复工作
- 队列模式决定了"Agent 正在干活时收到新消息怎么办"——4 种策略对应 4 种业务场景
- 流式传输不是一口气把长文发出来,而是按段落、换行、句子逐级切割,找最优断点
1. 先猜一下
你掏出手机,在 Telegram 或 Discord 里给 Agent 发了一句:
「帮我查一下服务器状态」
然后你看到 Agent 开始"思考",几秒后回复了一段详细报告。
问题来了——从你按下发送到收到回复,中间发生了几个步骤?
你可能猜 3 个:发出去 → AI 处理 → 回复回来。
实际答案是 8 个。而且其中好几个步骤,你永远不会在聊天界面上看到。
💬 说人话
你以为你在跟 AI 聊天。但实际上你的消息经过了接线员、分机表、工作台、资料柜……一整套流程,才到达 AI 面前。而 AI 看到的东西,比你发的那句话多了几百倍。
为什么理解这个旅程很重要?因为当 Agent 表现不对的时候——不回复、答非所问、回复重复——你只有理解了整条管道,才能定位到底是哪个环节出了问题。不懂管道的人只能说"AI 坏了",懂管道的人能说"Binding(绑定,消息路由规则)没匹配上"或者"Context(上下文,模型可见的全部内容)组装时 SOUL.md 被截断了"。
后者排查问题的速度是前者的 10 倍。
2. 消息的 8 个生命阶段
让我们从头开始,追踪这条消息的完整旅程。
2.1 阶段一:Channel(渠道,聊天平台接入层)标准化——翻译官
你的消息从手机 App 发出。但不同平台的消息格式完全不同。
Telegram 的消息长这样——有 chat_id、message_id、text、from.username 这些字段。Discord 的消息长另一个样——有 guild_id、channel_id、content、author.id。WhatsApp 又是另一套。
🎯 打个比方
想象三个人分别用中文、英文、日文给你写信。你得先把它们全翻译成中文,才能统一处理。Channel 层就是翻译官——不管消息从哪个 App 来,出来之后都变成统一的内部格式。
统一之后的消息,大概长这样(用表格表示,不是代码):
🔑 关键点
Channel 标准化是确定性的——不需要 AI 参与。它是纯粹的格式转换:Telegram 格式 → 内部格式,Discord 格式 → 内部格式。没有任何智能决策,只有规则映射。
2.2 阶段二:Gateway 路由——总机接电话
标准化后的消息到达 Gateway(网关,系统核心进程)。Gateway 跑在本地的 127.0.0.1:18789,是整个 OpenClaw 的中枢。
🎯 打个比方
Gateway 就是公司前台的总机。所有电话先到总机,总机再决定转给谁。它不处理任何业务,只做分发。
Gateway 在这一步做三件事:
📌 记住这点
消息去重是在 Gateway 层完成的。平台有时候会因为网络原因重复投递同一条消息(特别是 Telegram),Gateway 用短期缓存记住最近处理过的消息 ID,发现重复就直接丢弃,不会让 Agent 处理两次。
2.3 阶段三:Binding 匹配——查分机表
现在 Gateway 需要决定:这条消息给哪个 Agent?
OpenClaw 可以有很多 Agent——总经理、小红书部、微信部、研发部……10 个甚至更多。每个 Agent 绑定在不同的频道。Gateway 怎么知道这条消息属于谁?
答案是 Binding(绑定规则)。
🎯 打个比方
你打电话到一家公司。总机看了一下分机表:"拨 1 转销售,拨 2 转技术,拨 3 转人事。"Binding 就是这张分机表——它定义了"哪个频道的消息转给哪个 Agent"。
匹配规则可以基于多种条件:
🧠 底层逻辑
Binding 匹配是确定性的——不是 AI 在做决策,而是按规则表逐条匹配。第一条命中的规则决定目标 Agent。这保证了路由的可预测性:同一个频道的消息,永远会到达同一个 Agent,不会因为 AI "心情不好"而发错。
2.4 阶段四:Session(会话,一次连续对话)定位——找到你的对话桶
找到了目标 Agent,但同一个 Agent 可能同时和多个人聊天。你的对话上下文在哪个桶里?
这就是 Session 的作用。
Session Key 的格式类似:agent:main:discord-1477147694987481261-978954749326004254。看起来很长,但逻辑很简单——它是三个信息的拼接:
💬 说人话
Session 就是一个对话桶。你和总经理在 #总部频道的所有对话,都装在同一个桶里。换了一个频道,就是另一个桶。这样 Agent 不会把你在 A 频道聊的内容跟 B 频道混在一起。
2.5 阶段五:Context 组装——拼装巨大的输入包
这是整个旅程中最关键、也最反直觉的一步。
你以为 AI 收到的就是你那句"帮我查一下服务器状态"?
错误直觉
「我发了一句话,AI 就收到一句话。」
不。AI 收到的是一个巨大的输入包。你的那句话只是这个包里的最后一行。
让我们看看这个包里到底有什么:
总计:2 万到 6 万 token。你的消息占比:千分之一到万分之一。
🎯 打个比方
你给员工递了一张便利贴:"查下服务器"。但在员工真正看到这张便利贴之前,他已经读完了一本 50 页的员工手册、翻了一遍最近 3 天的工作日志、检查了一遍工具箱清单。你的便利贴是他今天读的第 51 页。
💡 划重点
这就是为什么 Context(上下文)管理如此重要——不是因为你话太多,而是因为系统本身就非常"话多"。你还没开口,AI 已经在处理 2 万 token 的前置信息了。
Context 组装的顺序
组装不是随意堆叠的,而是有严格的顺序——越重要的东西越先放:
🧠 底层逻辑
为什么身份文件比你的消息优先级高?因为 AI 必须先知道"我是谁、我能做什么、我不能做什么",才能正确理解你的指令。如果身份信息被挤掉了,Agent 可能会用错工具、违反安全规则、忘记自己的职责。
验证理解:Workspace 文件的加载差异
注意一个重要细节——不是所有 Session 都加载完整的 7 个 Workspace 文件。
为什么子 Agent 不加载 MEMORY?因为子 Agent 是临时工——它被创建出来执行一个特定任务,做完就销毁。它不需要知道过去发生了什么,只需要知道现在要干什么。
💡 划重点
如果你发现一个被 spawn 出来的子 Agent"不记得"某些东西,别以为它失忆了——它从来就没有加载记忆文件。这是设计如此,不是 Bug。省掉 MEMORY 和 HEARTBEAT 大约能节省 1,500 token 的上下文空间。
2.6 阶段六:Model API 调用——发给大脑
Context 组装完毕,这个巨大的输入包通过 API 发送给 LLM(大语言模型)(Claude、GPT 或其他模型)。
这一步其实是最简单的——就是一个 HTTP 请求。但它是整个链路中最贵的一步。每一个 token 都有成本,无论是按量付费还是订阅制,你都在为这 2-6 万 token 的输入买单。
模型处理完之后,返回一个响应。这个响应可能是:
📌 记住这点
NO_REPLY 是一个特殊标记——当 Agent 的输出包含这个标记时,Gateway 不会发送任何消息给用户。这在心跳检查(Heartbeat)场景下特别有用:Agent 巡检完发现一切正常,没必要打扰你,就输出 NO_REPLY(在心跳场景中叫 HEARTBEAT_OK)。
🔍 深入一步
NO_REPLY 的设计解决了一个微妙的问题:有些系统事件需要触发 Agent 思考,但不需要用户看到回复。比如心跳巡检——每 55 分钟系统自动给 Agent 发一条"检查一下"的指令。Agent 执行完检查,如果一切正常,用户不需要每小时收到一条"一切正常"的消息。NO_REPLY 让 Agent 做了事、但不说话。如果发现异常,Agent 正常回复告警信息,这时候就不会用 NO_REPLY。
2.7 阶段七:Tool 执行——Agent 用工具干活
如果模型返回的是工具调用请求,Agent 就开始"干活"了。
比如你说"查服务器状态",Agent 可能会:
这里有一个关键点——工具执行可能不止一轮。
Agent 调了一个工具,拿到结果,发现需要更多信息,于是再调一个工具,拿到新结果,再分析……这叫 Agent Loop(代理循环)。
🎯 打个比方
你让员工查服务器状态。他先看了一眼监控面板(第一轮工具调用),发现内存使用率偏高,于是又去查了一下哪个进程占内存最多(第二轮),发现是日志堆积导致的,于是又查了日志文件大小(第三轮)。最后给你一份完整报告。
每一轮工具调用的结果都会被加入 Context,然后再次发送给模型,让模型决定是继续调工具还是生成最终回复。
🔑 关键点
每一轮工具调用都消耗 token——工具的输出(比如一个 500 行的日志文件)会被完整塞进上下文。这就是为什么 OpenClaw 需要 Pruning(剪枝,裁剪旧工具结果)机制来裁剪过大的工具结果。
Agent Loop 的终止条件
Agent Loop 不会无限循环下去。它在以下任一条件满足时停止:
💬 说人话
Agent Loop 就像一个做研究的实习生——你让他查服务器状态,他先查了 CPU,发现偏高,又去查进程列表,发现某个进程吃内存,又去看日志……每一步都有道理,但你不能让他无限追查下去。到了一定轮次或空间快满时,必须停下来交报告。
工具调用中的错误处理
如果工具调用失败了怎么办?比如要运行的命令不存在、权限不够、超时了。
Agent 不会崩溃——它会把错误信息当作工具结果,放进上下文,然后让模型决定怎么办。模型可能选择换一个方法重试,也可能直接告诉你"这个命令无法执行"。
📌 记住这点
工具调用的错误也是信息。一个好的 Agent 不会被一次失败吓住——它会分析错误原因,尝试替代方案。这不是因为 Agent 被编程了"遇到错误就重试",而是因为 LLM 本身有推理能力,能从错误信息中推导出下一步应该做什么。
2.8 阶段八:回复分块——按规矩切割
Agent 终于生成了最终回复。但这个回复不能直接发出去。
为什么?因为每个平台对消息长度有限制:
如果 Agent 的回复是 8000 字符,在 Discord 上就必须拆成至少 4 条消息。
但拆在哪里?不能随便从第 2000 个字符处一刀切——可能正好切在一个表格中间、一个代码块里面、甚至一个字的中间。
💬 说人话
你写了一封 4 页的信要塞进信封,但每个信封最多装 1 页。你不能把一句话从中间撕开分装——得找段落结尾、句子结尾这种"天然断点"来分。
OpenClaw 的切割策略是逐级寻找最优断点:
从最优断点开始找。如果在长度限制内找到了段落结束,就从那里切;找不到就退一步找换行;再找不到就找句号;都没有?只好在空格处切;连空格都没有(比如一长串中文),就硬切。
⚡ 速记
断点优先级:段落 > 换行 > 句子 > 空格 > 硬断点。越靠前的断点,读者体验越好。
3. 全景图:一条消息的完整旅程
把 8 个阶段串起来,你的一句话经历了这些:
🧠 底层逻辑
注意一个反直觉的事实:8 个阶段中,只有第 6 和第 7 阶段需要 AI 参与。其余 6 个阶段都是确定性的——不需要任何智能决策,只需要规则和数据结构。这意味着,当你的消息"卡住"了,绝大多数情况下不是 AI 的问题,而是前面某个确定性阶段出了问题(网络、配置、规则匹配)。
3.1 时间分布:每个阶段花多久
好奇这 8 个阶段各自需要多少时间?来看一个典型场景的时间分布。
场景:你发了"帮我查一下服务器状态",Agent 调用了 2 次工具后回复。
总耗时:约 8-20 秒。其中 80% 的时间花在 LLM 推理上。
💡 划重点
当你觉得 Agent 回复慢的时候,瓶颈几乎永远在 Model API 调用(阶段六)。Gateway、Binding、Session 这些阶段加起来不超过 200 毫秒。所以优化响应速度的关键不在消息管道,而在选择更快的模型、减少不必要的工具调用轮次、控制 Context 大小。
3.2 如果某个阶段出了问题
理解正常流程很重要,理解异常更重要——当消息"失踪"了,你需要知道去哪里找问题。
⚡ 速记
没反应 → 查 Gateway。答非所问 → 查 Context。一直转圈 → 查 Model/Tool。回复两次 → 查去重。按阶段定位,不要瞎猜。
4. 错误直觉暴露:AI 到底收到了什么
这一节值得单独拿出来讲,因为它是理解 OpenClaw 的关键转折点。
错误直觉
「AI 收到的就是我发的那句话。我跟它聊了 10 轮,它就看到 10 轮对话。」
不是。
当你发了一句"帮我查服务器状态",AI 实际处理的输入是这样的(简化描述):
AI 看到的不是一句话,而是一本小册子的最后一行。
💡 划重点
这就解释了一个常见困惑:"为什么我只说了一句简单的话,Agent 的反应速度却时快时慢?"——因为速度取决于 AI 要处理的总 token 数,而不是你发了多少字。对话历史越长,处理越慢。
🔍 深入一步
System Prompt 也有模式之分。主 Agent 用
full模式(完整系统提示,约 10,000 token),子 Agent 用minimal模式(精简版,约 3,000 token)。这一个优化就省了 7,000 token——一条消息级别的成本差异。
5. 入站防抖:如果没有它会怎样
理解了消息的完整旅程,接下来看三个精巧的工程设计。它们解决的都是同一个问题——现实世界的用户行为,远比你以为的更混乱。
如果说 8 个阶段是"高速公路",那么接下来的防抖、队列和去重就是"交通规则"。没有交通规则的高速公路,再宽也会堵车。
5.1 问题:用户不是一次说完的
你有没有这种发消息的习惯?
10:30:01 帮我查一下
10:30:03 服务器的状态
10:30:05 主要看CPU和内存
三条消息,其实是一个意思。
如果没有防抖会怎样?
Gateway 收到第一条 → 立即组装 Context → 调用 LLM → Agent 开始处理"帮我查一下"(查什么?不知道)→ 2 秒后第二条来了 → 又组装一次 Context → 又调用一次 LLM → 再 2 秒后第三条 → 又来一次。
结果:
💬 说人话
想象你给秘书打电话,说了一个字"帮"就挂了。秘书立即开始行动——"帮什么?不知道,先做准备吧"。然后你又打来说"我",秘书又开始新一轮准备。再打来说"查"。三通电话,三次无效准备。
5.2 解法:等一会儿再处理
OpenClaw 的做法很朴素——等一等。
收到第一条消息后,不立即处理,而是等一个短暂的时间窗口(默认 2 秒)。如果窗口内又来了新消息,就重置计时器继续等。直到连续 2 秒没有新消息,才把所有累积的消息打包成一条,一次性处理。
Agent 最终收到的是一条合并消息:"帮我查一下\n服务器的状态\n主要看CPU和内存"——完整的意图,一次调用,一次回复。
🔑 关键点
防抖时间可以按渠道单独设置。WhatsApp 用户特别喜欢分条发消息(每条只有几个字),所以 WhatsApp 渠道的防抖通常设到 5 秒甚至更长。Discord 用户倾向于一次写完再发送,2 秒就够了。
配置参数是 inbound.debounceMs——以毫秒为单位。默认 2000 毫秒(2 秒),WhatsApp 建议 5000 毫秒。
5.3 不同平台的用户发消息习惯
为什么防抖时间要按渠道设置?因为用户在不同平台上的发消息习惯差别巨大。
🎯 打个比方
防抖就像电梯关门——有人来了就重新开门等一等。不同的楼,电梯等待时间不一样。办公楼电梯等 2 秒就够了(大家步伐快),医院电梯可能等 5 秒(有人推轮椅进来需要更多时间)。OpenClaw 的防抖时间就是"电梯等门时间",按场景调整。
5.4 防抖的边界情况
错误直觉
「防抖时间越长越好——等得越久,收到的消息越完整。」
不。防抖时间太长会导致响应延迟。
如果你设了 10 秒的防抖,即使只发了一条消息,Agent 也要等 10 秒才开始处理。用户会觉得"这个 AI 反应好慢"——其实 AI 还没开始思考,是防抖在等待。
⚡ 速记
防抖是成本和体验的权衡:太短浪费 token(重复处理),太长浪费用户时间(白等)。2 秒是大多数场景下的最优解。
6. 队列模式:Agent 正在忙怎么办
6.1 问题:新消息撞上正在处理的任务
防抖解决了"消息发太快"的问题。但还有一个更棘手的场景:
Agent 正在处理你上一条指令(可能要 30 秒甚至几分钟),这时候你又发了一条新消息。
怎么办?
🎯 打个比方
你让员工去开会。会开到一半,你又发消息说"顺便帮我查个东西"。员工应该:
- A. 先把这条消息存起来,等会开完再看?
- B. 排队等当前任务做完,按顺序处理?
- C. 立刻打断会议去处理你的新消息?
每种选择都有道理,也都有代价。OpenClaw 提供了 4 种队列模式,让你按业务场景选择。
6.2 四种模式
💬 说人话
- collect 像收集信箱——信越攒越多,等员工忙完了一起处理。好处是不打扰工作流,坏处是回复可能很慢。
- followup 像银行取号——每个客户按顺序服务,一个处理完再叫下一个。公平但慢。
- steer 像老板推门进来说"刚才那个先不管了,来处理这个"——高效但可能导致之前的任务中断。
- steer-backlog 是 steer 的安全版——老板推门进来,但如果员工正在锁着门做重要操作,消息先贴在门上,操作完了再看。
6.3 用具体场景理解每种模式
光看定义不够直观。来看同一个场景在四种模式下的不同表现。
场景:Agent 正在帮你分析一份 500 行的日志文件(需要 40 秒),这时你发了一条新消息"对了,先把昨天的备份状态也查一下"。
collect 模式——攒信件
Agent 继续分析日志。你的新消息被存进信箱。40 秒后日志分析完了,Agent 回复你日志分析结果。然后从信箱里取出你的第二条消息,开始查备份状态。
适合什么时候用?你给 Agent 批量下任务,不在乎处理顺序,只要全部做完就行。
followup 模式——排队叫号
跟 collect 几乎一样,区别是:collect 把所有等待中的消息打包成一条一次性给 Agent,followup 按顺序逐条处理。如果你等待期间发了 3 条消息,collect 把 3 条合并成 1 次调用,followup 做 3 次独立调用。
适合什么时候用?每条消息是独立任务,不能合并(比如"翻译这段话"和"查个天气"——合并处理可能导致混乱)。
steer 模式——老板推门
你的新消息被立即插入 Agent 的当前对话。Agent 在分析日志的过程中看到你的新消息,可能会:中断日志分析,先去查备份状态;或者把两个任务合并,一起处理。
适合什么时候用?你需要实时纠正 Agent 的方向。比如 Agent 正在写一篇长文章,你中途说"写轻松点,别太正式",steer 模式能让 Agent 立刻调整风格。
steer-backlog 模式——老板推门但有记事本
跟 steer 一样,但增加了安全保护。如果 Agent 当前正在等工具调用结果(比如一个命令正在执行),新消息不会被立刻注入——而是先存进 backlog,等工具结果回来后再一起注入。
适合什么时候用?你需要 steer 的实时性,但又不想因为消息注入时机不对而打乱 Agent 正在进行的工具调用链。
🔑 关键点
没有"最好"的队列模式——只有最适合你使用习惯的模式。如果你是那种"想到什么马上发"的人,steer 或 steer-backlog 适合你。如果你喜欢一次性下完所有指令然后等结果,collect 更好。
6.4 如果没有队列会怎样
想象一个没有任何队列管理的系统:
- Agent 正在执行你的第一条指令(运行一个 30 秒的数据分析)
- 你发了第二条消息
- 系统启动第二个 Agent 实例来处理第二条消息
- 两个实例同时读写同一个 Session 文件
- 冲突。 数据损坏,或者两个回复互相矛盾 更糟糕的是——两个 Agent 实例都把自己的回复塞进对话历史。下一次你发消息时,Agent 的上下文里有两条自相矛盾的"自己说过的话",AI 会困惑:"我到底是同意了还是拒绝了这个方案?"
🧠 底层逻辑
队列模式的本质是并发控制。同一个 Session 在同一时刻只允许一个 Agent 实例在处理。不同的队列模式只是在回答一个问题:"等待期间收到的新消息,怎么处理?"——是攒着、排队、还是立刻插进去。这不是锦上添花的优化,而是保证数据一致性的必需品。
7. Session 的 dmScope:谁跟谁的对话分开
讲完队列,还有一个容易混淆的设计——dmScope(私信会话范围)。
7.1 问题:两个人同时给 Agent 发消息
如果你和你的合伙人同时在 Discord 上给同一个 Agent 发消息,Agent 怎么区分你们的对话?
你说"查一下本周营收",合伙人说"帮我写个周报"。如果它们混在同一个 Session 里,Agent 可能一边查营收一边写周报——两件事搅在一起,全部搞砸。
7.2 三种隔离方式
🎯 打个比方
main模式:整个办公室只有一张办公桌,谁来了都在这张桌上办公。适合独居。
per-peer模式:每个员工有自己的桌子,但去哪个会议室用的都是同一张桌上的东西。
per-channel-peer模式:每个员工在每个会议室都有一套独立的办公用品。最隔离,也最占空间。
📌 记住这点
OpenClaw 默认用
per-channel-peer——最大隔离。这意味着你在 #总部频道跟总经理聊的内容,和你在其他地方跟同一个总经理聊的内容,是完全独立的两个 Session。对话历史、上下文、一切都分开。
7.3 为什么默认不用 main(最简单的模式)
你可能会想:如果只有我一个人在用 OpenClaw,为什么不用 main 模式?所有对话共享一个 Session,多好——Agent 在任何频道都能记住之前的上下文。
问题在于:Session 共享意味着上下文共享。
你在 #总部频道跟总经理讨论公司战略(严肃话题),同时在 #私人助理频道聊日程安排(轻松话题)。如果用 main 模式,这两段对话的历史混在同一个上下文里。Agent 可能在帮你安排日程的时候,突然冒出一句关于战略决策的思考——因为它的上下文里有这些信息。
更严重的是上下文空间竞争。200K 的窗口被多个频道的对话共同占用,每个频道分到的有效空间都变小了。
💬 说人话
main模式像一个笔记本,所有课程的笔记都记在同一本上。数学课的公式旁边是语文课的古诗,翻来翻去容易串。per-channel-peer像每门课一个笔记本——整洁,但你要带更多本子。
🔍 深入一步
Session Key 的格式
agent:<agentId>:<mainKey>中,mainKey 的生成方式取决于 dmScope。main模式下 mainKey 固定(不含用户和频道信息),per-channel-peer模式下 mainKey 包含频道 ID 和用户 ID,这就是隔离的技术实现。
8. 流式传输:Agent 一边想一边说
到目前为止,我们假设 Agent 是"想完了再说"——处理完所有逻辑,生成完整回复,然后切块发出去。
但实际上,LLM 生成文本是逐 token 的——它一个词一个词往外吐。OpenClaw 可以选择让 Agent "一边想一边说",而不是等全部想完。
这就是流式传输(Streaming)。
8.1 不流式的痛点
先说不开流式会怎样。Agent 生成一个 3000 字的分析报告,可能需要 15-20 秒。用户在这 15-20 秒里什么都看不到——只有一个"正在输入"的指示器在闪烁。然后突然一大段文字"砰"地出现。
对于短回复(1-2 句话),这种体验没问题。但对于长回复,用户会焦虑:它到底在干什么?卡住了吗?要不要重新发一遍?
流式传输解决的就是这个等待焦虑。
8.2 两种流式模式
💬 说人话
Block Streaming 像看报纸印刷——一版一版出来。Draft Streaming 像看人手写——字一个一个冒出来。后者更有"实时感",但只有 Telegram 支持(因为 Telegram 允许编辑已发送的消息)。
8.3 分块的断点选择
Block Streaming 在发送时,也要选择"在哪里断开"。断点选择和回复分块(阶段八)一样:
段落 > 换行 > 句子 > 空格 > 硬断点
这保证了每一块都是语义完整的——用户不会看到半句话。
🔍 深入一步
流式传输不是免费的午餐。Draft Streaming 需要频繁编辑消息(每生成几个 token 就编辑一次),如果 API 调用频率过高,可能触发平台的速率限制。这也是为什么 Draft Streaming 目前只在 Telegram 上实现——Telegram 的编辑 API 限制相对宽松。
8.4 为什么不总是开流式
错误直觉
「流式传输让用户更快看到回复,应该默认全开。」
听起来合理,但现实更复杂。
⚡ 速记
流式传输的价值在于「长回复 + 等待时间长」的场景。短平快的交互反而增加了不必要的 API 调用。
9. 消息去重:网络抖动的防线
最后一个设计,短小但关键。
9.1 问题:消息被重复投递
网络不是完美的。Telegram、Discord、WhatsApp 都可能因为网络抖动,把同一条消息投递两次甚至三次。
如果不做去重,Agent 就会处理同一个问题两三次——浪费 token,回复重复,用户困惑。
9.2 解法:短期缓存
Gateway 维护一个短期缓存,记住最近处理过的消息 ID。新消息到达时,先检查这个 ID 有没有处理过:
⚡ 速记
去重靠的是消息 ID + 短期缓存。简单粗暴但有效——大部分重复投递发生在几秒之内,短期缓存完全够用。
9.3 去重 vs 防抖:别搞混
去重和防抖听起来相似(都在"合并消息"),但解决的是完全不同的问题。
10. 回顾:为什么这些设计不是过度工程
你可能会想:防抖、队列、去重、分块……有必要搞这么复杂吗?
让我们用反事实来验证——如果去掉每一个设计,会怎样。
可以看到,越底层的设计(标准化、路由、隔离)越不可或缺——去掉它们系统直接不可用。越上层的设计(防抖、去重)更像是体验优化——没有它们系统能跑,但跑得很难看。
🧠 底层逻辑
这揭示了一个架构设计原则:越靠近消息入口的组件越关键,越靠近消息出口的组件越可替换。 Channel 标准化如果出问题,整条管道都瘫痪;回复分块如果出问题,最多切得不好看。设计系统时,把精力放在管道的"入口"而不是"出口"。
🧠 底层逻辑
这些设计解决的不是"AI 能力"的问题,而是"现实世界的混乱"的问题——网络抖动、用户习惯、平台差异、并发冲突。AI 模型再强大,如果没有这层"基础设施",它在真实场景中根本跑不起来。
11. 设计权衡
设计权衡
每一个工程选择都是取舍。理解了取舍,你才真正理解了设计意图。
💬 说人话
没有完美的设计,只有最适合的权衡。OpenClaw 在"可预测性"和"灵活性"之间,坚定地选了前者——因为一个你不理解其行为的 AI Agent,比一个不够灵活的 AI Agent 危险得多。
一句话检验
如果你能回答以下问题,你就真正理解了一条消息的旅程:
- AI 实际收到了什么? → 2-6 万 token 的巨大输入包,你的消息只是最后一行
- Binding 匹配需要 AI 吗? → 不需要,是确定性的规则匹配
- 防抖解决什么问题? → 用户分多条发消息时,合并成一次处理,避免重复浪费
- 四种队列模式的核心区别? → Agent 正在忙时,新消息是攒着(collect)、排队(followup)、还是打断(steer)
- 回复分块的断点优先级? → 段落 > 换行 > 句子 > 空格 > 硬断点
- 响应时间的瓶颈在哪? → 80% 的时间花在 Model API 调用,Gateway 管道本身只要几十毫秒
- NO_REPLY 是什么? → Agent 做了事但不说话——用于心跳巡检等不需要回复用户的场景
- dmScope 为什么默认 per-channel-peer? → 最大隔离,避免不同频道的对话上下文互相干扰
终极测试
试着向别人解释这个场景:
「你在 Discord 的 #总部频道给 Agent 连发了 3 条消息,Agent 正在处理上一个任务。这 3 条消息最终是怎么到达 AI 的?」
如果你能说清楚以下要点,你就真正掌握了:
- 3 条消息经过 Channel 标准化后格式统一
- Gateway 路由到 Binding,确定性地匹配到总经理 Agent
- Session Key 包含频道 ID 和用户 ID,定位到你的专属对话桶
- 防抖把 3 条消息等齐(2 秒内没有新消息后打包)
- 队列模式决定这包消息是立即插入(steer)还是等上一个任务完成(collect/followup)
- Context 组装把你的消息加在 2-6 万 token 的系统信息之后
- LLM 处理整个输入包,可能调用工具形成多轮循环
- 最终回复按段落断点切割后分块发回 Discord
CC 提示词
理解了原理之后,用 Claude Code 实际检查一下你的 OpenClaw 消息管道配置。不需要你手动翻配置文件——把提示词交给 CC,它会帮你全面体检。
🚀 对 Claude Code 说 帮我追踪 OpenClaw 一条消息的完整旅程: - 读取 /你的路径/.openclaw/openclaw.json 中的 bindings 配置,列出所有 Agent 绑定的频道
- 找到 session 配置中的 dmScope 设置,解释当前的隔离策略
- 检查是否配置了 inbound.debounceMs(防抖),如果没有,说明默认值是多少
- 检查消息队列模式(queue)的配置,解释当前模式的行为
- 查看 Gateway 端口配置,确认是否在 18789 上运行
- 综合分析:当前配置下,如果用户在 2 秒内连发 3 条消息,会发生什么
🚀 对 Claude Code 说 帮我分析 OpenClaw Agent 的 Context 组装大小: - 读取 /你的路径/.openclaw/workspace-main/ 下所有 workspace 文件(SOUL.md、AGENTS.md、USER.md、IDENTITY.md、TOOLS.md、MEMORY.md)
- 统计每个文件的字符数和估算 token 数(按中文 1 字 ≈ 2 token,英文 1 词 ≈ 1.3 token 估算)
- 列出占用最大的 3 个文件
- 计算所有 workspace 文件的总 token 数,判断是否在推荐范围内(总计 ≤ 5000 token)
- 如果超标,给出具体的精简建议
延伸阅读
理解了消息旅程的全貌后,以下篇章会深入其中的关键环节:
- 深度教程-04《记忆——AI 怎么记住你》— 阶段五中记忆文件的加载逻辑,三层记忆架构如何让 Agent 越来越像"老员工"
- 深度教程-05《上下文——最贵的资源》— 阶段五中的 Context 为什么这么大,Compaction(压缩,旧对话摘要化)和 Pruning 如何回收空间
- 翔宇版 04《核心概念深度解析》— Binding、Session、dmScope 的技术实现细节
- 翔宇版 05《Gateway 配置大全》— 防抖、队列模式、流式传输的完整参数手册和调优建议