OpenClaw 架构设计概览

本文以大纲形式介绍 OpenClaw 的整体架构、核心模块和数据流。目标是让读者快速理解系统如何把多个消息渠道(Telegram、Discord、WhatsApp 等)统一接入 AI Agent,并保证会话隔离、回复原路返回。


〇、前置知识

阅读本文前,建议了解以下概念:

核心概念

概念 说明
Pi OpenClaw 内置的 AI Agent 运行时,负责与 LLM(如 Claude)交互、管理会话上下文、执行工具调用。Pi 是 OpenClaw 的"大脑",所有消息最终都会交给 Pi 处理
Workspace 工作空间,一个隔离的配置单元。每个 workspace 有独立的 agent 配置、知识库、工具权限等。适合多租户或多人共享场景
Agent AI 助手实例,绑定特定的模型(如 claude-3-5-sonnet)、系统提示词、工具集。一个 workspace 可配置多个 agent
Channel 消息通道,如 Telegram、Discord、WhatsApp、Signal 等。每个通道有 inbound(接收消息)和 outbound(发送消息)两个方向
Hook 钩子函数,在消息处理的特定时机触发自定义逻辑。如 message_received hook 可在消息进入系统时执行预处理

平台术语对照

不同消息平台对"群组"的概念有不同叫法:

OpenClaw 统一术语 Telegram Discord Slack 说明
guild - Server(服务器) Workspace 平台的顶级组织单元
peer Chat(对话/群组) Channel(频道) Channel 消息的具体来源位置
team - - Team Slack 特有的组织概念

技术背景

概念 说明
Commander Node.js 生态中流行的命令行参数解析库,用于构建 CLI 工具。OpenClaw 使用它来注册 openclaw gatewayopenclaw agent 等子命令
interface(接口) TypeScript 中的类型定义结构,用于描述对象的"形状"(有哪些字段、什么类型),不包含具体实现代码
.impl. 后缀 OpenClaw 代码中的命名约定,表示"实现文件"。如 server.impl.tsserver.ts 接口的具体实现

一、架构分层总览

OpenClaw 采用五层架构,自上而下依次为:接入层 → 网关层 → 编排层 → 执行层 → 投递层

回写通道

投递层:回发

routeReply / resolveAgentDeliveryPlan

Channel Outbound

执行层:队列与 Agent

command-queue / lanes

runEmbeddedPiAgent / subscribe

runMessageAction

编排层:路由与派发

resolveAgentRoute

dispatchReplyFromConfig

getReplyFromConfig

网关层:常驻进程

server.impl

WebSocket + HTTP

辅助服务(Sidecars): ChannelManager / Hooks / 插件 / Cron

接入层:入口与通道

通道 Monitor

Telegram / Discord / Web / WebChat / 扩展

CLI

entry / run-main / program

层次职责速览

层次 核心职责 关键数据结构
接入层 接收各渠道原始消息,归一化为统一上下文 FinalizedMsgContext(详见下节)
网关层 常驻进程,管理通道生命周期、处理连接与请求 OpenClawConfig、会话 store
编排层 决定谁处理、怎么拿回复、往哪发 ResolvedAgentRouteMsgContext
执行层 按 session 并发控制,运行 Agent sessionKey(详见下节)、ReplyPayload
投递层 解析目标通道,调用 outbound 发送消息 AgentDeliveryPlan
sessionKey 格式说明

sessionKey 是会话的唯一标识符,采用冒号分隔的字符串,规范形式为:

agent : <agentId> : <rest>
  • agent:固定前缀,表示该 key 属于 agent 作用域,用于路由和会话存储。
  • agentId:代理 ID(如 mainwork),对应不同的 workspace 和会话存储。
  • rest:会话类型与上下文,随场景变化,常见形式如下:
场景 rest 示例 说明
主会话 main 默认主会话,多设备/多渠道可共用
群组 <channel>:group:<id> telegram:group:-1001234567890
频道/房间 <channel>:channel:<id> discord:channel:123456
私信(按渠道+对端) <channel>:direct:<peerId> telegram:direct:123456
子代理 subagent:<uuid> 子任务会话
线程 在 base 后追加 :thread:<id>:topic:<id> Slack/Discord 线程、Telegram 论坛话题

