Day 5 | OpenClaw 多 Agent 路由:一个 Gateway 托管多个 AI 大脑

系列:《从 0 到 1 拆解 AI Agent 框架:OpenClaw 技术深度解析》


前言

想象一个场景:你有一个个人助手 Agent,同时你还部署了一个专门处理代码审查的 Agent,以及一个管理家庭自动化的 Agent。它们需要接入同一个 Telegram 账号,但各自有独立的"大脑"和记忆。

这就是 多 Agent 路由 要解决的问题:一个 Gateway,多个 AI 大脑,消息如何精准投递?

路由看起来简单,但实现起来有不少细节:怎么区分消息属于哪个 Agent?跨 Agent 的消息怎么传递?不同 Agent 如何共享同一个渠道账号却互不干扰?本文将逐一拆解。


一、架构概览:Gateway 与 Agent 的关系

先明确核心概念。

1.1 Gateway vs Agent

在 OpenClaw 中:

  • Gateway 是消息的"交换机"——它负责接收来自各渠道(Telegram/Discord/WhatsApp…)的消息,并将其路由到正确的 Agent。它不做 AI 推理,只做路由。

  • Agent 是消息的"处理器"——每个 Agent 有自己的配置(使用什么模型、什么系统提示词、什么工具),负责接收消息、调用 LLM、返回回复。

Telegram ─┐
Discord  ──┤  Gateway  ──┬── Agent A (个人助手)
WhatsApp ─┘              ├── Agent B (代码审查)
                         └── Agent C (家庭自动化)

1.2 一对多 vs 多对多

最简单的部署是一个 Gateway + 一个 Agent,这是大多数人的起点。

但 OpenClaw 支持更复杂的拓扑:

  • 一个 Gateway + 多个 Agent:共享渠道账号,按规则分发消息
  • 多个 Gateway + 多个 Agent:完全隔离的部署,适合多用户场景

本文聚焦最有意思的场景:一个 Gateway,多个 Agent


二、Binding:路由规则的核心

消息从 Telegram 进来,Gateway 怎么知道该交给哪个 Agent?答案是 Binding(绑定)

2.1 什么是 Binding?

Binding 是一条路由规则,定义了:

“来自渠道 X、用户 Y 的消息,交给 Agent Z 处理”

配置示例:

agents:
  - id: personal-assistant
    bindings:
      - channel: telegram
        userId: "123456789"   # 我自己的 Telegram ID

  - id: code-reviewer
    bindings:
      - channel: discord
        guildId: "my-work-server"
        channelId: "code-review"

  - id: home-automation
    bindings:
      - channel: telegram
        userId: "987654321"   # 家里另一个账号

2.2 Binding 的匹配优先级

当一条消息可能匹配多个 Binding 时(比如同一个用户在不同场景),需要有清晰的优先级规则:

精确匹配 > 通配匹配 > 默认 Agent

具体来说:

  • userId + channelId 都匹配 → 优先级最高
  • 只有 guildId 匹配 → 次之
  • 没有任何精确匹配 → 走默认 Agent(如果配置了的话)

2.3 动态 Binding vs 静态 Binding

除了配置文件里的静态 Binding,OpenClaw 还支持运行时动态创建 Binding

典型场景:用户发 /start 命令,Gateway 动态创建一个新 Session 并绑定到指定 Agent:

// 用户发送 /start code-review
// Gateway 解析命令,动态创建 Binding
await bindingManager.create({
  agentId: 'code-reviewer',
  channel: 'telegram',
  userId: message.userId,
  sessionKey: `code-reviewer:telegram:${message.userId}`,
  ttl: 3600 * 24,  // 24小时后过期
});

这让用户可以在运行时"切换"使用不同的 Agent,而不需要重新配置。


三、Session 隔离:多 Agent 的记忆边界

多 Agent 场景下,Session 的隔离尤为重要。

3.1 Session Key 的 Agent 前缀

还记得 Day 3 讲的 Session Key 格式吗?

{agentId}:{channelId}:{userId}

agentId 作为前缀,天然保证了不同 Agent 的 Session 完全隔离:

personal-assistant:telegram:user_123  ← 个人助手的记忆
code-reviewer:telegram:user_123       ← 代码审查的记忆

同一个用户(user_123)和不同 Agent 的对话,存储在不同的 JSONL 文件中,互不干扰。

3.2 上下文不跨 Agent 共享

这是一个重要的安全和隐私设计:Agent 之间默认不共享上下文

你告诉个人助手的私人信息,不会出现在代码审查 Agent 的上下文里。每个 Agent 只看得到自己 Session 里的历史。

