1. 本期目标
前几期我们已经分析了 CLI、Gateway、Agent 执行链路和 Session 会话模型。到这里,OpenClaw 的核心骨架已经比较清楚:
CLI / UI / Channel / Node
↓
Gateway
↓
Agent / Session / Tool / Skill
这一期继续分析一个非常关键的问题:
Telegram、Slack、Discord、WhatsApp、WebChat 等外部消息,
到底是如何进入 OpenClaw 的?
OpenClaw 的 Channel 不是简单的"第三方平台适配器"。它承担了外部消息接入、访问控制、会话路由、Agent 绑定、回复投递、去重、防抖、群聊激活等一整套逻辑。官方消息流程文档把高层链路概括为:Inbound message → routing/bindings → session key → queue → agent run → outbound replies。(OpenClaw)
本期主要解决几个问题:
1. Channel 在 OpenClaw 架构中处于什么位置?
2. 一个 Channel 是如何通过配置启用的?
3. 外部消息进入后,如何判断是否允许处理?
4. 消息如何映射到 agentId 和 sessionKey?
5. DM、群聊、频道、线程的路由有什么区别?
6. 为什么需要 dedupe、debounce、mention gating?
7. 源码应该从哪些文件开始读?
2. Channel 是什么?
在 OpenClaw 中,Channel 可以理解为"外部消息平台接入层"。
例如:
Telegram
Slack
Discord
WhatsApp
Signal
iMessage
Matrix
WebChat
Google Chat
Microsoft Teams
Mattermost
这些平台的消息格式、用户 ID、群聊 ID、线程机制、权限模型都不一样。OpenClaw 需要把它们转换成统一的内部消息结构,然后交给 Gateway 和 Agent 处理。
所以 Channel 的作用可以概括为:
外部平台消息
↓
Channel adapter
↓
统一内部消息事件
↓
Gateway 路由
↓
Session / Agent
↓
回复投递回原平台
也就是说,Channel 解决的是"外部世界如何接入 OpenClaw"的问题。
3. Channel 和 Gateway 的关系
Channel 不是独立运行的模型调用服务,而是由 Gateway 统一管理。
官方配置文档说明,每个 Channel 都有自己的配置段,通常位于 channels.<provider> 下;如果某个 channel 配置存在,它会自动启动,除非显式设置 enabled: false。(OpenClaw)
可以这样理解:
openclaw.json
↓
channels.telegram / channels.discord / channels.slack ...
↓
Gateway 启动时读取配置
↓
加载对应 Channel
↓
Channel 开始监听外部消息
所以,Channel 的生命周期受 Gateway 管理:
Gateway 启动
↓
读取配置
↓
发现已配置的 channels
↓
启动 channel runtime
↓
接收外部消息
↓
交给 Gateway 内部消息管线
这也是为什么我们前面一直强调 Gateway 是 OpenClaw 的控制平面。Channel 接入、Session 管理、Agent 运行和回复投递最终都会回到 Gateway。
4. Channel 配置入口
Channel 配置通常写在 ~/.openclaw/openclaw.json 中。
一个简化的 Telegram 配置可能类似:
{
"channels": {
"telegram": {
"enabled": true,
"botToken": "123:abc",
"dmPolicy": "pairing",
"allowFrom": ["tg:123"]
}
}
}
官方配置文档中也给出了类似模式:每个 Channel 放在 channels.<provider> 下,DM 访问通过 dmPolicy 控制,例如 pairing、allowlist、open 和 disabled。(OpenClaw)
这里有三个字段尤其重要:
enabled:
是否启用该 Channel。
dmPolicy:
私聊消息如何授权。
allowFrom:
允许哪些用户或来源给 Agent 发消息。
从源码阅读角度看,Channel 配置不是孤立的。它会影响:
Channel 是否启动;
谁能发 DM;
哪些群聊允许触发;
是否需要 mention;
回复投递到哪里;
是否启用多账号;
是否允许频道内命令写配置。
5. DM 和 Group 的访问控制
Channel 接入的第一道门槛是访问控制。
官方 Channel 配置文档明确区分了 DM policy 和 Group policy:
DM policy:
pairing / allowlist / open / disabled
Group policy:
allowlist / open / disabled
其中,pairing 是默认 DM 策略:未知发送者会收到一次性配对码,需要 owner 批准;allowlist 表示只允许白名单;open 表示允许所有入站 DM,但需要配合 allowFrom: ["*"];disabled 表示忽略 DM。群聊默认是 allowlist,也可以设置为 open 或 disabled。(OpenClaw)
可以这样理解:
外部消息进来
↓
先判断来自 DM 还是群聊
↓
DM 走 dmPolicy
群聊走 groupPolicy
↓
不满足策略则丢弃或进入 pairing
↓
满足策略才继续路由
这一层非常重要。因为 OpenClaw 不是普通聊天机器人,它可以调用工具、访问本地环境、连接工作区。如果任何人都能随便给它发消息,风险会非常高。
6. pairing:首次私聊为什么需要配对?
pairing 是 OpenClaw 的默认 DM 策略。
它的基本思路是:
未知用户第一次私聊 Agent
↓
系统生成 pairing code
↓
owner 在本地批准
↓
该用户才被允许继续对话
这解决了一个问题:你的 Telegram bot、Discord bot 或其他 Channel 可能被陌生人找到。如果默认直接开放,陌生人就能驱动你的 Agent 做事情。通过 pairing,OpenClaw 把"第一次接入"变成显式授权流程。
从博客解释角度,可以这样写:
pairing 不是聊天体验功能,而是安全边界。
它决定外部身份是否可以进入 OpenClaw 的 Agent 运行管线。
7. Channel 源码目录怎么看?
从源码结构看,src/channels 是 Channel 相关的核心目录。当前目录中可以看到 allowlists、inbound-event、message-access、message、plugins、status、transport、turn 等子目录,以及 channel-config.ts、conversation-resolution.ts、mention-gating.ts、model-overrides.ts、native-command-session-targets.ts 等文件。(GitHub)
可以先建立一个目录级理解:
src/channels/
├─ allowlists/ 白名单与允许来源
├─ inbound-event/ 入站事件结构或处理
├─ message-access/ 消息访问控制
├─ message/ 消息抽象与通用处理
├─ plugins/ Channel 插件接入
├─ status/ Channel 状态
├─ transport/ 传输层抽象
├─ turn/ 与一次 Agent turn 相关的 Channel 逻辑
├─ channel-config.ts Channel 配置解析
├─ conversation-resolution.ts
├─ mention-gating.ts
├─ model-overrides.ts
└─ native-command-session-targets.ts
初学者不要一上来就钻进某个平台的实现。更推荐先看这些公共模块,因为它们体现了 OpenClaw 对 Channel 的统一抽象。
8. 一条消息进入 OpenClaw 的高层流程
可以先用下面这张图理解:
外部平台消息
↓
Channel plugin / adapter 接收
↓
标准化成内部 inbound message
↓
访问控制:dmPolicy / groupPolicy / allowlist / pairing
↓
群聊激活:mention gating / activation
↓
路由匹配:bindings / peer / guild / team / account / default agent
↓
生成 sessionKey
↓
进入 Session store
↓
Agent run
↓
生成 reply payload
↓
Channel delivery
↓
外部平台收到回复
官方消息文档同样强调,入站消息会经过 session resolution、queueing、streaming、tool execution 和 reasoning visibility 等管线。(OpenClaw)
所以 Channel 并不是"收到消息后直接调用模型"。它前面有访问控制和路由,后面还有回复投递和平台限制处理。
9. 消息如何选择 Agent?
OpenClaw 支持多个 Agent,因此一条外部消息进入后,系统必须先决定"由哪个 Agent 处理"。
官方 Channel Routing 文档列出了入站消息选择 Agent 的匹配顺序:
1. 精确 peer 匹配
2. parent peer 匹配,也就是 thread inheritance
3. Discord guild + roles 匹配
4. Discord guild 匹配
5. Slack team 匹配
6. accountId 匹配
7. channel 匹配
8. 默认 agent
如果一个 binding 同时包含多个匹配字段,例如 peer、guildId、teamId、roles,那么这些字段都必须满足,该 binding 才会生效;匹配到的 Agent 会决定使用哪个 workspace 和 session store。(OpenClaw)
这说明 Agent 路由不是简单地"所有消息都给 main"。
更准确地说:
外部消息
↓
根据 channel / account / peer / guild / team / roles 等信息匹配 binding
↓
得到 agentId
↓
用该 agentId 找 workspace 和 session store
示例:
{
"agents": {
"list": [
{
"id": "support",
"name": "Support",
"workspace": "~/.openclaw/workspace-support"
}
]
},
"bindings": [
{
"match": {
"channel": "slack",
"teamId": "T123"
},
"agentId": "support"
},
{
"match": {
"channel": "telegram",
"peer": {
"kind": "group",
"id": "-100123"
}
},
"agentId": "support"
}
]
}
这类设计让 OpenClaw 可以做到:
某个 Slack workspace 交给 support agent;
某个 Telegram 群交给 research agent;
某个 Discord guild 交给 community agent;
没有匹配时回到默认 main agent。
10. 消息如何生成 sessionKey?
上一期我们讲过,Session 是上下文边界。Channel 进来的消息最终也要落到某个 sessionKey。
官方 Channel Routing 文档给出了几类典型 session key 形态:
Direct messages:
agent:<agentId>:<mainKey>
默认是 agent:main:main
Groups:
agent:<agentId>:<channel>:group:<id>
Channels / rooms:
agent:<agentId>:<channel>:channel:<id>
Slack / Discord threads:
在基础 key 后追加 :thread:<threadId>
Telegram forum topics:
在 group key 中嵌入 :topic:<topicId>
例如:
agent:main:telegram:group:-1001234567890:topic:42
agent:main:discord:channel:123456:thread:987654
这些格式来自官方 Channel Routing 文档。(OpenClaw)
可以这样理解:
agentId 决定由哪个 Agent 处理;
channel 决定来自哪个平台;
group/channel/direct 决定消息场景;
id 决定具体对话空间;
thread/topic 决定更细粒度的上下文分支。
也就是说,Channel 路由最终要落到 Session 路由:
外部平台身份
↓
内部 agentId
↓
内部 sessionKey
↓
sessionId / transcript
11. DM 为什么默认进入 main session?
官方文档中有一个重要默认行为:Direct messages 默认会折叠到 Agent 的 main session key,例如 agent:main:main。同时,群组和频道会按 channel 维度隔离,形成各自的 sessionKey。(OpenClaw)
这个设计有它的使用逻辑:
单用户个人助手场景:
用户从 Telegram、WebChat、CLI 私聊同一个 Agent,
希望它们共享一个主上下文。
多人或多渠道场景:
需要打开 dmScope 隔离,否则不同人的 DM 可能进入同一个上下文。
所以可以在博客里强调:
DM 默认合并主会话适合"个人助手"场景;
多人开放接入时,需要特别关注 session.dmScope。
上一期已经讲过 DM isolation,这一期可以把它放到 Channel 里理解:Channel 是外部身份进入系统的入口,Session 是这些身份进入上下文的边界。
12. Group / Channel 为什么要隔离?
群聊和频道默认隔离是合理的。
因为一个群或一个频道往往有自己的话题、成员和上下文。如果所有群聊都进入 main session,会造成明显的上下文污染。
例如:
Telegram A 群在讨论代码问题;
Discord B 频道在讨论项目管理;
Slack C 频道在讨论部署故障。
如果三者共用同一个 session,Agent 很可能把 A 群的上下文带到 B 频道,把 C 频道的故障信息带回 Telegram。
所以 OpenClaw 用不同 sessionKey 把它们隔离:
agent:main:telegram:group:-100123
agent:main:discord:channel:123456
agent:main:slack:channel:C123
一句话理解:
DM 默认强调个人连续性;
群聊和频道默认强调上下文隔离。
13. Thread 和 Topic 为什么还要细分?
Slack / Discord 有 thread,Telegram 有 forum topic。
这些结构在用户体验上是同一群或同一频道下的子对话,但在上下文管理上通常应该分开。
所以 OpenClaw 会在 sessionKey 里继续追加:
:thread:<threadId>
:topic:<topicId>
这样能把一个大频道中的不同讨论隔开。
例如:
agent:main:discord:channel:123456:thread:987654
表示:
main Agent
Discord 平台
某个频道
某个具体 thread
这种结构使 OpenClaw 能做到:
同一个频道中不同 thread 各有上下文;
thread 内连续讨论不会污染整个频道;
Agent 回复可以回到正确 thread。
14. Mention gating:群聊里为什么不能每句话都回复?
在群聊中,如果 Agent 对每条消息都自动回复,体验会很糟糕。
所以 OpenClaw 支持群聊激活规则。官方配置文档中提到,群消息默认需要 mention,可以通过 agent 的 groupChat.mentionPatterns 和 channel 的 group 配置来控制触发方式;还可以把 groupChat 的 visible replies 设置成 message_tool,让可见输出必须通过 message 工具显式发送。(OpenClaw)
可以这样理解:
群聊消息进入
↓
判断是否来自允许群
↓
判断是否 mention / 触发关键词
↓
未触发:只作为 quiet context 或直接忽略
↓
触发:进入 Agent run
这层机制主要解决两个问题:
第一,避免 Agent 在群里刷屏。
第二,避免群聊中无关消息不断消耗模型资源和上下文。
从源码路径看,可以重点关注:
src/channels/mention-gating.ts
src/auto-reply/group-activation.ts
src/auto-reply/inbound.test.ts
src/channels 目录中确实包含 mention-gating.ts,src/auto-reply 目录中也包含 group-activation.ts 和 inbound.test.ts 等与入站消息和群聊激活相关的文件。(GitHub)
15. Dedupe:为什么要入站去重?
外部平台经常会重投消息。
例如:
网络断开后重连;
Webhook 重试;
Channel adapter 重启;
平台 SDK 重放事件;
Gateway 恢复连接。
如果没有去重,同一条消息可能触发多次 Agent run。官方消息文档说明,OpenClaw 会保留一个短期缓存,并以 channel、account、peer、session、message id 等信息为 key,避免重复投递触发重复 Agent run。(OpenClaw)
可以写成:
同一条外部消息第一次到达:
↓
生成 dedupe key
↓
没有见过,继续处理
同一条外部消息再次到达:
↓
dedupe key 命中
↓
跳过,不再触发 Agent
这对于 Channel 系统非常重要,因为外部平台的可靠投递机制通常会带来"至少一次投递"的问题,而 OpenClaw 需要把它变成"尽量只处理一次"。
16. Debounce:为什么要把连续消息合并?
人在聊天软件中经常会连续发几条短消息:
你帮我看一下
这个日志
好像有错误
最后一行特别奇怪
如果每条都触发一次 Agent run,就会浪费资源,而且 Agent 可能在用户话还没说完时就开始回答。
官方消息文档说明,OpenClaw 支持入站 debouncing:同一 sender 的快速连续消息可以合并成一个 Agent turn;debounce 按 channel + conversation 作用域生效,并且媒体、附件会立即 flush,控制命令会绕过 debounce。(OpenClaw)
可以理解为:
短时间内多条文本消息
↓
先暂存
↓
等待 debounceMs
↓
合并成一个 Agent turn
示例配置:
{
"messages": {
"inbound": {
"debounceMs": 2000,
"byChannel": {
"whatsapp": 5000,
"slack": 1500,
"discord": 1500
}
}
}
}
这让 OpenClaw 更像真实聊天助手,而不是"每收到一个事件就机械响应一次"。
17. Channel model override:不同频道可以用不同模型
Channel 还可以影响模型选择。
官方 Channel 配置文档说明,可以使用 channels.modelByChannel 为特定 channel ID 绑定模型;这个映射会在 session 没有已有模型 override 时生效。例如 Discord 某个频道用 Claude Opus,Slack 某个频道用 GPT,Telegram 某个群或 topic 用另一个模型。(OpenClaw)
示意:
{
"channels": {
"modelByChannel": {
"discord": {
"123456789012345678": "anthropic/claude-opus-4-6"
},
"slack": {
"C1234567890": "openai/gpt-5.5"
},
"telegram": {
"-1001234567890": "openai/gpt-5.4-mini",
"-1001234567890:topic:99": "anthropic/claude-sonnet-4-6"
}
}
}
}
这说明 Channel 路由不仅影响"谁来处理"和"上下文在哪里",还可能影响"用什么模型处理"。
可以总结为:
Agent binding 决定 agentId;
sessionKey 决定上下文;
modelByChannel 决定默认模型;
send/delivery 逻辑决定回复位置。
18. Outbound delivery:回复如何回到原平台?
入站消息进入 Agent 后,最终会产生回复。这个回复还要回到原来的 Channel。
可以用下面的链路理解:
Agent 生成回复
↓
Gateway 得到 reply payload
↓
根据 session / lastRoute / delivery plan 找到目标 Channel
↓
Channel adapter 转成平台 API 调用
↓
发送到 Telegram / Slack / Discord / WhatsApp ...
这里要注意一点:回复不是简单打印到终端,而是要根据原消息来源、线程、群聊、频道、replyTo 等信息回到正确位置。
官方 Channel Routing 文档也提到,入站 replies 可以携带 ReplyToId、ReplyToBody、ReplyToSender 等上下文信息。(OpenClaw)
所以 Outbound delivery 关心的问题包括:
回复哪个 Channel?
回复哪个 chat / room / channel / group?
是否需要回复到 thread?
是否引用原消息?
消息过长是否要 chunk?
平台是否支持图片 / 文件 / markdown?
是否需要发送 typing / progress draft?
这也是 Channel 系统复杂的原因。
19. WebChat 的特殊性
WebChat 也是一种 Channel,但它和 Telegram、Slack 这类外部平台不完全一样。
官方 Channel Routing 文档说明,WebChat 会附着到选中的 Agent,并默认使用该 Agent 的 main session;因此 WebChat 可以看到该 Agent 的跨 Channel 上下文。(OpenClaw)
这说明 WebChat 更像是 Gateway / Agent 的本地可视化聊天入口,而不是一个完全隔离的外部社交平台。
可以这样理解:
Telegram / Slack / Discord:
更偏外部消息来源。
WebChat:
更偏 Gateway 控制界面中的聊天入口。
所以读 WebChat 相关代码时,要特别注意它和 main session 的关系。
20. Multi-account:同一个 Channel 可以有多个账号
OpenClaw 的 Channel 不只是"一种平台一个账号"。
官方 Channel 配置文档说明,多账号配置适用于所有 Channel;每个账号有自己的 accountId,例如 Telegram 可以配置 default 和 alerts 两个 bot token。(OpenClaw)
示意:
{
"channels": {
"telegram": {
"accounts": {
"default": {
"name": "Primary bot",
"botToken": "123456:ABC..."
},
"alerts": {
"name": "Alerts bot",
"botToken": "987654:XYZ..."
}
}
}
}
}
这样一来,路由时就不能只看 channel=telegram,还可能要看 accountId。
因此完整路由字段可能包括:
channel
accountId
peer.kind
peer.id
guildId
teamId
roles
threadId
topicId
这也解释了为什么 OpenClaw 的 binding 匹配顺序比较细。
21. Channel 和配置校验
Channel 配置会进入 OpenClaw 的严格配置校验体系。
官方配置文档明确说明,OpenClaw 只接受完全符合 schema 的配置;未知字段、类型错误或非法值会导致 Gateway 拒绝启动,配置失败时只能使用 doctor、logs、health、status 等诊断命令。(OpenClaw)
这对 Channel 很重要。
因为 Channel 配置往往包含:
token
allowlist
group policy
accountId
binding
model override
command permissions
native commands
这些字段如果写错,可能导致:
Channel 启动失败;
消息无法路由;
群聊策略失效;
模型 override 不生效;
配置写操作被拒绝;
Gateway 热重载跳过新配置。
所以调试 Channel 时,不能只看日志,还要用:
openclaw doctor
openclaw config validate
openclaw channels status
openclaw gateway status
22. Channel 相关源码阅读路线
这一期建议按下面顺序看源码:
第一组:Channel 基础结构
src/channels/channel-config.ts
src/channels/status/
src/channels/transport/
src/channels/plugins/
第二组:访问控制
src/channels/allowlists/
src/channels/message-access/
src/channels/allow-from.ts
src/channels/allowlist-match.ts
第三组:会话和路由
src/channels/conversation-resolution.ts
src/channels/conversation-binding-context.ts
src/channels/conversation-label.ts
src/channels/native-command-session-targets.ts
第四组:群聊激活
src/channels/mention-gating.ts
src/auto-reply/group-activation.ts
第五组:入站消息处理
src/channels/inbound-event/
src/auto-reply/inbound-debounce.ts
src/auto-reply/dispatch.ts
src/auto-reply/reply/
第六组:Gateway 管理
src/gateway/server-methods/channels.ts
src/gateway/server-methods/chat.ts
src/gateway/server-methods/agent.ts
从目录结构看,src/gateway/server-methods 中确实包含 channels.ts、chat.ts 和 agent.ts,这说明 Gateway 把 Channel 管理、Chat 消息和 Agent run 分到了不同方法模块。(GitHub)
23. 一条 Telegram 群消息的示例链路
假设有一条 Telegram 群消息:
@openclaw 帮我总结一下今天的部署日志
它进入 OpenClaw 后,大致会经过:
Telegram bot 收到消息
↓
telegram channel adapter 标准化消息
↓
检查 groupPolicy / groupAllowFrom
↓
检查是否满足 mention gating
↓
根据 bindings 匹配 agentId
↓
生成 sessionKey:
agent:<agentId>:telegram:group:<groupId>
↓
如果是 forum topic,嵌入 :topic:<topicId>
↓
检查 dedupe
↓
检查 debounce
↓
进入 Agent run
↓
Agent 读取对应 transcript
↓
模型生成回复
↓
Channel delivery 发送回 Telegram 群或 topic
可以看到,这里面模型调用只是中间一环。真正复杂的是前后的工程链路。
24. 一条 Slack Thread 消息的示例链路
Slack thread 的链路会多一个 thread 维度:
Slack message in thread
↓
Slack channel adapter 标准化消息
↓
识别 teamId / channelId / threadTs
↓
bindings 匹配 agentId
↓
生成 sessionKey:
agent:<agentId>:slack:channel:<channelId>:thread:<threadId>
↓
进入独立 thread session
↓
Agent run
↓
回复到同一个 Slack thread
这种设计的好处是:
同一 Slack 频道内,不同 thread 不会混淆上下文。
25. 本期重点理解
这一期可以总结为五点:
第一,Channel 是 OpenClaw 连接外部消息平台的接入层,负责把 Telegram、Slack、Discord、WhatsApp 等平台消息转换为内部消息事件。
第二,Channel 由 Gateway 统一管理,通常通过 openclaw.json 中的 channels.<provider> 配置启用。
第三,外部消息进入后,先经过 dmPolicy、groupPolicy、allowlist、pairing 和 mention gating 等访问控制与激活逻辑。
第四,消息会通过 bindings 匹配到具体 agentId,并生成对应 sessionKey,从而进入正确上下文。
第五,Channel 还负责去重、连续消息防抖、模型覆盖、回复投递、线程 / topic 路由和多账号场景。
一句话概括:
OpenClaw 的 Channel 不是简单平台适配器,而是外部消息进入 Agent 系统前的访问控制层、路由层和上下文边界生成层。
26. 本期小结
本期主要分析了 OpenClaw 的 Channel 接入机制。Channel 负责把 Telegram、Slack、Discord、WhatsApp、WebChat 等外部平台消息接入 Gateway,并经过访问控制、群聊激活、Agent binding、sessionKey 生成、去重、防抖、队列和投递等流程,最终触发 Agent run 并把回复发送回原平台。OpenClaw 通过 channels.<provider> 配置启用不同平台,通过 dmPolicy 和 groupPolicy 控制谁能发消息,通过 bindings 决定哪个 Agent 处理消息,通过结构化 sessionKey 维护不同 DM、群聊、频道、thread 和 topic 的上下文边界。
这一期可以用一句话总结:
Channel 是 OpenClaw 从"本地 Agent"走向"多平台个人助手"的关键入口,它把外部消息平台、Gateway、Session 和 Agent Runtime 串成了一条完整消息链路。
下一期可以继续分析:
OpenClaw 源码解析(十):消息队列、去重与防抖机制
下一期重点看 dedupe、debounce、active run queue、steering queue、abort、retry、streaming 和 chunking,理解 OpenClaw 如何在真实聊天环境中处理重复消息、连续消息、长任务和平台消息限制。