OpenClaw 把这些问题拆成多个模块,每个维度对应一组明确的不变量和工程约束。本文逐一展开,重点在设计决策背后的 Why,以及怎样验证这些决策没有被穿透。

一、控制面与数据面
一个 Agent 系统最先要回答的架构问题:状态的真实本源放在哪。
OpenClaw 选择了经典的 Hub-and-Spoke 拓扑:
Gateway(Hub,控制面) 负责所有决策:WebSocket 连接管理、入站 Schema 校验、会话路由、租户隔离、鉴权。它是唯一的状态真实本源。 Runtime(Spoke,数据面) 完全无状态,只执行一个固定五步的原子闭环:
组装上下文 → 模型推理 → 工具执行 → 状态写回 → 流式回复
这五步的顺序是硬约束。跳过「状态写回」直接回复,会导致Audit链断裂;提前释放流,会让后续请求读到半写状态。
这个分法的好处在于 Runtime 可以水平扩缩而不引入分裂真实本源——同一个会话键在任意时刻只被一个 Gateway 实例持有路由。如果两个 Hub 同时持有同一个会话键,这本身就是一个架构级 Bug。

一个容易踩的坑:把 Session Key 混同为 Auth Token。Session Key 的语义是数据面的寻址标识和上下文边界,跟身份无关。Auth Token 只应出现在 Gateway 的鉴权链路上,不下沉到工具参数,不写进持久化的note文件。

二、并发与调度
确定了控制面和数据面之后,下一个问题是:多个请求同时到达怎么调度。
五路事件源
OpenClaw 的调度器统一处理五类输入:用户消息、心跳(默认 30 分钟轮询)、定时任务(Cron)、内部钩子(Hooks)和外部 Webhook。它们走同一套事件循环,不分主次通道。
双Lane锁
并发控制分两层:
Session Lane(会话Lane)——同一个 session_id 上同时只允许一个写者进入「状态写回」阶段。读可以并发,但写必须串行。这是单写者不变量,违反它会导致状态机脏写,后果是该会话的整个执行历史不可信。
Global Lane(全局Lane)——全进程级的 maxConcurrent 上限,用来保护下游算力(GPU 集群、外部 API)。它和 Session Lane 是相乘关系而非替代:拿锁的顺序必须在代码里固定(先会话锁后全局槽位),否则会死锁。
三种队列模式
collect | ||
followup | ||
steer |
steer 是一种软中断,合法的抢占点只在「工具边界」——也就是模型已经产出 tool_calls 但工具尚未开始执行、或者工具刚返回结果但状态还没写回的时刻。在工具执行到一半时硬杀,除非你定义了补偿事务,否则会留下半提交的副作用。

落盘时序不变量
先 Flush 再 Discard:状态必须先落到可靠介质,然后才允许丢弃内存中的上下文。
整理上下文(Compaction)之前,系统会注入一条 NO_REPLY 静默微指令,暂停用户可见输出和新工具调用——避免一边压缩一边对用户回复产生不一致。

三、记忆层次
这是 OpenClaw 架构里最有意思的一个硬件映射:把 Agent 的记忆系统映射到 CPU 的 L1/L2/L3 缓存层次。

| L1 | |||
| L2 | |||
| L3 |
这个映射指导了一条硬规则:禁止全量上下文 Dump。
把 L3 的全部内容搬进 L1,等价于做了一次 O(N) 的线性全表扫描。代价是推理成本暴涨(输入 token 翻倍甚至更多)、越权信息一旦进入窗口难以擦除、排障时无法区分「召回噪声」和「事实错误」。
正确的做法是按需加载,类似操作系统的缺页中断(Demand Paging):
memory_search(query) → 候选列表(id, 摘要, 分数)memory_get(id) → 行级精确记录先搜后取,只把命中的几条精确记录注入 L1。

四、检索融合
记忆层解决了「存在哪」,检索层解决「怎么找」和「取哪几条」。

