OpenClaw源码解析(二):整体架构
上一篇回顾
这一篇怎么读
这篇不是“背目录树”,而是要完成两件事
-
把上一篇的产品边界映射到真实目录 -
建立一个足够准确、但不过度简化的架构地图
这里的 5 层模型是学习时用的心智模型,不是运行时严格的分层图。真正执行链在很多地方会跨层,例如:
-
channel ingress 直接把消息推入 routing / agent -
agent runner 会反过来调用 tools、memory、context engine -
plugin 体系横切多个子系统
所以你要一边看“分层”,一边记住“真实控制流会跨层”。
第一步:先从根目录判断项目形态
根目录里最重要的不是文件数量,而是这些分区:
src/:主运行时代码extensions/:工作区扩展包apps/:客户端和平台应用docs/:文档与产品说明scripts/:构建、发布、生成脚本
这已经说明 OpenClaw 不是单体 CLI,而是:
-
一个主 runtime -
一组可安装扩展 -
若干平台客户端
架构全景(5 层模型)
┌─────────────────────────────────────────────────┐│ CLI / Commands ││ src/cli/ src/commands/ ││ 用户直接交互层:命令解析、参数处理 │├─────────────────────────────────────────────────┤│ Gateway(控制平面) ││ src/gateway/ ││ WebSocket 服务器、协议帧、设备配对、认证 │├─────────────────────────────────────────────────┤│ Agent / AI 层 ││ src/agents/ src/plugins/ src/plugin-sdk/ ││ LLM 调用、工具执行、子 Agent、认证轮换 │├─────────────────────────────────────────────────┤│ Channels(消息通道) ││ src/channels/ src/telegram/ src/discord/ ... ││ 20+ 平台的统一适配器,消息入站/出站 │├─────────────────────────────────────────────────┤│ Infrastructure ││ src/infra/ src/config/ src/security/ ││ 配置、文件系统、设备信任、环境变量 │└─────────────────────────────────────────────────┘
这张图的价值在于先给你一个入口排序:
-
先理解 CLI -
再理解 channels + routing -
然后进入 agents -
最后再补 gateway、plugins、infra
如果一上来直接钻 src/gateway/,很容易把控制平面误当成主执行链。
第二步:把 src/ 映射成几个真正重要的模块簇
1. 启动与命令层
src/entry.tssrc/cli/src/commands/
这一层解决的是:
-
程序怎么启动 -
命令怎么注册 -
CLI 怎么把请求送入 runtime
2. 消息接入与通道层
src/channels/src/telegram/src/discord/src/slack/src/signal/src/imessage/src/web/src/whatsapp/
这一层解决的是:
-
不同平台的消息如何被标准化 -
哪些能力由统一接口表达 -
哪些仍保留平台差异
3. 路由与 Agent 运行层
src/routing/src/agents/src/agents/pi-embedded-runner/src/agents/tools/
这一层才是“消息变成一次 agent turn”的核心。
4. 上下文与记忆层
src/context-engine/src/memory/src/sessions/
这一层解释:
-
当前会话上下文如何控制 -
长期记忆如何检索 -
session 文件如何参与运行时
5. 横切扩展层
src/plugins/src/plugin-sdk/extensions/*
这是整个仓库最容易被低估的一层,因为它不是单独一个功能,而是很多能力的扩展边界。
目录职责速查表
|
|
|
|---|---|
src/entry.ts |
|
src/cli/ |
|
src/commands/ |
|
src/gateway/ |
|
src/agents/ |
|
src/plugins/ |
|
src/plugin-sdk/ |
|
src/channels/ |
|
src/telegram/ |
|
src/discord/ |
|
src/slack/ |
|
src/signal/ |
|
src/imessage/ |
|
src/web/ |
|
src/config/ |
|
src/infra/ |
|
src/security/ |
|
src/media/ |
|
src/browser/ |
|
src/auto-reply/ |
|
src/routing/ |
|
src/context-engine/ |
|
src/memory/ |
|
src/sessions/ |
|
第三步:看 extensions/,确认插件不是装饰层
extensions/ 不是 demo 目录,而是正式工作区的一部分。
从现有结构看,里面至少有几类插件:
-
channel plugins:如 msteams、matrix、zalo -
memory plugins:如 memory-core、memory-lancedb -
tool / runtime plugins:如 llm-task、voice-call -
shared / infra plugins:如 shared
这说明 OpenClaw 的“可扩展”不是营销说法,而是仓库结构级事实。
扩展系统(workspace packages)
extensions/├── msteams/ MS Teams 通道├── matrix/ Matrix 通道├── feishu/ 飞书通道├── zalo/ Zalo 通道├── voice-call/ 语音通话工具├── memory-core/ 内存后端(基础)├── memory-lancedb/ 内存后端(LanceDB 向量存储)├── llm-task/ LLM 任务 Agent 工具└── ...(共 40+ 个)
插件机制:
-
从 npm 安装: openclaw plugins add @openclaw/msteams -
本地开发: ~/.openclaw/plugins/目录 -
运行时依赖注入:插件接收 PluginRuntime上下文
第四步:从 package.json exports 判断公开边界
package.json 的 exports 很值得看,因为它告诉你:
-
哪些模块被当成正式 API -
哪些只是内部实现
最关键的事实有两个:
1. ./plugin-sdk/* 出口非常多
这说明插件生态不是补充件,而是设计目标之一。
2. ./cli-entry 单独导出
这说明 CLI 入口本身也是被刻意包装和稳定暴露的。
npm 包导出结构
json
{ ".": "./dist/index.js", // 主入口 "./plugin-sdk": "./dist/plugin-sdk/index.js", // 完整 SDK "./plugin-sdk/core": "...", // 核心类型 "./plugin-sdk/telegram": "...", // Telegram 专用 "./plugin-sdk/discord": "...", // Discord 专用 // ... 每个通道都有独立入口}
为什么这样设计? → 按需加载,不同通道的依赖互不影响
技术栈
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
关键设计模式
1. Channel Plugin Adapter(最重要)
每个消息平台实现同一套 ChannelPlugin 接口。核心层不了解 Telegram/Slack 的细节,只调用抽象接口。
2. 懒加载命令注册
CLI 命令不在启动时全部加载,只有被调用时才 await import()。
3. 依赖注入(PluginRuntime)
插件通过 PluginRuntime 上下文获取能力,而不是直接 import 内部模块。→ 实现了外部插件与内部实现的解耦
4. .runtime.ts 边界
需要懒加载的通道使用 *.runtime.ts 文件作为边界。→ 避免混用 await import() 和静态 import 导致的打包问题
5. Config-Driven Policies
白名单、命令门控、线程策略全部通过 YAML 配置驱动。→ 运行时行为可配置,不需要改代码
6. Slot-based replacement
memory、context engine 这类能力开始通过 slot 机制变成“同类能力只激活一个实现”。
这会成为你后面理解 src/plugins/slots.ts、src/context-engine/、src/memory/ 的关键前提。
第五步:修正一个常见误解
很多人看完目录会形成一个误解:
gateway 是中心,其他一切都围着 gateway 转
更接近事实的说法是:
-
gateway 是控制平面 -
channel + routing 是消息入口面 -
agent runner 是实际执行面 -
memory / context 是状态与检索面 -
plugins 是横切扩展面
所以读代码时,主线应当优先追:
ingress->routing->agent runner-> tools/context/memory -> outbound
而不是只追:
cli -> gateway -> ws
消息流简图
入站(用户发消息给 AI)
Telegram 消息 ↓ grammy 接收src/telegram/handler.ts ↓ 格式化为内部 MsgContextsrc/channels/* / ingress adapter ↓src/routing/ (命令门控、agent 路由) ↓src/agents/pi-embedded-runner/ ↓ 生成回复src/auto-reply/ (格式化、分块) ↓Channel outbound adapter (发回 Telegram)
出站(openclaw message send)
CLI: openclaw message send --to "+1234" "Hello" ↓src/gateway/call.ts (执行引擎) ↓ 解析目标通道src/channels/ (通道分发) ↓ 媒体处理(如需要)src/media/ ↓Channel outbound adapter.sendText()
这个图先作为学习地图,下一阶段会开始逐步把这条线落到具体文件和调用点上。
这一篇读完后你应该得到什么
你应该至少形成下面 5 个判断:
src/agents/
才是核心执行层,不只是辅助目录 src/context-engine/
和 src/memory/是独立系统,不是零散工具extensions/
代表真实扩展机制,而不是示例代码 -
5 层模型是学习地图,不是严格运行时调用图 -
后面读代码时,要优先沿执行链读,而不是沿目录名字平均扫
下一篇预告
开始进入真正的“逐段源码拆解”模式,从 src/entry.ts 这个最具体的入口开始,了解 CLI 启动过程。
夜雨聆风