cd ..
2026-04-2824 min176 views

剖析 OpenClaw 与 Hermes:Agent 如何通过统一网关层接入 IM 插件

#Im Gateway#Agent Integration#Plugin Architecture#Message Standardization#Session Management
AI Summary
每分钟最多 5 次
  • Agent 不应直接对接每个即时通讯(IM)平台:OpenClaw 和 Hermes 都建议通过一个统一的网关层将不同 IM 的消息转换为标准事件,再由 Agent 执行。这避免了 Agent 核心被特定 IM 协议污染,并简化了会话管理和权限控制。
  • OpenClaw 采用插件化 Channel Gateway:它允许以插件形式添加新的 IM 支持,每个插件需提供 openclaw.plugin.json 文件用于身份识别、配置校验等。这种方式非常适合构建多 IM 插件生态系统。
  • Hermes 使用内置 Platform Adapter:其扩展方式更倾向于在仓库内实现适配器,而不是外部插件。Hermes 的架构设计使得同一个 AIAgent 可以服务多种入口点,如 CLI、API Server 等,而平台差异则在接入层处理。
  • 统一的消息标准化与回复分发机制:无论是 OpenClaw 还是 Hermes,都采用了将原始 IM 消息转化为统一格式(如 MessageEvent 或 ctxPayload),并通过专门的分发组件(如 reply dispatcher 或 adapter.send_message())来发送回复,确保 Agent 逻辑与具体的 IM 实现解耦。
  • 推荐的自建 Agent IM 网关架构:结合 OpenClaw 的插件式扩展性和 Hermes 的统一消息处理及权限控制策略,开发者可以创建既灵活又安全的 IM 网关,支持多样化的通信渠道同时保持核心业务逻辑的一致性。

剖析 OpenClaw 与 Hermes:Agent 如何通过统一网关层接入 IM 插件

现在很多 Agent 项目已经不满足于“在网页里聊天”了。真正有用的 Agent,往往需要出现在用户原本工作的地方:Slack、Telegram、飞书、钉钉、企微、Discord、WhatsApp、OpenIM、邮件、短信,甚至 issue tracker 里。

这就带来一个工程问题:Agent 要不要直接对接每一个 IM?

答案通常是否定的。OpenClaw 和 Hermes 给出的思路都很明确:Agent 核心不直接理解各个 IM 协议,而是通过一个统一的 Gateway,把不同 IM 的消息转换成标准事件,再交给 Agent 执行。


一、为什么 Agent 需要统一 IM 网关?

如果没有网关层,一个 Agent 要接入多个 IM,就会变成这样:

Agent
 ├── Telegram SDK
 ├── Slack SDK
 ├── 飞书事件回调
 ├── 钉钉 WebSocket
 ├── 企微回调
 ├── Discord Bot
 └── WhatsApp Business API

这会导致几个问题:

第一,Agent 核心被 IM 协议污染。模型调用、工具调用、记忆、RAG、任务执行,本来应该是平台无关的,但一旦把 Telegram、Slack、飞书的字段写进 Agent,后面维护会很痛苦。

第二,会话管理会混乱。私聊、群聊、线程、频道、机器人多账号、群里 @ 触发,这些都需要稳定的 session key。如果每个 IM 自己处理,很容易出现上下文串线。

第三,权限控制难统一。谁能唤醒 Agent?群里是否必须 @?哪些人能调用高危工具?这些都应该在网关层统一处理,而不是散落在每个 Bot 里。

所以更合理的架构是:

各类 IM / 消息平台
        ↓
IM Adapter / Channel Plugin
        ↓
统一 Gateway
        ↓
Agent Runtime
        ↓
工具调用 / RAG / 数据库 / 业务系统

OpenClaw 和 Hermes 都是这个方向,但两者的扩展方式不同:OpenClaw 更像插件化 Channel Gateway,Hermes 更像内置 Platform Adapter Gateway。


二、OpenClaw:以 Channel Plugin 为核心的统一网关

OpenClaw 官方文档对自己的定位很清楚:它是一个 self-hosted gateway,可以把 Discord、Google Chat、iMessage、Matrix、Teams、Signal、Slack、Telegram、WhatsApp、Zalo 等聊天入口连接到 AI Agent;同时它强调一个 Gateway 可以同时服务内置 channel、bundled channel plugin、外部 channel plugin。OpenClaw 文档还明确说:Gateway 是 sessions、routing、channel connections 的 single source of truth。(GitHub)