示例:

agent:main:main
agent:main:telegram:group:-1001234567890
agent:main:discord:channel:123456:thread:987654
agent:main:telegram:direct:123456

作用:同一 sessionKey 的消息会串行处理(通过 session lane 入队),避免同一会话内回复乱序。队列的 lane 名通常为 session: + sessionKey,与 sessionKey 本身不要混淆。

FinalizedMsgContext 核心字段

FinalizedMsgContext 是归一化后的消息上下文,包含:

字段 类型 说明
channel string 通道标识,如 telegramdiscord
accountId string 账户 ID,支持同通道多账号
peerId string 对话 ID,标识群组或私聊
userId string 发送者用户 ID
text string 消息正文
attachments array 附件列表(图片、文件等)
replyTo object 引用回复的原消息信息
timestamp number 消息时间戳

二、各层模块详解

2.1 接入层:入口与通道

接入层负责接收外部消息,并将其转换为系统内部统一的数据结构。

模块一览
模块 是什么 负责什么
entry 进程入口点 解析环境变量、profile 配置,校验实验性功能开关,然后转交 CLI 主控
run-main CLI 主控模块 加载环境、规范化命令行参数、构建命令树,决定走 CLI 逻辑还是启动 Gateway
program 命令树(基于 Commander 库) 注册所有子命令(gateway、channels、agent、message 等),解析并派发到具体 handler。Commander 是 Node.js 生态流行的 CLI 框架
通道 Monitor 各消息渠道的监听器 接收原始消息事件,解析发送者、正文、媒体等,调用路由得到 sessionKey,组装成 FinalizedMsgContext
ChannelPlugin 通道插件契约(接口定义) 定义 channel 的 id、meta、inbound/outbound、config 等接口,内置通道和扩展都实现此接口。接口是 TypeScript 的类型约束,确保所有通道插件有一致的 API

核心数据流

渠道原始事件 → Monitor 归一化 → resolveAgentRoute 算 sessionKey → FinalizedMsgContext → dispatchReplyFromConfig

2.2 网关层:常驻进程

网关层是系统的核心常驻进程,负责管理所有连接、通道和请求处理。

模块一览
模块 是什么 负责什么
server.impl 网关主进程(实现文件) 启动 HTTP/WebSocket 服务器,加载配置,注册 RPC 方法(agent/send/sessions 等),维护心跳与健康检查。.impl. 后缀表示这是接口的具体实现代码
WebSocket + HTTP 传输协议层 WS 处理连接握手、请求-响应帧、服务端推送;HTTP 提供静态资源和可选 API
辅助服务(Sidecars) 网关启动时一并初始化的辅助模块 按顺序启动:清理过期锁 → browser 控制 → Gmail watcher → 加载 hooks → 启动通道 → 启动插件服务。Sidecar 意为"边车",比喻依附主服务运行的辅助组件
ChannelManager 通道生命周期管理 提供 startChannels / stopChannel 等接口,按配置启动各通道的 monitor

启动流程

startGatewayServer
  → 读配置、TLS
  → startGatewaySidecars(启动所有辅助服务)
  → startChannels(启动各通道 monitor)
  → 挂载 WS/HTTP 路由

2.3 编排层:路由与派发

编排层是消息处理的核心决策层,决定"谁来处理"和"结果往哪发"。

三大模块职责
模块 是什么 负责什么
resolveAgentRoute 路由决策器 根据消息位置(channel/accountId/peer/guildId)和配置 bindings(静态路由表,如 { "agentId": "main", "match": { "channel": "telegram" } }),确定 agentId 和 sessionKey
dispatchReplyFromConfig 派发控制器 接收带 sessionKey 的消息上下文,做去重、hook 触发,调用 getReply,然后按回复类型投递
getReplyFromConfig 回复生成器 解析 agent 配置、模型、workspace,做上下文增强、命令鉴权,最终调用 Agent 获取回复

路由优先级(从高到低):