当然,如果你明确需要跨 Agent 共享信息,可以通过 Workspace 文件(比如 MEMORY.md)来传递——这是一种受控的、显式的信息共享机制。


四、消息分发:从 Gateway 到 Agent

一条消息进来,Gateway 的分发流程如下:

1. 接收消息
   ↓
2. 解析来源(channel + userId + groupId…)
   ↓
3. 查询 Binding 规则,确定目标 Agent
   ↓
4. 构造投递包(消息内容 + 元数据 + sessionKey)
   ↓
5. 将投递包发送到 Agent 的消息队列
   ↓
6. Agent 异步处理,回复通过 Gateway 发回

4.1 消息队列:解耦 Gateway 和 Agent

Gateway 和 Agent 之间通过消息队列通信,而不是直接调用。这个设计有几个好处:

好处一:解耦。Gateway 不需要知道 Agent 在哪里、是否在线。只要把消息放进队列,就完成了职责。

好处二:背压。如果 Agent 处理速度跟不上消息速度,队列可以缓冲,避免丢消息。

好处三:可观测。队列里的消息可以被监控,方便排查问题。

4.2 本地部署的简化实现

对于单机部署,OpenClaw 用内存中的事件总线替代正式的消息队列:

// Gateway 发送消息
eventBus.emit(`agent:${agentId}:message`, {
  sessionKey,
  content: message.content,
  role: 'user',
  metadata: { channel, userId, messageId }
});

// Agent 监听消息
eventBus.on(`agent:${agentId}:message`, async (payload) => {
  await agent.processMessage(payload);
});

这在单机上运行得很好,扩展到多机时可以把 EventBus 替换成 Redis Pub/Sub 或 RabbitMQ,上层逻辑不变。


五、多账号支持:同一 Agent,多个渠道身份

一个 Agent 可以同时出现在多个渠道——比如同一个助手,在 Telegram 用一个号,在 Discord 用另一个号。

5.1 Channel Account 管理

每个渠道账号在 OpenClaw 中叫做 Channel Account

channelAccounts:
  - id: tg-personal
    provider: telegram
    token: "BOT_TOKEN_1"

  - id: discord-work
    provider: discord
    token: "BOT_TOKEN_2"

  - id: tg-family
    provider: telegram
    token: "BOT_TOKEN_3"

Agent 通过 Binding 关联到一个或多个 Channel Account:

agents:
  - id: personal-assistant
    channelAccounts: [tg-personal, discord-work]  # 在两个渠道都活跃

5.2 回复时的渠道感知

Agent 处理完消息后,回复需要发回原来的渠道,而不是随便选一个。

OpenClaw 在 Session 中记录了消息来源:

// 每条消息都携带来源信息
interface InboundMessage {
  content: string;
  source: {
    channelAccountId: string;  // 从哪个渠道账号来的
    channelType: string;       // telegram/discord/...
    userId: string;
    messageId: string;
  }
}

// 回复时,使用相同的 channelAccountId 发送
async function reply(sessionKey: string, content: string) {
  const source = getLastMessageSource(sessionKey);
  await channelManager.send(source.channelAccountId, source.userId, content);
}

这确保了用户在哪个渠道问,就在哪个渠道收到回答。


六、Sub-Agent 路由:Agent 内部的任务分发

除了 Gateway 层面的多 Agent 路由,OpenClaw 还支持 Agent 内部的 Sub-Agent 路由

6.1 什么是 Sub-Agent?

当主 Agent 遇到需要"专注处理"的任务时,可以召唤一个 Sub-Agent:

// 主 Agent 调用 sessions_spawn 创建子 Agent
const subSession = await sessions.spawn({
  agentId: 'code-reviewer',  // 使用专门的代码审查 Agent
  task: '请审查这段 Python 代码:\n' + code,
  mode: 'run',  // 一次性任务
  cleanup: 'delete',  // 完成后删除 Session
});

Sub-Agent 有完全独立的执行环境:独立的 Session、独立的上下文、独立的工具调用历史。

6.2 Sub-Agent 的结果返回

Sub-Agent 完成任务后,结果会推送回主 Session:

主 Agent Session
  → 调用 sessions_spawn
    → 创建 Sub-Agent Session
      → Sub-Agent 执行任务(可能有多轮对话)
      → Sub-Agent 完成,生成最终结果
    → 结果推送到主 Session
  → 主 Agent 接收结果,继续处理

主 Agent 不需要等待(阻塞),Sub-Agent 完成后会主动通知。这是异步编排的典型模式。

6.3 Agent 间的信任边界

