今天我们从路由引擎的源码出发,拆解从消息到达到 Agent 被唤醒的完整路径。

一、消息的标准化:Channel Adapter 接口
OpenClaw 接 20+ 个聊天平台,每个平台的 SDK 和消息格式完全不同——WhatsApp 用的是 Baileys WebSocket 的 protobuf 消息,Telegram 用的 grammY Bot API 的 JSON 结构,Discord 用的是 Gateway Intents 的事件对象。
Channel Adapter 的职责就是把所有平台的异构消息转成统一的内部格式:
WhatsApp protobuf → Channel Adapter → {sender, chatId, text, timestamp, platform: ”whatsapp”}Telegram JSON → Channel Adapter → {sender, chatId, text, timestamp, platform: ”telegram”}Discord Event → Channel Adapter → {sender, chatId, text, timestamp, platform: ”discord”}
Agent 完全不需要知道消息是从哪个平台来的——它只看到统一的 InboundMessage。回复时也一样:Agent 生成文本 → Channel Adapter 反向适配成各平台的发送格式。
二、路由三件套:allowFrom、mentionRules、groupPolicy
allowFrom:最粗粒度的白名单
{”channels”: {”whatsapp”: {”allowFrom”: [”+15551234567”, ”+8613800138000”]}}}
只允许列表里的号码给 Agent 发消息。其他人发了 → 消息被静默丢弃,Agent 不唤醒。这是第一道防线——"连门都不让进"。
mentionRules:群聊里的 @ 规则
{”channels”: {”telegram”: {”groups”: {”*”: { ”requireMention”: true },”-1001234567890”: { ”requireMention”: false }}}}}
"*"规则:默认所有群都需要 @ 才能触发 Agent(防止群聊噪音) 特定群 ID 规则:某个群不需要 @(比如你和 Agent 的私人群) 还支持 mentionPatterns:自定义触发词(比如@openclaw或!ai)
groupPolicy:群级别的细粒度控制
每个群可以独立配置谁的消息能被处理、需要什么触发条件、是否需要审批。插件通道(比如 Discord/MSTeams)可以注册自己的 groupPolicy 实现。
三层过滤的执行顺序:allowFrom → groupPolicy → mentionRules。如果 allowFrom 不通过,后面的根本不检查——短路求值,避免浪费计算。
三、Session Key:跨通道的用户身份绑定
当同一个用户在 WhatsApp 和 Telegram 上都有消息时,怎么把它们路由到同一个 Session?
OpenClaw 用 session-key 机制解决:
{”routing”: {”sessionKey”: {”mode”: ”sender”,”bindings”: {”whatsapp:+15551234567”: ”user-alice”,”telegram:123456789”: ”user-alice”}}}}
session-key 的值是"用户标识"——WhatsApp 号码 + Telegram ID 映射到同一个 user-alice,Agent 在两边看到的是同一个会话上下文。你在 WhatsApp 上聊了三轮,切到 Telegram 上接着问"刚才那个 bug 怎么样了"——Agent 记得。
默认模式 sender 让每个发送者自动获得独立 Session。不需要配绑定也能用——只是跨平台不共享。
四、消息处理的全链路时序
一条 WhatsApp 消息触发 Agent 推理的完整路径:
1. Baileys WebSocket 收到 WhatsApp 消息2. WhatsApp Channel Adapter 标准化为 InboundMessage3. Routing Engine 评估:a. allowFrom 检查 → sender 在白名单里 ✓b. 如果是群消息 → groupPolicy 检查 → 允许处理 ✓c. mentionRules 检查 → 消息包含 @openclaw ✓4. Session Manager 根据 session-key 查找 Session:a. 找到 → 追加消息到现有 Sessionb. 没找到 → 创建新 Session(新 JSONL 文件)5. Agent Loop 启动:a. 读 System Prompt(AGENTS.md + Skills)b. 读 Session 历史(前 20 条消息 + 上下文窗口)c. 绑定 Tools(内置 + plugin 注册)d. 调 LLM → tool_call → 执行 → 再推理 → 生成回复6. Response 通过 Channel Adapter → Baileys → WhatsApp 发送
整个链路里,路由层的开销极低——allowFrom 是内存查表,session-key 是字符串映射,都不涉及 I/O。唯一可能慢的是 Session 文件读取(磁盘 I/O),但 JSONL 的追加写+顺序读让这个开销也几乎可以忽略。
五、核心通道 vs 插件通道:代码怎么分布?
src/telegram/ | ||
src/discord/ | ||
src/slack/ | ||
src/signal/ | ||
src/imessage/ | ||
src/web/ | ||
extensions/msteams/ | ||
extensions/matrix/ | ||
extensions/zalo/ | ||
extensions/line/ |
为什么 Telegram/Discord/Slack 放在核心而不是插件?因为这些是使用量最大的通道,编译进核心减少安装步骤。而 MSTeams/Matrix/Zalo/LINE 等放在插件里,用户不需要的就不装——减少依赖树和包体积。
六、群聊消息的噪音问题:怎么不让 Agent 被群里聊天淹死?
群聊最大的问题是噪音——100 人的群每分钟几十条消息,如果每条都触发 Agent 推理,token 费用直接爆炸。
OpenClaw 的解决策略:
requireMention(默认开启):只有 @Agent 的消息才触发推理 mentionPatterns:自定义触发模式("!ai"、"@claw"、"hey bot") 静默丢弃:没 @ 的消息 → 不进 Agent Loop → 不计费 群摘要模式:可以配 cron 定时任务,让 Agent 定期读群聊天记录生成摘要——而不是实时响应每条消息
总结
消息路由层的设计核心:
Channel Adapter 让 Agent 对平台透明:Agent 只看到统一消息格式 三层过滤(allowFrom → groupPolicy → mentionRules)短路求值:不在白名单的消息根本不进推理 session-key 实现跨平台会话连续性:WhatsApp + Telegram 共享同一个 Agent 上下文 核心通道编译进二进制,小众通道插件化:减少默认安装的依赖
下一篇拆工具系统与 Skills 协同——看看 TypeBox 怎么保证工具 Schema 的类型安全,Skills 怎么在 System Prompt 里告诉模型"什么时候用哪个工具"。
夜雨聆风