peer(指定对话)→ guild+roles(群组+角色)→ team → account → channel → default

优先级术语解释

  • peer:最精细粒度,精确匹配某个对话(如 Discord 的某个频道、Telegram 的某个群组)
  • guild+roles:匹配群组级别,可进一步按用户角色细分(如 Discord 服务器中只让管理员触发 agent)
  • team:组织级别匹配(主要用于 Slack)
  • account:账户级别,匹配某个渠道下的特定账号
  • channel:通道级别,如"所有 Telegram 消息"
  • default:兜底配置,当以上都不匹配时使用

数据流

FinalizedMsgContext
  → dispatchReplyFromConfig
    → 去重 / message_received hook / TTS 前置
    → getReplyFromConfig
      → 解析 agentId、模型、workspace
      → 命令分支 或 runPreparedReply(调 Agent)
    → 按 payload 类型投递(routeReply / dispatcher.send*)

bindings 配置说明与示例:bindings 是顶层的静态路由表,把「某通道、某账号、某群/私聊/频道/身份」固定绑定到某个 agent;未匹配任何 binding 时走 default agent。

  • 配置位置:顶层 bindings(与 agentschannels 并列;历史曾用 routing.bindings,已迁移)。
  • 单条结构{ agentId: string, match: { channel, accountId?, peer?, guildId?, teamId?, roles? } }channel 必填;其余可选,用于收窄匹配范围。
  • match 各字段含义
字段 必填 含义 典型取值
channel 通道 ID,该 binding 只对该通道生效 telegramdiscordwhatsappslack
accountId 账号范围:不写或空 = 仅匹配默认账号 "default""*" = 该通道下任意账号;写具体 ID = 仅该账号 "default""*""personal""T0AAAA"
peer 精确到某对话:私聊/群/频道,由 kind+id 决定 { "kind": "direct"|"group"|"channel", "id": "<对话ID>" }
guildId 群组/服务器 ID(如 Discord Server),与 channel 一起限定「哪个服务器」 Discord 的 guild ID
teamId 工作区/团队 ID(如 Slack Workspace) Slack 的 team ID
roles 角色 ID 列表(如 Discord 角色),消息发送者须至少具备其一才匹配;常与 guildId 同用 ["1111111111111111111"]
  • 路由优先级(从高到低,先匹配到的 binding 生效):
    1. binding.peer:当前消息的对话(peer)与某条 binding 的 peer 完全一致。
    2. binding.peer.parent:当前是子对话(如 Discord 线程)时,用父对话的 peer 再匹配一次。
    3. binding.guild+roles:同一 guild 且发送者带有 binding 中某角色(如 Discord 管理员)。
    4. binding.guild:同一 guild,且该 binding 未写 roles
    5. binding.team:同一 team(如 Slack 工作区)。
    6. binding.account:同一通道下该账号,且 binding 写了具体 accountId(非 *)。
    7. binding.channel:同一通道,binding 为 accountId: "*" 的通道级兜底。
    8. default:以上都不中则用配置的默认 agent。

下面按匹配粒度给出详细示例,并逐条说明含义(ID 均为占位,需按实际替换)。

1)仅 channel(通道级兜底)

{ "agentId": "main", "match": { "channel": "telegram", "accountId": "*" } }

含义:所有来自 Telegram、任意账号的消息,只要没有更具体的 binding 命中,都由 main 处理。accountId: "*" 表示不限定账号,属于通道级规则。

2)channel + accountId(账号级)

{ "agentId": "home", "match": { "channel": "whatsapp", "accountId": "personal" } },
{ "agentId": "work", "match": { "channel": "whatsapp", "accountId": "biz" } }

含义:同一 WhatsApp 通道下,personal 账号的对话走 homebiz 账号的对话走 work。多账号时用 accountId 区分不同 bot/身份。

3)channel + accountId + peer(对话级,最细)

{ "agentId": "support", "match": {
  "channel": "telegram",
  "accountId": "default",
  "peer": { "kind": "group", "id": "-1001234567890" }
} }

