OpenClaw 设计原理

理解这些设计决策,能帮你在阅读源码时不感到困惑。
每个"反共识"都是经过工程权衡的有意选择。


一、Gateway 是业务网关,不是网络网关

常见误解

听到 “Gateway” 这个词,很多人会联想到 Nginx、Kong 这样的网络网关——无状态、只做转发。

实际设计

OpenClaw 的 Gateway 是一个有状态的业务控制平面

维度 网络网关 OpenClaw Gateway
状态 无状态 有状态(会话、记忆、配置)
持久化 不存数据 持久化会话、记忆、凭据到本地
业务逻辑 会话管理、路由决策、工具调度、定时任务
运行方式 按请求 24x7 持续运行

为什么? OpenClaw 是 Local-First 架构——所有数据留在本地,不依赖云端。Gateway 不只是消息通道,它是整个系统的大脑。

代码印证:看 src/gateway/server.impl.ts 的启动逻辑——初始化的全是业务组件(channelManager、cronService、agentEventHandler),不是网络组件。


二、消息经过多层转换,不是直达 AI

常见误解

用户消息 → Gateway → AI 模型 → 回复

实际设计

消息经过 5 层转换才到达 AI 模型:

第 1 层  平台原生消息     WhatsApp/Telegram/Discord 各自的格式
    ↓    渠道适配器解析
第 2 层  InboundEnvelope  统一的内部消息格式(channel, peer, content...)
    ↓    路由决策
第 3 层  RouteContext      agentId + sessionKey + matchedBy
    ↓    上下文组装
第 4 层  AgentContext      历史对话 + 系统提示词 + 记忆 + 工具列表
    ↓    格式转换
第 5 层  API 请求          ChatCompletion messages + tools(模型厂商格式)

为什么? 每层职责单一,彼此解耦:

  • 新增渠道只需实现第 1→2 层的适配器
  • 更换 AI 模型只需修改第 4→5 层的格式化
  • 路由规则变化不影响其他层

三、会话级串行 + 全局并发控制

常见误解

“消息来了就处理,或者每条消息开一个线程。”

实际设计

OpenClaw 采用两层并发控制

第一层:同一会话内串行

同一个用户的消息必须按顺序处理,否则会话状态会乱。代码通过 session lane 实现——同一 sessionKey 的消息进入同一队列。

第二层:全局并发度限制

跨会话可以并行,但有上限。防止同时发起太多 AI 调用导致资源耗尽或成本失控。

会话 A 的消息 ─→ [会话 A 队列(串行)] ─┐
                                          ├→ [全局调度器(并发度限制)] → Agent 执行
会话 B 的消息 ─→ [会话 B 队列(串行)] ─┘

四种队列模式

模式 行为 场景
collect (默认) 排队消息合并成一条后续回复 用户连续发了好几条
steer 立即插入当前正在进行的 Agent 回合 用户想纠正正在处理的问题
followup 等当前处理完再排队 正常的新消息
steer-backlog 转向当前回合 + 保留后续 复杂交互

关键代码src/agents/pi-embedded-runner/run/lanes.tssrc/auto-reply/reply/queue/


四、流式响应 + 工具调用循环

常见误解

“AI 想好了一次性返回,中间调工具就等着。”

实际设计

AI 的响应是流式的(一个 token 一个 token 到达),工具调用是循环执行的:

AI 模型开始流式输出
    │
    ├→ 文字 token → 实时推送给用户("正在输入..."效果)
    │
    ├→ 检测到 tool_call → 暂停流 → 执行工具 → 工具结果喂回模型 → 模型继续输出
    │                                                                     │
    │                                                          可能再次调用工具 ←┘
    │
    └→ stop/end_turn → 输出完毕

关键点

  • 一次 Agent 回合可能包含多轮工具调用(模型决定何时停止)
  • 文字 token 可以实时推送,不需要等全部生成完
  • 工具执行期间,模型确实在等待——但之后可以基于工具结果继续调用更多工具

关键代码src/agents/pi-embedded-runner/run.ts 中的 runEmbeddedAttempt()


五、组合优于继承

在其他项目中常见

class TelegramChannel extends BaseChannel {
  override parseMessage() { ... }
  override sendMessage() { ... }
}

OpenClaw 的做法

不用类继承,用可选适配器对象组合:

const telegram: ChannelPlugin = {
  id: "telegram",
  meta: { name: "Telegram", ... },
  config: { listAccountIds, resolveAccount, ... },  // 可选
  outbound: { send, ... },                           // 可选
  messaging: { onInbound, ... },                     // 可选
  // 不支持的能力就不提供对应适配器
};

为什么?

  • 继承层次深了难以理解(“这个方法是从哪一层 override 来的?”)
  • 可选适配器让能力组合更灵活——一个渠道不支持线程就不实现 threading
  • 测试时可以只 mock 需要的适配器

详见 01-tech-basics.md 中的"适配器组合模式"。


六、Local-First 数据架构

常见做法

聊天机器人通常把数据存在云数据库(PostgreSQL、MongoDB 等)。

OpenClaw 的做法

所有数据存在本地文件系统:

数据类型 存储方式 为什么不用数据库
会话记录 JSONL 追加写 简单、可靠、无需数据库进程
记忆 SQLite + 向量 嵌入式数据库,无需部署
配置 JSON5 文件 人类可读、可编辑、可版本控制
凭据 文件 (0600) 不经过网络,最小化暴露面

为什么?

  • 用户拥有完全的数据控制权
  • 部署简单——不需要额外的数据库服务
  • 备份就是复制目录
  • 迁移就是 rsync

代价:不支持多实例水平扩展(但这对个人 AI 助手来说不是问题)。

Logo

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

更多推荐