也就是说,OpenClaw 的核心不是“某一个 IM Bot”,而是:

OpenClaw Gateway
 ├── built-in channels
 ├── bundled channel plugins
 ├── external channel plugins
 ├── session manager
 ├── routing manager
 ├── reply dispatcher
 └── agent runtime bridge

它的设计重点是:IM 能力以 channel plugin 的形式挂到 Gateway 上。


三、OpenClaw 的插件是怎么被发现和加载的?

OpenClaw 的 native plugin 需要提供一个 openclaw.plugin.json 文件。官方 manifest 文档说明,OpenClaw 会在加载插件代码之前读取这个 manifest,用它做插件身份识别、配置校验、UI 提示、auth/setup 元数据、channel config 元数据等;缺失或无效的 manifest 会被视为插件错误,并阻止配置校验。(GitHub)

这点很关键:Gateway 在执行插件代码之前,就已经知道这个插件是谁、提供什么 channel、需要什么配置。

一个典型插件会有两层元信息:

openclaw.plugin.json
  负责:插件身份、配置 schema、channel config schema

package.json
  负责:npm 包信息、入口文件、openclaw.extensions

OpenClaw 文档也区分了两者:openclaw.plugin.json 用于 discovery、config validation、UI hints 等运行前信息;package.json 里的 openclaw 字段用于 entrypoints、安装门控、setup、catalog 元数据等。(GitHub)


四、以 OpenIM 插件为例:OpenClaw 的 IM 插件结构

OpenIM 的 OpenClaw channel 插件是一个比较好的样本。它的 manifest 中声明了插件 id、name、version、description,并且声明自己提供 openim channel,同时提供 configSchema。(GitHub)

简化后类似这样:

{
  "id": "openclaw-channel",
  "name": "OpenIM Channel",
  "version": "0.3.2",
  "description": "OpenIM protocol channel for OpenClaw",
  "channels": ["openim"],
  "configSchema": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "enabled": {
        "type": "boolean",
        "default": true
      }
    }
  }
}

OpenClaw 的 manifest 文档里还区分了 configSchemachannelConfigs:前者校验 plugins.entries.<plugin-id>.config,后者校验 channels.<channel-id>。这说明 OpenClaw 把“插件自身配置”和“channel 实例配置”分得很清楚。(GitHub)

这类结构对做企业 IM 接入很有价值。比如你做飞书插件,可以这样拆:

plugins.entries.feishu.config
  app_id
  app_secret
  encrypt_key
  verification_token

channels.feishu
  allowUsers
  allowGroups
  requireMention
  defaultAgent
  sessionScope

也就是说,插件负责能力注册,channel 配置负责运行策略。


五、OpenClaw 插件运行时:register(api)

OpenIM 插件的入口是一个 register(api) 函数。代码里可以看到它会调用:

api.registerChannel({ plugin: OpenIMChannelPlugin })
api.registerCli(...)
registerOpenIMTools(api)
api.registerService(...)

也就是说,一个 OpenClaw IM 插件不只是“发消息接口”,它会同时注册 channel、CLI setup、工具、后台 service。(GitHub)

抽象出来就是:

OpenClaw Plugin
 ├── registerChannel()
 │    └── 告诉 Gateway:我提供一个 openim channel
 ├── registerCli()
 │    └── 提供 openim setup 等命令
 ├── registerTools()
 │    └── 让 Agent 可以主动调用 OpenIM 工具
 └── registerService()
      └── 启动 OpenIM SDK、登录账号、监听消息

这套机制很适合三方 IM,因为 IM 插件通常需要长期连接,例如 WebSocket、长轮询、事件回调服务、SDK 登录状态维护等。单纯写一个 sendMessage() 是不够的。


六、OpenClaw 的 Channel Plugin 如何发消息?

OpenIM 插件的 channel 实现中有 sendText 逻辑:先解析目标 to,要求是 user:<id>group:<id>;然后获取已连接的 OpenIM client;最后调用 OpenIM 的发送逻辑,把文本发到目标用户或群组。(GitHub)

简化后可以理解成:

sendText({ to, text, accountId }) {
  const target = parseTarget(to)

  if (!target) {
    return { ok: false, error: "invalid target" }
  }

  const client = getConnectedClient(accountId)

  if (!client) {
    return { ok: false, error: "OpenIM not connected" }
  }

  await sendTextToTarget(client, target, text)

  return { ok: true, provider: "openim" }
}

这就是典型的 outbound adapter 设计:

Gateway 标准发送请求
        ↓
Channel Plugin 解析目标
        ↓
调用具体 IM SDK/API
        ↓
返回统一 SendResult

Agent 不需要知道 OpenIM 的用户 ID、群 ID、SDK 方法名,它只需要通过 Gateway 说:“往这个 channel 的这个 target 发一段文本。”


七、OpenClaw 的入站消息流:从 IM 消息到 Agent 上下文

OpenIM 插件的 processInboundMessage 很能体现 OpenClaw 的网关思想。

它会先做几类处理:

1. 判断 runtime.channel.reply 是否可用
2. 解析消息文本、媒体、发送人、群信息
3. 做白名单校验
4. 群聊判断是否 @ 机器人
5. 生成 baseSessionKey
6. 通过 routing.resolveAgentRoute 做 Agent 路由
7. 构造统一 ctxPayload
8. recordInboundSession 记录会话
9. dispatchReplyWithBufferedBlockDispatcher 调 Agent 并分发回复

代码中可以看到,OpenIM 插件会在有 inbound whitelist 时校验发送者;如果是群聊并且没有 mention,就直接 return;如果没有 whitelist,但群聊配置了 requireMention 且没有被 @,也会 return。(GitHub)

这说明 OpenClaw 把群聊触发策略放在 channel 入站层处理:

私聊:可以默认响应
群聊:通常需要 @bot
白名单:只有允许的用户/群可以触发

然后它会生成类似这样的 session key:

const baseSessionKey = group
  ? `openim:group:${msg.groupID}`.toLowerCase()
  : `openim:${msg.sendID}`.toLowerCase()

再调用 Gateway runtime 的 routing 能力解析最终 agent route。代码里可以看到它会拿到 route.sessionKey,并构造 ctxPayload,里面包含 BodyRawBodyFromToSessionKeyAccountIdChatTypeSenderIdProviderSurfaceOriginatingChannel 等字段。(GitHub)

这个 ctxPayload 就是 OpenClaw 的关键抽象:不同 IM 的原始事件,最终都要变成 Agent 能读懂的统一上下文。


八、OpenClaw 的回复分发:Agent 输出如何回到原 IM?

OpenIM 插件最后会调用:

runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
  ctx: ctxPayload,
  cfg,
  dispatcherOptions: {
    deliver: async (payload) => {
      await sendReplyFromInbound(client, msg, payload.text)
    }
  }
})

代码中可以看到,它把 ctxPayload 交给 Gateway 的 reply dispatcher;dispatcher 负责处理 Agent 回复,然后通过插件提供的 deliver 回调把文本发回 OpenIM 原会话。(GitHub)

这背后的分层很漂亮:

Agent
  只负责生成回复

Gateway Reply Dispatcher
  负责流式/分块/缓冲/错误处理

Channel Plugin
  负责把回复发回具体 IM

这避免了一个常见错误:让 Agent 自己记住消息来自哪个 IM,然后自己调用对应 SDK 发回去。

正确做法是 Gateway 记录 OriginatingChannelSessionKeyFromTo,再通过 channel plugin 回发。


九、Hermes:以内置 Platform Adapter 为核心的统一网关

Hermes 的网关设计和 OpenClaw 类似,也是把消息平台接入和 Agent 核心解耦。但 Hermes 的扩展方式更偏“内置 adapter”,而不是 OpenClaw 那种外部 channel plugin。

Hermes 的架构文档里说明,Messaging Gateway 是一个 long-running process,包含 18 个 platform adapters、统一 session routing、用户授权、slash command dispatch、hook system、cron ticking 和后台维护。(GitHub)

Hermes 的整体 Agent 架构是:

Entry Points
 ├── CLI
 ├── Gateway
 ├── ACP
 ├── Batch Runner
 ├── API Server
 └── Python Library
        ↓
AIAgent
        ↓
Provider Resolution / Tool Dispatch / Session Storage