Sub-Agent 的权限由父 Agent 决定,不能超越父 Agent 的权限范围:

  • 父 Agent 可以给 Sub-Agent 更少的工具权限(沙箱化)
  • 父 Agent 不能给 Sub-Agent 自己没有的权限(防权限提升)

这个设计参考了 Unix 的进程权限模型:子进程不能拥有比父进程更高的权限。


七、路由的安全性:防止越权访问

多 Agent 场景下,安全性是个重要议题。

7.1 Agent 隔离的安全价值

不同 Agent 的 Session 完全隔离,这不只是功能设计,也是安全设计:

  • 防止信息泄露:代码审查 Agent 无法读取个人助手的对话历史
  • 防止权限提升:低权限 Agent 无法调用高权限 Agent 的工具
  • 故障隔离:一个 Agent 崩溃不影响其他 Agent

7.2 消息来源验证

Gateway 在路由消息前,会验证消息来源的合法性:

async function routeMessage(message: InboundMessage): Promise<void> {
  // 1. 验证消息签名(防止伪造)
  if (!verifyMessageSignature(message)) {
    logger.warn('Invalid message signature', message);
    return;
  }

  // 2. 检查发送者是否有权限访问目标 Agent
  const binding = findBinding(message);
  if (!binding) {
    logger.info('No binding found, using default agent or dropping');
    return;
  }

  // 3. 路由到目标 Agent
  await dispatch(binding.agentId, message);
}

7.3 频率限制

为了防止滥用,每个 (channelAccount, userId) 对都有独立的频率限制:

  • 单用户每分钟最多 N 条消息
  • 超过限制的消息被静默丢弃(或返回提示)
  • 不同 Agent 的频率限制独立计算

八、实战:配置一个多 Agent 部署

理论讲完了,来看一个实际的配置示例。

假设你想部署:

  1. 个人助手:接 Telegram 私聊,使用 Claude Sonnet,有记忆功能
  2. 代码审查机器人:接 Discord 某个频道,使用 GPT-4o,专注代码质量
# openclaw.config.yaml

gateway:
  port: 3000
  token: "your-gateway-token"

channelAccounts:
  - id: tg-me
    provider: telegram
    token: "${TELEGRAM_BOT_TOKEN}"

  - id: discord-work
    provider: discord
    token: "${DISCORD_BOT_TOKEN}"

agents:
  - id: personal-assistant
    model: anthropic/claude-sonnet-4
    systemPrompt: |
      你是一个私人助手。你有访问用户文件和工具的权限。
    workspace: ~/assistant-workspace
    bindings:
      - channelAccountId: tg-me
        # 不限制 userId,接受所有私聊
    tools:
      - read
      - write
      - web_search
      - memory_search

  - id: code-reviewer
    model: openai/gpt-4o
    systemPrompt: |
      你是一个专业的代码审查助手。只关注代码质量、安全性和最佳实践。
    bindings:
      - channelAccountId: discord-work
        channelId: "1234567890"   # 只监听特定频道
    tools:
      - read  # 只允许读文件,不允许写

这个配置实现了:

  • 两个 Agent 使用不同的模型
  • 两个 Agent 有不同的工具权限(代码审查只读)
  • 消息来源不同,互不干扰

九、设计哲学:显式优于隐式

回顾 OpenClaw 的多 Agent 路由设计,有一个贯穿始终的原则:

路由规则要显式、可读、可调试。

  • Binding 是配置文件中的 YAML,而不是隐藏在代码里的逻辑
  • Session Key 把路由信息编码进 ID 本身,一眼可见
  • Agent 之间的信息共享通过 Workspace 文件显式进行,而不是隐式传递

这让整个系统的行为可预测、可审计。当出现问题时,你可以通过查看配置文件和日志,快速定位是哪个 Agent 处理了哪条消息。


小结

本文拆解了 OpenClaw 的多 Agent 路由机制:

机制 实现方案 核心思想
消息路由 Binding 规则匹配 显式配置,精确投递
Session 隔离 agentId 作为 Key 前缀 身份即边界
多渠道支持 Channel Account 管理 一 Agent 多身份
Sub-Agent 异步任务分发 + 结果推送 关注点分离
安全性 消息验证 + 权限隔离 最小权限原则

下一篇,我们将进入工具系统与 Skills——AI 是怎么"动手"的?工具调用的完整生命周期是什么样的?


作者:一个在折腾 AI Agent 框架的工程师
系列索引:[Day 1 架构概览] | [Day 2 Gateway] | [Day 3 Agent 运行时] | [Day 4 流式输出] | Day 5 多 Agent 路由 | Day 6 工具系统 → 敬请期待

如果这篇文章对你有帮助,欢迎点赞收藏 🎯

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