含义:仅当消息来自 Telegram、默认账号、且对话是群组 -1001234567890 时,由 support 处理。其他群或私聊不匹配本条,会继续匹配更粗的规则或 default。

4)Discord:某服务器的某频道(peer + guildId)

{ "agentId": "dev-bot", "match": {
  "channel": "discord",
  "accountId": "default",
  "peer": { "kind": "channel", "id": "1234567890123456789" },
  "guildId": "9876543210987654321"
} }

含义:仅当消息来自 Discord、默认账号、且来自 guild 9876543210987654321 下的频道 1234567890123456789 时,由 dev-bot 处理。同一 bot 在不同服务器里可指向不同 agent。

5)Discord:某服务器 + 角色(guildId + roles)

{ "agentId": "ops", "match": {
  "channel": "discord",
  "accountId": "default",
  "guildId": "9876543210987654321",
  "roles": ["1111111111111111111"]
} }

含义:同一 Discord 服务器下,只有发送者拥有角色 ID 1111111111111111111 的消息由 ops 处理;其他用户仍走更粗的 binding 或 default。常用于「仅管理员/某角色用专用 agent」。

6)Discord:仅 guild(整服一个 agent)

{ "agentId": "sonnet", "match": {
  "channel": "discord",
  "accountId": "default",
  "guildId": "123456789012345678"
} }

含义:该 Discord 服务器下所有频道、所有用户的消息都由 sonnet 处理(未再限定 peer 或 roles)。

7)Slack:某工作区下某私聊(teamId + peer)

{ "agentId": "main", "match": {
  "channel": "slack",
  "accountId": "T0AAAA",
  "teamId": "T0AAAA",
  "peer": { "kind": "direct", "id": "U1BBBB" }
} }

含义:仅当消息来自 Slack 工作区 T0AAAA、账号 T0AAAA、且是与用户 U1BBBB 的私聊时,由 main 处理。

8)组合示例:先细后粗(推荐顺序)

同通道下建议先写 peer/角色等细规则,再写 account/channel 粗规则,这样先匹配到的更具体 binding 会生效:

"bindings": [
  { "agentId": "support", "match": { "channel": "telegram", "accountId": "default", "peer": { "kind": "group", "id": "-1001234567890" } } },
  { "agentId": "main", "match": { "channel": "telegram", "accountId": "default" } }
]

含义:Telegram 默认账号下,群 -1001234567890support;其余对话(其他群、私聊)→ main。若两条顺序颠倒,则「仅 channel+account」那条会先命中,群消息也会被误判给 main

  • 注意agentId 必须在 agents.list 中存在;match.channel 需与通道 ID 一致(小写、无空格)。路由时先按通道+账号筛出候选 binding(getEvaluatedBindingsForChannelAccount),再按上述优先级选第一条匹配。

  • 完整配置文件示例(可放入 ~/.openclaw/openclaw.json;ID 为占位):

{
  "agents": {
    "list": [
      { "id": "main", "model": "claude-sonnet-4-20250514" },
      { "id": "support", "model": "claude-sonnet-4-20250514" }
    ]
  },
  "bindings": [
    { "agentId": "main", "match": { "channel": "telegram", "accountId": "default" } },
    {
      "agentId": "support",
      "match": {
        "channel": "telegram",
        "accountId": "default",
        "peer": { "kind": "group", "id": "-1001234567890" }
      }
    }
  ],
  "channels": {
    "telegram": {
      "accounts": {
        "default": { "botToken": "YOUR_BOT_TOKEN" }
      }
    }
  }
}

含义:默认 Telegram 消息由 main 处理;仅当消息来自群组 -1001234567890 时由 support 处理(peer 匹配优先于 account/channel 级规则)。


2.4 执行层:队列与 Agent

执行层负责 Agent 的实际运行,并通过队列机制保证并发安全。

模块一览
模块 是什么 负责什么
command-queue / lanes 并发控制队列 维护多条 lane(main/cron/subagent/nested + per-session),保证同一 session 串行,不同 session 可并行
runEmbeddedPiAgent Pi Agent 运行器 管理重试、上下文窗口压缩,订阅 Pi 会话流,汇总输出为 payload
subscribeEmbeddedPiSession 会话订阅器 把 Pi 的流式事件转换为 onPartialReply/onBlockReply/onToolResult 等回调
runMessageAction 消息动作执行器 根据 payload 中的 send action,解析目标并调用通道 outbound 发送
Lane 并发模型