官方文档中也明确写到:同一个 AIAgent class 服务 CLI、gateway、ACP、batch、API server;平台差异存在于 entry point,而不是 agent 本身。(GitHub)

这句话非常关键。它说明 Hermes 的原则是:

平台接入层可以不同,
但 Agent 核心必须保持平台无关。

十、Hermes 的消息流:Adapter → MessageEvent → GatewayRunner → AIAgent

Hermes 官方 Gateway Internals 文档把消息流写得很清楚:

1. Platform adapter 接收原始事件,并标准化成 MessageEvent
2. Base adapter 检查 active session guard
3. GatewayRunner._handle_message() 接收事件
4. 解析 session key
5. 做授权检查
6. 判断是否是 slash command
7. 如果没有正在运行的 agent,则创建 AIAgent 并运行对话
8. 回复通过 platform adapter 发回

这个流程说明 Hermes 也采用了标准事件模型:不同平台进来的原始事件,会先被 adapter 统一成 MessageEvent,再进入 GatewayRunner。(Hermes Agent)

对应架构可以画成:

Telegram / Slack / Discord / 飞书 / 钉钉 / 企微
        ↓
Platform Adapter
        ↓
MessageEvent
        ↓
GatewayRunner._handle_message()
        ↓
Authorization / Slash Command / Session Routing
        ↓
AIAgent.run_conversation()
        ↓
Adapter.send_message()

Hermes 的 session key 格式也很明确:

agent:main:{platform}:{chat_type}:{chat_id}

例如:

agent:main:telegram:private:123456789

官方文档还提醒,Telegram forum topics、Discord threads、Slack threads 这类支持 thread 的平台,可能会把 thread id 放进 chat_id 部分,并且不要手工拼 session key,而应该使用 build_session_key()。(Hermes Agent)

这说明 Hermes 对“跨平台会话隔离”很重视。它不是简单用用户 id 做上下文,而是把 agent、platform、chat_type、chat_id 都编码进 session key。


十一、Hermes 的 Platform Adapter 抽象

Hermes 文档列出了 gateway/platforms/ 下的各种 adapter,包括 Telegram、Discord、Slack、WhatsApp、Signal、Matrix、Mattermost、Email、SMS、DingTalk、Feishu/Lark、WeCom、Weixin、BlueBubbles、QQ Bot、Webhook、API Server、Home Assistant 等。每个平台 adapter 实现共同接口,包括生命周期管理、出站消息发送、入站消息标准化为 MessageEvent。(Hermes Agent)

官方添加平台 adapter 的文档也给出了标准结构:

User ↔ Messaging Platform ↔ Platform Adapter ↔ Gateway Runner ↔ AIAgent

并说明每个 adapter 都要继承 BasePlatformAdapter,实现 connect()disconnect()send()send_typing()get_chat_info() 等方法;入站消息则构造 MessageEvent,调用 self.handle_message(event) 交给基础类转发到 Gateway Runner。(Hermes Agent)

简化成代码就是:

class NewPlatAdapter(BasePlatformAdapter):
    async def connect(self) -> bool:
        # 建立 WebSocket / webhook / long polling
        return True

    async def disconnect(self) -> None:
        # 清理连接
        pass

    async def send(self, chat_id, content, reply_to=None, metadata=None):
        # 调用平台 API 发消息
        return SendResult(success=True)

    async def on_message(self, raw_event):
        source = self.build_source(
            chat_id=chat_id,
            chat_type="dm",
            user_id=user_id,
            user_name=user_name,
        )

        event = MessageEvent(
            text=content,
            message_type=MessageType.TEXT,
            source=source,
            message_id=msg_id,
        )

        await self.handle_message(event)

所以 Hermes 的 adapter 责任是:

1. 连接平台
2. 接收平台事件
3. 把事件转成 MessageEvent
4. 把 Gateway 的回复发回平台

而 GatewayRunner 负责:

1. session key
2. authorization
3. slash command
4. running-agent guard
5. AIAgent 创建和执行

十二、Hermes 的权限与运行中断设计

Hermes 的 Gateway 不只是转发消息,它还做了较完整的运行控制。

官方文档描述了两级 message guard:

Level 1:Base adapter 检查 active session
Level 2:Gateway runner 检查 running agents

