第二章 整体架构解析
在第一章,我们从设计哲学的角度理解了 OpenClaw 的核心定位:本地优先、智能体循环、工具驱动。这些选择回答了"为什么"的问题。
现在我们要回答"是什么"的问题——这些设计是如何在代码层面落地的?当你启动 openclaw gateway 后,系统内部到底在发生什么?
1. 从一个问题开始
想象一下这个场景:
你正在使用 OpenClaw,突然遇到了奇怪的行为。你让它"查找项目里所有 TODO 注释",它却回复"我找不到任何文件"。你知道项目里明明有几十个 TODO,问题出在哪里?
作为用户,你只能换个说法再试一次。但如果你理解架构,你就能定位问题:
- 是 Gateway 没有正确接收你的消息?
- 是 Command Queue 把消息路由到了错误的会话?
- 是 Agent 调用了 Glob 但模式匹配失败?
- 还是 LLM 返回的结果解析出错?
这就是本章的目标:从"再试一次"到"我知道为什么"。
2. 架构全景:WebSocket Gateway
OpenClaw 采用 WebSocket Gateway 架构,核心组件包括:
┌─────────────────────────────────────────────────────────────┐
│ Clients │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Control UI │ │ CLI │ │ Web Dashboard │ │
│ │ (macOS) │ │ (Terminal) │ │ (Browser) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
│ HTTP / WebSocket
▼
┌─────────────────────────────────────────────────────────────┐
│ Gateway (WebSocket) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Message Router & Session Mgr │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
▲ ▲ ▲
│ │ │
┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐
│ Telegram │ │ Discord │ │ WhatsApp (Baileys) │
│ Channel │ │ Channel │ │ Channel │
│ (extension)│ │ (extension)│ │ (built-in) │
└─────────────┘ └─────────────┘ └─────────────────────┘
▲ ▲ ▲
│ │ │
Telegram Discord API WhatsApp Web
Bot API (HTTP/WebSocket) (WebSocket)
│
│ node.invoke (WebSocket)
▼
┌─────────────────────────────────────────────────────────────┐
│ Nodes (Device Agents) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ macOS Node │ │ iOS Node │ │ Android Node │ │
│ │(system.run) │ │(camera.snap)│ │ (screen.record) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘一条消息的典型生命周期:
- 用户通过 Telegram/Discord/Slack 等渠道发送消息
- Gateway 接收消息,通过对应平台的 SDK 处理
- 消息进入 Command Queue,按会话排队
- Agent 取出消息,进入 Agent Loop 进行决策
- 需要时调用 LLM,需要时调用工具(read、write、edit、exec)
- 结果通过 Gateway 返回给用户
3. Gateway:统一入口与多平台接入
3.1 为什么需要 Gateway
Gateway 是 OpenClaw 的唯一入口点,负责管理所有消息渠道和控制平面客户端。
如果没有 Gateway,每个渠道都需要独立的连接管理、身份验证和消息处理逻辑,这会导致:
- 复杂度爆炸:每加一个渠道,核心代码都要改一次
- 状态分散:WhatsApp 会话、Telegram 连接分散管理
- 安全风险:多个入口点意味着多个攻击面
Gateway 通过统一 WebSocket 接口解决了这些问题。
3.2 多平台接入
OpenClaw 直接使用各平台的 SDK 接入,而非自行实现协议:
| 平台 | 接入方式 |
|---|---|
| Telegram | Webhook / 长轮询 |
| Discord | WebSocket |
| Slack | Events API / WebSocket |
| WebSocket | |
| WebChat | WebSocket |
这些 SDK 负责处理平台特定的协议细节,Gateway 专注于消息路由和会话管理。
3.3 连接模式与内网部署
不同渠道的接入方式差异很大:
| 模式 | 原理 | 实时性 | 公网要求 |
|---|---|---|---|
| Webhook | 平台主动 HTTP 请求 | 高 | 需要公网地址 |
| WebSocket | Gateway 主动保持长连接 | 高 | 不需要 |
| 长轮询 | Gateway 定期查询 | 中 | 不需要 |
对于 Webhook 模式,大多数个人用户没有公网服务器。解决方案包括:
- Tailscale:推荐方案,建立加密 mesh 网络
- SSH 隧道:建立端口转发隧道
这让 OpenClaw 可以安全地运行在内网服务器甚至笔记本上,而不需要公网 IP。
3.4 身份识别与会话隔离
Gateway 负责进行路由寻址。不同平台的用户 ID 格式各异,但都遵循统一规范:
- agent:main:telegram:123456789🧵42 - Telegram 话题线程
- agent:main:discord:channel:987654321 - Discord 频道
- agent:main:whatsapp:dm:+15551234567 - WhatsApp 私聊
- agent:ops:slack:cron:job:daily-report:run:uuid - Cron 任务会话
这个统一的身份标识决定了消息进入哪个会话(Session),是实现多用户隔离的基础。不同用户的消息被路由到独立的处理队列中,不会混淆。
4. Command Queue:命令队列与并发控制
4.1 为什么需要 Command Queue
直接让 Gateway 调用 Agent 会有三个问题:
| 问题 | 后果 | Command Queue 的解决 |
|---|---|---|
| 并发冲突 | 多个消息同时处理导致资源竞争 | 按会话序列化处理 |
| 无缓冲 | 高峰期消息涌入导致崩溃 | 提供队列缓冲 |
| 难以控制 | 无法控制并行度 | 可配置并发上限 |
Command Queue 是一个进程内队列,基于"泳道"模型实现并发控制,纯 TypeScript 实现,无外部依赖。
4.2 泳道模型:并发与顺序的平衡
Command Queue 的核心设计是 泳道模型:
泳道内:串行保证(按 Session Key) 泳道间:并行处理
┌──────────────────────────┐ ┌──────────┐ ┌──────────┐
│ Session A 的消息队列 │ │ 泳道 A │ │ 泳道 B │
│ ──────────────────────── │ │(SessionA)│ │(SessionB)│
│ 1. 创建 report │ │ ──────── │ │ ──────── │
│ 2. 写入内容 ←─────────────┼── 排队等待 │ 长任务 │ │ 短查询 │
└──────────────────────────┘ │ (5分钟) │ │ (1秒) │
│ ──────── │ │ ──────── │
│ │ │ 立即响应 │
└──────────┘ └──────────┘泳道内的串行保证:同一用户的消息按顺序处理。比如用户连续发送"创建 report.txt"和"写入内容",第二条会等待第一条完成后再执行,防止因文件不存在而失败。
泳道间的并行处理:不同用户互不等待。Session A 的 5 分钟代码分析不会阻塞Session B 的天气查询。
4.3 消息处理方式
Command Queue 支持多种消息处理方式,可通过配置或命令切换:
| 方式 | 行为 | 适用场景 |
|---|---|---|
| collect(默认) | 合并所有队列消息为单条跟进 | 大多数场景 |
| steer | 立即注入当前运行 | 需要实时干预 |
| followup | 排队等待下一轮 | 顺序处理 |
| steer-backlog | 立即注入 + 保留跟进 | 复杂交互 |
| steer+backlog | 与 steer-backlog 类似 | 复杂交互 |
| queue | 标准队列模式 | 顺序处理 |
| interrupt | 中断当前运行 | 紧急干预 |
配置示例(openclaw.json):
{
messages: {
queue: {
mode: "collect",
debounceMs: 等待毫秒数,
cap: 队列长度上限,
drop: "summarize",
byChannel: { discord: "collect" },
},
},
}会话内临时切换:发送 /queue steer 或 /queue collect
4.4 队列选项
- debounceMs:等待安静期后开始处理(防止"继续、继续"的连续消息)
- cap:每会话最大队列长度
- drop:溢出策略(
old丢弃旧消息 /new丢弃新消息 /summarize生成摘要)
5. Agent:决策核心与 Agent Loop
5.1 不是脚本,是循环
消息通过 Command Queue 后,到达系统的"大脑"——Agent。Agent 是 OpenClaw 最核心的组件,负责理解用户需求、规划任务、选择工具、协调执行。
传统的程序是线性的:输入、处理、输出。Agent 不是线性的,它是循环的。这个循环被称为 Agent Loop(智能体循环)。
Agent Loop 的工作流程:
┌──────────┐
│ 观察 │◄──────────────────────────┐
│ (Observe)│ │
└────┬─────┘ │
│ 接收当前状态 │
│ (用户输入、工具结果、环境信息) │
▼ │
┌──────────┐ ┌──────────┐ │
│ 思考 │────►│ 行动 │ │
│ (Think) │ │ (Act) │ │
└──────────┘ └────┬─────┘ │
│ │
│ 执行决策 │
│ (调用工具/回复) │
└────────────────┘循环持续进行,直到:
- 任务完成 → 生成最终回复
- 遇到错误 → 无法继续
- 达到最大循环次数 → 强制终止
这就是 OpenClaw 能处理复杂任务的原因。它不是一次性生成答案,而是像人类一样,一步步探索、验证、调整,直到达成目标。
5.2 工具选择:如何让 LLM 做出正确决策
在每次思考阶段,Agent 需要让 LLM 选择工具。Agent 会把当前状态、可用工具列表(包含每个工具的名称、描述和参数格式)发送给 LLM,LLM 根据任务需求选择最合适的工具并生成参数。
这个过程面临的三个挑战:
| 挑战 | 问题描述 | 解决方案 |
|---|---|---|
| 描述质量 | LLM 根据描述理解工具,模糊描述导致错误选择 | 精心设计工具描述 |
| 上下文限制 | 工具多时占用大量上下文空间 | 智能选择展示哪些工具 |
| 幻觉错误 | LLM "幻觉"出不存在的工具或格式错误 | 验证白名单,错误反馈 |
5.3 状态管理:记忆的维护
Agent 需要维护状态才能进行多轮推理。状态包括:
- 对话历史:用户和 Agent 之前的交互记录,作为短期记忆让 Agent 理解上下文
- 工具结果:最近调用的工具及其返回值,成为下一次思考的依据
- 环境信息:当前工作目录、系统状态、用户偏好等
状态管理的一个核心挑战是上下文窗口限制。LLM 能处理的文本长度是有限的(比如 4K、8K、128K Token)。当对话变长或任务变复杂时,状态可能超出这个限制。
Agent 采用了几种策略来应对:
- 截断:丢弃或压缩最旧的对话历史,重点保留最近的若干轮
- 摘要:对早期的对话进行压缩,用更短的文本保留关键信息
- 提取:将重要的事实(如用户偏好)提取出来,存储到长期记忆(MEMORY.md)中
5.4 安全边界
Agent 具有调用工具执行任意操作的能力,这既是优势也是风险。OpenClaw 设计了三层安全机制:
- Security 模式:
deny/allowlist/full三级控制 - Ask 模式:
off/on-miss/always确认级别 - Safe Bins:stdin-only 工具限制
6. LLM 集成与错误处理
6.1 多模型支持
Agent 做出了决策,但它需要调用外部的 LLM 来获得推理能力。
OpenClaw 支持接入多种 LLM,包括 OpenAI 的 GPT 系列、Anthropic 的 Claude、Google 的 Gemini,以及本地模型。系统负责屏蔽它们的差异:
- 封装不同的 API 格式
- 适配不同的能力(有的支持函数调用,有的不支持)
- 管理故障转移(当主模型不可用时自动切换到备用模型)
6.2 错误处理与自愈
LLM 的输出是不确定的,经常会遇到各种问题。系统实现三层错误自愈机制:
| 问题 | 处理方式 | 示例 |
|---|---|---|
| 格式错误 | 自动修复 | { "tool": "bash" → 补全 } |
| 模型故障 | 切换认证档案或使用备用模型 | 换一个Key或换一个模型 |
| 网络超时 | 指数退避重试 | 自动重试 |
这种机制让 OpenClaw 在面对不稳定的模型输出时,依然能保持极高的任务成功率。
6.3 Token 管理与成本控制
每次调用 LLM 都会产生 Token 消耗,系统从四个方面管理成本:
| 策略 | 作用 |
|---|---|
| 用量统计 | 记录每次调用的 input/output Token |
| 预算控制 | 设置上限,接近时发出警告 |
| 提示词压缩 | 压缩旧对话,保留新消息 |
| 响应缓存 | 缓存重复查询,避免重复调用 |
7. 场景化理解:当 things go wrong
理解了架构之后,让我们看看几个真实场景中如何定位问题。
场景一:消息没有响应
你发送了一条消息,Agent 完全没有反应。
排查思路:
- 检查 Gateway:日志中是否收到了消息?如果没有,检查网络连接和渠道配置。
- 检查 Command Queue:消息是否进入了正确的泳道?队列是否堆积?
- 检查 Agent:是否正在处理其他消息?是否陷入了长时间循环?
常见原因:
- Gateway 的 WebSocket 连接断开,没有重连成功
- Command Queue 已满,新消息被延迟处理
- Agent 正在处理一个耗时任务,你的消息在泳道中排队等待
场景二:响应非常慢
一条简单的查询等了十几秒才回复。
排查思路:
- Gateway 到 Queue:通常是毫秒级,不会是瓶颈
- Queue 排队:检查是否有其他消息在队列中等待
- Agent 处理:检查 Agent Loop 轮数,是否在反复尝试
- LLM 调用:检查 LLM 的响应时间,是否模型过载
常见原因:
- LLM 服务响应慢(尤其是高峰期)
- Agent 陷入工具调用的循环,反复尝试失败的命令
- 上下文过长,每次调用都携带大量历史记录
场景三:工具选择错误
你让它查找文件,它却一直用 Bash 运行 find 命令,而不是更适合的 Glob。
排查思路:
- 工具描述:系统提示词中 Glob 的描述是否清晰?
- 上下文限制:是否因为工具太多,Glob 没有被展示给 LLM?
- 历史惯性:是否之前的对话让 LLM 形成了使用 Bash 的习惯?
解决方案:
- 在 TOOLS.md 中明确描述 Glob 的适用场景
- 如果工具太多,考虑精简可用工具列表
- 在对话中明确提示"请使用 Glob 工具"
8. 组件协作示例
消息旅程(用户发送:"查找所有包含 TODO 的 Python 文件"):
Step 1: Gateway Step 2: Command Queue Step 3: Agent (Loop)
─────────────── ─────────────────── ─────────────────────
Telegram SDK ─────▶ 确定会话标识 第1轮:观察 → 思考 → 行动
提取消息内容 ───▶ 放入队列 ─────────▶ (调用Glob) 得50个文件
调度给Agent 第2轮:观察 → 思考 → 行动
(调用Grep) 得12个匹配
第3轮:观察 → 思考 → 行动
任务完成,生成回复
Step 4: Command Queue Step 5: Gateway
─────────────────── ───────────────
回复放入队列 ─────────▶ 通过 Telegram SDK
发送给用户异常处理:
- Gateway 网络问题 → 重试或返回错误
- Queue 溢出 → 根据 drop 策略处理
- Agent 死循环 → Tool Loop Detection 终止
- LLM 拒绝 → 错误自愈重试
9. 消息通信格式
Gateway 与客户端之间的通信使用标准的消息格式:
type 消息类型 = "lifecycle" | "tool" | "assistant" | "error" | (string & {});
type 消息内容 = {
runId: string; // 运行ID
seq: number; // 序列号(严格递增)
stream: 消息类型; // 消息类型
ts: number; // 时间戳(毫秒)
data: Record<string, unknown>; // 消息数据
sessionKey?: string; // 会话标识
};消息类型说明:
- lifecycle:运行生命周期事件(开始、结束)
- tool:工具调用事件
- assistant:助手回复事件
- error:错误事件
- compaction:状态压缩事件
- (string & {}):允许扩展其他自定义消息类型
10. 本章小结
通过这一章,我们从架构层面理解了 OpenClaw 的核心组件:
| 组件 | 核心职责 | 解决的关键问题 |
|---|---|---|
| Gateway | WebSocket 网关,管理所有渠道连接 | 统一入口、多平台接入、身份识别 |
| Command Queue | 命令队列,按会话序列化处理 | 并发控制、顺序保证、流量缓冲 |
| Agent | 理解需求、规划任务、选择工具 | Agent Loop 决策、状态管理、安全边界 |
| LLM 集成 | 连接多种模型,处理错误与重试 | 多模型适配、错误自愈、Token 管理 |
这些组件通过定义良好的协议协同工作,构成了 OpenClaw 的"数字大脑"。
架构设计的核心洞察:
OpenClaw 的架构体现了"关注点分离"的设计原则。每个组件只负责一件事,并且把这件事做好:
- Gateway 只关心如何接收和路由消息,不关心消息如何处理
- Command Queue 只关心如何排队和调度,不关心消息内容是什么
- Agent 只关心如何决策和行动,不关心消息从哪里来
- LLM 集成 只关心如何调用模型,不关心调用的结果如何使用
这种分离带来了极大的灵活性。你可以:
- 接入新的渠道,而不影响其他组件
- 使用不同的 LLM,Agent 的逻辑完全不用修改
- 在一台机器上运行多个实例,共享同一个 Gateway
理解了这个架构,你就能诊断问题发生在哪个环节:
- 消息没收到是 Gateway 的问题
- 消息没处理是 Queue 或 Agent 的问题
- 回复质量差是 LLM 或 Agent 的问题
在下一章,我们将深入 Agent 的"灵魂"——提示词系统。你会看到 SOUL.md、USER.md、TOOLS.md 等文件如何被组合成系统提示词,热更新机制如何让修改立即生效,以及如何通过调整提示词精细控制 Agent 行为。