混合打分
OpenClaw 采用稠密向量和稀疏关键词的加权融合:
Score = W_vec × Cosine(q, v) + W_text × BM25(q, doc)
两个权重系数建议上线前用离线网格搜索或 Bandit 小步调优标定,避免一组权重写死跑一整年。
candidateMultiplier 控制初始召回池的大小:设得太小会丢召回,设得太大会拖 p95 延迟。
MMR 去重
从召回池里选进上下文前,用 最大边际相关性(MMR) 做一轮筛选:在与 query 相关的前提下,尽量让选中的文档彼此不重复。生产环境常见 λ 在 0.5 到 0.8 之间。
时间衰减
给旧文档乘一个指数衰减因子 e^(-λΔt),对抗概念漂移。配合版本策略使用效果更好——大版本发布时可以重置衰减时钟,避免老文档被永久性压分导致集体失忆。

五、零信任
安全维解决的是:Agent 能调用什么、不能调用什么、谁来拦。

鉴权与路由正交
多租户隔离依赖 Gateway 的拓扑切分(dmScope: per-channel-peer):每个通道加对端身份组合,各自持有独立的信任上下文,不允许跨通道复用。
工具管控的两层防线
| 静态 deny | |
| 审批互锁 |
一个关键判断:仅靠 system prompt 说「不许调某工具」≠ 实现了零信任。Prompt 层面的禁令可以被注入攻击绕过,必须在 Gateway 侧做带外的物理拦截。
Ring 拓扑:能力面越大越危险
| 3(最外层) | ||
| 1–2 | ||
| 0(核心) |

一条攻击链:从 Ring 3 一路打到 Ring 0 = 插件系统配置错误 + 会话路由跨租户串联 + 工具审批流程缺席。做 Design Review 的时候,沿这条链逐项检查。

六、传输清洗
网络处理的是 Gateway 与客户端之间的信号质量。
WebSocket 三态帧
req | ||
res | ||
event |
首帧必须是 connect。任何在 connect 完成前到达的 req,直接丢弃——不校验、不路由、不报错到 Runtime。

传输层噪声清洗
Debounce(防抖):用 debounceMs 做低通滤波,压平客户端的输入法连发、重复点击等高频毛刺。
Dedupe(去重):用 (channel_id, client_msg_id, payload_hash) 三元组做短 TTL 的哈希拦截。目标是吸收 TCP/WS 闪断重连造成的重复帧,阻断重放攻击。

Debounce 和 Dedupe 串联使用时,固定处理顺序,否则两端的 metric 会对不上账。
幂等重试域
Retry-After+ 幂等键 | ||
非幂等操作的重试必须锁定在原子级的单步 HTTP 客户端内部。在多步编排的顶层挂无界重试,遇到部分失败会级联为重试雪崩。

七、可靠性
当 Gateway 和 Runtime 之间的 WebSocket 闪断、节点切换、进程崩溃之后,系统需要恢复。
抗回放原则
核心规则:不用内存缓冲区里未持久化的事件做回放依据。
缓冲区里的事件是易失的、可能无序的、可能重复的。把它当「该执行但没执行完的清单」自动重跑,在扣款、改 DNS 等非幂等场景下等价于未声明的二次执行。
正确的做法:从 L3 不可变文件(JSONL、Markdown、append-only log)加上最后已提交的水位线重构会话状态。未完成的 saga 标记为 needs_human 或 compensating,交给人工决策。
证据四元组
事故复盘的输出不是一段模型生成的Processign,而是四块可独立验证的物理证据:
| Intent | ||
| Capability_Surface | ||
| Approvals | ||
| Durable_SideEffects |
四个字段缺任何一个,事故报告不算闭合。
八、发布质量
怎么判断系统是否在正确工作、什么条件下允许发布新版本。
双轨闭环
在线旁路:持续采样生产轨迹,检测离群执行——比如同一意图突然产生异常多的副作用、同一个 req_id 触发多条实际副作用路径、Flush 序在 span 上出现倒挂。发现异常后自动降权(收紧能力面)或切只读模式。
离线回归:从线上事故切片中抽象「最小复现拓扑」(输入帧序列 + Ring 配置 + 工具集),对环境终态写确定性断言——比如 orders 表行数 = 1、dns 记录 TTL 未变。CI 跑红 = 禁止进生产 tag。
故障剥离矩阵
当生产出问题时,按以下模式快速定位:
SLA指标
| 终态一致率 | |
| 工具误用率 | |
| 证据完整率 | |
| 恢复时间 |
夜雨聆风