如果某个 session 里的 Agent 正在运行,新来的消息会被排队,并设置 interrupt event;而 /approve/deny/stop 这类命令可以绕过普通后台任务系统,直接进入处理流程,避免竞态。(Hermes Agent)

这非常适合“能执行任务”的 Agent。因为 Agent 可能正在跑一个长任务,例如查资料、写代码、执行 shell、调用工具。如果用户这时发了 /stop,网关必须能打断;如果用户发了 /approve,网关必须能让审批命令直达。

Hermes 的授权也分多层:平台级 allow-all、平台 allowlist、DM pairing、全局 allow-all,最后默认拒绝未授权用户。(Hermes Agent)

这对企业 IM 接入尤其重要。你不能让任何群成员都能让 Agent 执行工具,尤其是涉及文件、代码、数据库、内部系统时。


十三、Hermes 的平台接入为什么不像 OpenClaw 插件?

这是 OpenClaw 和 Hermes 最大的差异之一。

OpenClaw 的 channel 能以外部插件方式接入,插件通过 manifest 和 register(api) 挂载到 Gateway。

Hermes 虽然也有 plugin system,但其文档描述的插件主要用于 tools、hooks、CLI commands,以及 memory providers、context engines 等;platform adapter 则是 gateway/platforms/ 里的内置 adapter 体系。Hermes 的架构文档中,plugin system 的发现来源包括用户插件目录、项目插件目录和 pip entry points,但描述的是工具、hooks、CLI 命令、memory provider、context engine 等。(GitHub)

Hermes 添加 platform adapter 的官方指南也提醒:新增一个 messaging platform adapter 会涉及 20 多个文件,adapter 文件本身通常只占 40% 工作量。(Hermes Agent)

更直接的是,一个关于 Linear platform adapter 的 issue 提到:Hermes 有工具插件系统,但没有 platform adapter 插件系统;目前 established approach 是 built-in adapter pattern。(GitHub)

所以可以这样总结:

OpenClaw:
  平台接入 = 外部 channel plugin 也可以做

Hermes:
  平台接入 = 主要走仓库内 built-in adapter

这不是优劣问题,而是取舍不同。

OpenClaw 更适合做“多 IM 插件生态”。
Hermes 更适合做“统一 Agent 产品内置多平台入口”。


十四、两者架构对比

维度 OpenClaw Hermes
核心定位 self-hosted multi-channel gateway self-improving agent with messaging gateway
IM 接入方式 built-in channel + bundled/external channel plugin gateway/platforms/ 内置 adapter
插件发现 openclaw.plugin.json + package.json#openclaw Python 插件系统主要用于 tools/hooks/memory/context,不是平台 adapter
入站标准化 channel plugin 构造 ctxPayload adapter 构造 MessageEvent
会话路由 Gateway 是 sessions/routing/channel connections 的 single source of truth session key: agent:main:{platform}:{chat_type}:{chat_id}
群聊触发 channel 层处理 mention、whitelist adapter/Gateway 层结合授权与命令处理
出站回复 reply dispatcher + plugin deliver adapter send / send_message
扩展第三方 IM 更像 npm 插件式扩展 更像修改核心仓库新增 adapter

十五、如果我们自己做 Agent IM 网关,应该学什么?

如果你要做自己的 Agent 产品,比如“AI 标书助手接入飞书、钉钉、企微、OpenIM”,可以同时借鉴两者。

我建议的架构是:

im-gateway
 ├── core
 │   ├── message-envelope.ts
 │   ├── session-router.ts
 │   ├── auth-policy.ts
 │   ├── reply-dispatcher.ts
 │   ├── channel-directory.ts
 │   └── attachment-store.ts
 │
 ├── adapters
 │   ├── feishu
 │   ├── dingtalk
 │   ├── wecom
 │   ├── openim
 │   └── slack
 │
 ├── agent-clients
 │   ├── dify
 │   ├── coze
 │   ├── langchain
 │   └── custom-agent
 │
 └── plugins
     ├── manifest.json
     └── register.ts

核心接口可以这样设计:

interface IMAdapter {
  id: string

  start(ctx: GatewayContext): Promise<void>
  stop(): Promise<void>