一次 Agent 请求的入队

入站消息

1. enqueueSession
2. enqueueGlobal(main)
3. 执行 runEmbeddedAttempt

command-queue 的 Lane 划分

Session Lane(每会话一条)

session:agent:main:telegram:...

session:agent:main:discord:...

全局 Lane(按用途隔离)

main\n默认 maxConcurrent=4

cron\n定时任务

subagent\n子任务

nested\n嵌套调用

双层入队机制

入队的都是任务(一个返回 Promise 的异步函数 task)。一次入站消息会先后占两个队:先占 session lane,再占 main lane,然后才执行真正的 Agent 逻辑(runEmbeddedAttempt)。

  • 第一层:Session lane(enqueueSession

    • 入队内容:针对该会话的「整次处理」任务,即「先去 main 排队,再执行本次 runEmbeddedAttempt」的包装任务。代码形态为 enqueueSession(() => enqueueGlobal(async () => { ... runEmbeddedAttempt(...) })) 中传给 enqueueSession 的那个函数。
    • 设计目的:同一 sessionKey 下多条消息按到达顺序排队,同一会话串行,避免同一对话里多轮回复交错、会话状态错乱。
    • 粒度:每个 sessionKey 对应一条 session lane,lane 名为 session: + sessionKey(例如 session:agent:main:telegram:direct:123),不同会话的 lane 相互独立,可并行。
  • 第二层:Main lane(enqueueGlobal("main")

    • 入队内容:单次 Pi Agent 执行任务,即上述包装任务内部传给 enqueueGlobalasync () => { ... runEmbeddedAttempt(...) ... },包含模型解析、会话加载、调用 LLM、工具调用、产出回复等整段逻辑。
    • 设计目的:限制全局并发数(main 默认 maxConcurrent=4),避免同时跑过多 Pi/LLM 请求导致资源打满或限流。
    • 粒度:全局共用一个 main lane,所有会话的「已通过 session 队」的请求在这里再排一次队。

设计思路小结

层级 入队的「东西」 作用
Session lane 该会话的「整次处理」任务(内部会再入 main 并执行 runEmbeddedAttempt 同一会话串行,避免同一对话内多轮交错
Main lane 单次 runEmbeddedAttempt 执行任务 控制全局并发,避免同时跑太多 Agent

因此:先入 session lane 保证会话内顺序,再入 main lane 保证全局并发上限;两者结合既避免单会话乱序,又避免多会话同时把系统撑爆。


2.5 投递层:回发

投递层负责把 Agent 产出的内容发送到正确的目标。

模块一览
模块 是什么 负责什么
routeReply 回复路由器 根据 payload、channel、to 等参数,找到对应通道的 outbound 并发送
resolveAgentDeliveryPlan 投递目标解析器 当目标未明确时,根据 session 的 lastChannel/lastTo 等算出 resolvedChannel 和 resolvedTo
Channel Outbound 通道发送实现 调用各渠道 API(Telegram Bot API、Discord REST 等)真正发送消息

投递流程

ReplyPayload
  → runMessageAction / routeReply
  → resolveAgentDeliveryPlan(解析目标)
  → Channel Outbound(实际发送)

三、核心流程:一条消息的完整旅程

通道 Outbound routeReply LLM API Agent 执行 命令队列 getReply 派发 通道 Monitor 通道 Outbound routeReply LLM API Agent 执行 命令队列 getReply 派发 通道 Monitor 阶段一:入站与派发 阶段二:入队与 Agent 执行 阶段三:回复投递 FinalizedMsgContext 1 去重 / 会话查询 / hook 2 ctx, opts, cfg 3 入队(sessionKey / lane) 4 runEmbeddedPiAgent 5 subscribeEmbeddedPiSession(订阅流式/工具结果) 6 加载会话、整理上下文、effectivePrompt 7 activeSession.prompt() → streamFn 请求 8 流式回复 / 工具调用轮次 9 payloads 10 payload 11 outbound.send 12 用户看到回复 13

流程要点

  1. 通道只关心:把原始事件变成 FinalizedMsgContext,以及收到 outbound 调用时真正发送
  2. 中间层关心:谁处理(路由)、谁执行(队列 + Agent)、发到哪里(delivery plan + routeReply)
  3. 会话隔离:通过 sessionKey + lane 保证同一会话的消息串行处理

四、关键设计亮点

4.1 通道插件化

内置通道(Telegram、Discord、Web 等)和扩展通道(extensions/*)实现同一套 ChannelPlugin 接口。Gateway 只认"channelId + outbound",新增通道无需修改核心代码。

4.2 Lane 并发控制

  • 全局 lane(main/cron/subagent)按用途隔离
  • Session lane 保证同一会话串行
  • 双层入队机制防止并发冲突

4.3 会话键与路由

sessionKey 把 agent + channel + account + peer 绑定在一起,配合 bindings 配置实现:

  • 多 agent 场景:不同群/私聊可绑定不同 agent
  • 多账户场景:同一渠道多账号独立会话

4.4 Delivery Plan

resolveAgentDeliveryPlan 统一计算回复目标,避免:

  • 回复错通道
  • 回复错会话
  • implicit/explicit 目标混淆

4.5 Streaming 与 Final-only

  • Agent 流式输出可推送给 Control UI(OpenClaw 的管理界面,用于配置 agent、查看会话、监控状态)
  • 外发通道(Telegram/Discord 等)只发最终结果,避免把思考过程暴露给用户

五、源码阅读入口

推荐从以下文件入手,按顺序阅读:

  1. 入口src/entry.tssrc/cli/run-main.ts
  2. Gateway 启动src/gateway/server.impl.ts(关注 startGatewaySidecars
  3. 消息处理主线src/auto-reply/reply/dispatch-from-config.tssrc/auto-reply/reply/get-reply.ts
  4. 路由src/routing/resolve-route.ts
  5. 并发控制src/process/command-queue.ts
  6. 投递src/auto-reply/reply/route-reply.ts

六、术语速查

术语 含义
Pi OpenClaw 内置的 AI Agent 运行时,负责与 LLM 交互、管理会话上下文、执行工具调用
Workspace 工作空间,隔离的配置单元,包含 agent 配置、知识库、工具权限等
Agent AI 助手实例,绑定模型、系统提示词、工具集
sessionKey 会话唯一标识,规范格式为 agent:<agentId>:<rest>,其中 rest 随会话类型变化(如 main、telegram:group:id、discord:channel:id、channel:direct:peerId 等)。同一 sessionKey 串行处理,lane 名为 session: + sessionKey。
lane 并发队列的划分单位,用于隔离不同类型的任务
FinalizedMsgContext 归一化后的消息上下文,包含 channel、accountId、peerId、userId、text、attachments 等字段
ResolvedAgentRoute 路由结果,包含 agentId、sessionKey、mainSessionKey
ReplyPayload Agent 产出的回复内容,核心字段包括:type(回复类型,如 text/image/markdown)、text(文本内容)、to(目标地址,可选)、channel(目标通道,可选)、attachments(附件列表,可选)。
outbound 通道的发送接口实现
bindings 配置项,定义哪个位置(群/私聊/账号)使用哪个 agent
Hook 钩子函数,在消息处理的特定时机触发自定义逻辑
Control UI OpenClaw 的管理界面,用于配置 agent、查看会话、监控状态。Agent 的流式输出可推送到 Control UI
guild 平台的顶级组织单元(Discord Server、Slack Workspace 等)
peer 消息的具体来源位置(Discord 频道、Telegram 群组等)
team Slack 特有的组织概念
辅助服务(Sidecars) 与网关主进程同时启动的辅助模块,包括 ChannelManager、Hooks、插件服务、定时任务等
Commander Node.js 命令行参数解析库,OpenClaw 用它构建 CLI 命令树
Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