  sendText(target: SendTarget, text: string): Promise<SendResult>
  sendImage?(target: SendTarget, imageUrl: string): Promise<SendResult>
  sendFile?(target: SendTarget, fileUrl: string): Promise<SendResult>
}

统一入站事件可以这样设计:

interface MessageEnvelope {
  provider: string
  accountId?: string

  chatType: "direct" | "group" | "channel" | "thread"
  chatId: string
  threadId?: string

  senderId: string
  senderName?: string

  messageId: string
  timestamp: number

  text: string
  rawEvent?: unknown
  attachments?: Attachment[]

  sessionKey: string
  originatingChannel: string
}

Agent 侧只需要看这个:

{
  "user_id": "feishu:ou_xxx",
  "session_id": "agent:main:feishu:group:oc_xxx",
  "text": "帮我总结一下这个投标文件",
  "attachments": []
}

而不是看这个:

{
  "open_id": "...",
  "union_id": "...",
  "chat_id": "...",
  "tenant_key": "...",
  "event_id": "...",
  "message": {
    "message_type": "text"
  }
}

这就是统一网关层的价值。


十六、最值得复用的 6 个设计点

1. Agent 核心必须平台无关

Hermes 的设计原则很清楚:同一个 AIAgent 服务 CLI、Gateway、ACP、Batch、API Server,平台差异留在入口层。(GitHub)

你自己的 Agent 也应该如此:

Agent 不知道消息来自飞书还是钉钉
Agent 只知道 user、session、text、attachments、tools

2. SessionKey 要稳定且可解释

可以参考 Hermes:

agent:{agent_id}:{platform}:{chat_type}:{chat_id}

比如:

agent:bid-assistant:feishu:group:oc_abc
agent:bid-assistant:dingtalk:direct:user_123
agent:contract-review:wecom:group:wr_xxx

3. 群聊必须有触发规则

OpenClaw 的 OpenIM 插件已经体现了这个逻辑:白名单、群聊 mention、requireMention 都在入站层做拦截。(GitHub)

建议你也做成配置:

{
  "channels": {
    "feishu": {
      "groups": {
        "*": {
          "requireMention": true
        }
      }
    }
  }
}

4. Reply Dispatcher 要独立出来

不要让 Agent 自己发 IM 消息。
应该是:

Agent 输出
  ↓
Gateway Reply Dispatcher
  ↓
Adapter.sendText()

OpenClaw 的 dispatchReplyWithBufferedBlockDispatcher 就是这个思路。(GitHub)

5. 权限控制放在 Gateway 层

Hermes 的授权链路包括平台 allow-all、平台 allowlist、DM pairing、全局 allow-all,最后默认拒绝。(Hermes Agent)

企业场景下可以再细分:

普通用户:只能问答
项目成员:可以读知识库
管理员:可以创建任务
超级管理员:可以调用外部系统写操作

6. Channel Directory 很重要

Agent 不能只知道一堆 ID,它需要知道:

{
  "feishu": {
    "groups": [
      {
        "id": "oc_xxx",
        "name": "投标项目A群"
      }
    ],
    "users": [
      {
        "id": "ou_xxx",
        "name": "张三"
      }
    ]
  }
}

这样用户才能自然地说:

把这份总结发到投标项目A群

而不是:

把这份总结发到 feishu:oc_xxx

结语

OpenClaw 和 Hermes 的共同点是:它们都没有让 Agent 直接绑定 IM 协议,而是通过 Gateway 做消息标准化、会话隔离、权限控制和回复分发。

它们的差异在于:

OpenClaw 更像插件平台:
  第三方 IM 可以通过 channel plugin 接入 Gateway。

Hermes 更像完整 Agent 产品:
  多平台 adapter 内置在 gateway/platforms/ 里,统一服务 AIAgent。

如果你要做自己的 Agent IM 接入层,最推荐的组合是:

OpenClaw 式插件机制
+
Hermes 式统一 MessageEvent / SessionKey / Authorization / Running-Agent Guard

最终目标是让 Agent 只关心一件事:

我收到了一条来自某个用户、某个会话的标准消息;
我处理它;
然后把标准回复交给 Gateway。

至于这条消息来自飞书、钉钉、企微、OpenIM、Slack 还是 Telegram,都应该由统一网关层和 IM 插件去解决。

/** Comments(0)*/

Loading comments...