基于 OpenClaw 的设计理念从零搭建 Agent 框架,第一步应该做的三个模块是:Gateway(消息路由与会话管理)、Agent Runtime(LLM 推理与工具执行)、Plugin System(扩展机制)。Gateway 是控制面,决定了消息怎么进、怎么出;Agent Runtime 是数据面,决定了智能怎么产生、工具怎么调;Plugin System 是扩展面,决定了框架能否在不改核心代码的前提下持续生长。三者构成最小可用闭环:消息进来→路由分发→Agent 推理→工具执行→结果返回,且后续的渠道接入、记忆系统、监控能力都能通过插件形式叠加上来。
一、为什么选这三个模块
Agent 框架的核心矛盾是”智能的不确定性”和”工程的确定性”之间的张力。LLM 输出不可预测,但消息路由必须可靠、工具执行必须安全、扩展能力必须可控。三个模块分别解决这三个问题:
| 模块 | 解决的核心矛盾 | 在 OpenClaw 中的角色 |
|---|---|---|
| Gateway | 消息进出的确定性与安全性 | 控制面,单一长运行进程 |
| Agent Runtime | LLM 推理与工具调用的编排 | 数据面,Brain + Hands |
| Plugin System | 扩展能力与核心稳定性的平衡 | 扩展面,注册式 API |
OpenClaw 采用 Hub-and-Spoke 架构,以一个 Gateway 作为 Hub,连接用户输入端与 Agent Runtime。Gateway 将接口层(消息来源)与助手运行时(智能与执行所在)解耦,用户通过任何消息平台都能访问拥有同一份记忆的 Agent,而对话状态和工具访问由自己的硬件集中管理(来源:clawdocs.org 架构概览)。
1.1 不选其他模块的理由
| 候选模块 | 为什么不选为前三 | 依赖关系 |
|---|---|---|
| 记忆系统 | 可用文件系统暂代,后续插件化 | 依赖 Agent Runtime 的上下文组装 |
| 多渠道接入 | 初期只需 CLI 或 Web 单渠道 | 依赖 Gateway 的路由能力 |
| 监控系统 | 前期可依赖日志,非核心路径 | 依赖 Plugin System 的钩子机制 |
| 心跳调度 | 自主任务在基础能力稳定后才有意义 | 依赖 Gateway 生命周期管理 |
记忆系统和多渠道接入都很重要,但它们可以后续以插件形式叠加。如果一开始就把精力分散到这些模块上,核心闭环反而难以快速跑通。
二、第一个模块:Gateway
Gateway 是整个框架的入口和控制面。没有它,消息无处可去,Agent 无从触发。OpenClaw 的 Gateway 是一个单一 Node.js 进程,承担 WebSocket 服务器、守护进程和进程管理器的角色(来源:clawdocs.org 架构概览)。
2.1 Gateway 的核心职责
| 职责 | 说明 | 实现要点 |
|---|---|---|
| 消息路由 | 将入站消息分发到正确的 Agent | 绑定规则匹配,最具体优先 |
| 会话管理 | 维护对话状态和上下文 | 按 sessionKey 隔离 |
| 认证鉴权 | 拦截未授权请求 | 挑战-签名,设备配对 |
| 幂等去重 | 防止重复请求重复执行 | idempotencyKey + dedupe Map |
| 并发控制 | 防止请求打爆后端 | 会话级排队 + 全局并发限制 |
2.2 最小 Gateway 实现
从零搭建时,Gateway 的最小实现需要覆盖消息接收、路由分发和响应返回三个环节:
// 最小 Gateway:消息路由与会话管理
import { WebSocketServer } from "ws";
const gateway = new WebSocketServer({ port: 18789 });
// 会话状态表
const sessions = new Map<string, { agentId: string; history: string[] }>();
// 绑定规则:最简单的情况,直接绑定 agent
const bindings = [
{ agentId: "main", match: { channel: "cli" } }
];
// 幂等去重表
const dedupe = new Map<string, { ok: boolean; payload: unknown }>();
gateway.on("connection", (ws) => {
ws.on("message", async (data) => {
const req = JSON.parse(data.toString());
// 1. 幂等检查
if (req.idempotencyKey && dedupe.has(req.idempotencyKey)) {
const cached = dedupe.get(req.idempotencyKey)!;
ws.send(JSON.stringify({ type: "response", cached: true, ...cached }));
return;
}
// 2. 路由匹配
const binding = bindings.find(b => b.match.channel === req.channel);
if (!binding) {
ws.send(JSON.stringify({ type: "error", message: "no binding matched" }));
return;
}
// 3. 会话管理
const sessionKey = `agent:${binding.agentId}:${req.channel}`;
if (!sessions.has(sessionKey)) {
sessions.set(sessionKey, { agentId: binding.agentId, history: [] });
}
const session = sessions.get(sessionKey)!;
session.history.push(req.message);
// 4. 返回 runId(两阶段协议的第一阶段)
const runId = `run_${Date.now()}`;
ws.send(JSON.stringify({ type: "accepted", runId }));
// 5. 调用 Agent Runtime(下一节实现)
const result = await agentRuntime.execute({
agentId: binding.agentId,
message: req.message,
history: session.history
});
// 6. 缓存结果并返回最终响应
const response = { ok: true, payload: result };
if (req.idempotencyKey) {
dedupe.set(req.idempotencyKey, response);
}
ws.send(JSON.stringify({ type: "final", runId, ...response }));
});
});
2.3 Gateway 的设计决策
| 决策点 | OpenClaw 的选择 | 理由 |
|---|---|---|
| 进程模型 | 单进程 | 部署简单,无需进程间通信 |
| 绑定位置 | localhost 默认 | 安全,外部不可直接访问 |
| 协议 | WebSocket + JSON 文本帧 | 透明可调试,双向通信 |
| 配置格式 | JSON | 人可读,工具可解析 |
单进程模型是 OpenClaw 的核心设计决策。一个 Gateway 管理一切,不需要微服务、不需要容器、不需要进程间通信。Node.js 事件循环天然处理并发,部署只需要一个二进制和一个配置文件。代价是垂直扩展是主要选项——要水平扩展需要跑多个 Gateway 实例放在负载均衡器后面(来源:clawdocs.org 架构概览)。
三、第二个模块:Agent Runtime
Gateway 只管消息进出,真正的智能在 Agent Runtime 中产生。Agent Runtime 端到端运行 AI 循环:从会话历史和记忆中组装上下文,调用模型,对系统提供的能力执行工具调用,并持久化更新后的状态。
3.1 Agent Runtime 的核心循环
Agent Runtime 的核心是一个”思考-决策-执行-反馈”的闭环:
- 组装上下文:从 SOUL.md(身份)、AGENTS.md(指令)、记忆检索、会话历史、活跃技能中拼装 Prompt
- 调用模型:将组装好的上下文发送给 LLM,获取响应
- 解析响应:判断模型输出的是文本回复还是工具调用
- 执行工具:如果是工具调用,执行对应工具并获取结果
- 循环反馈:将工具结果送回模型,重复步骤 2-4 直到模型输出最终回复
| 上下文来源 | 典型大小 | 说明 |
|---|---|---|
| SOUL.md | 200-1000 tokens | Agent 身份与人格 |
| AGENTS.md / TOOLS.md | 100-500 tokens | 操作规则与可用能力 |
| 记忆检索结果 | 默认上限 2000 tokens | 混合检索的相关记忆 |
| 活跃技能 | 100-500 tokens/技能 | 匹配到的技能指令 |
| 会话历史 | 可变 | 最近的消息 |
(来源:clawdocs.org 架构概览)
3.2 最小 Agent Runtime 实现
// 最小 Agent Runtime:LLM 推理 + 工具执行
import { Anthropic } from "@anthropic-ai/sdk";
const client = new Anthropic();
const tools = new Map<string, (params: any) => Promise<string>>();
// 注册工具
function registerTool(name: string, handler: (params: any) => Promise<string>) {
tools.set(name, handler);
}
async function executeAgent(req: {
agentId: string;
message: string;
history: string[];
}) {
const messages = [
{ role: "user", content: req.message }
];
// Agent 循环
while (true) {
const response = await client.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 4096,
system: "You are a helpful assistant. Use tools when needed.",
messages,
tools: Array.from(tools.keys()).map(name => ({
name,
description: `Tool: ${name}`,
input_schema: { type: "object", properties: {} }
}))
});
// 如果模型输出文本,返回最终结果
if (response.stop_reason === "end_turn") {
const text = response.content
.filter(b => b.type === "text")
.map(b => b.text)
.join("");
return text;
}
// 如果模型请求工具调用,执行工具并继续循环
if (response.stop_reason === "tool_use") {
const toolUse = response.content.find(b => b.type === "tool_use");
if (toolUse && tools.has(toolUse.name)) {
const result = await tools.get(toolUse.name)!(toolUse.input);
messages.push({ role: "assistant", content: response.content });
messages.push({
role: "user",
content: [{ type: "tool_result", tool_use_id: toolUse.id, content: result }]
});
}
}
}
}
export { executeAgent, registerTool };
3.3 模型抽象层的必要性
Agent Runtime 中对底层模型的抽象封装是第二优先级的设计。OpenClaw 支持多种模型提供商,通过统一接口屏蔽差异:
| 提供商类型 | 代表 | 接入方式 |
|---|---|---|
| 云端 API | OpenAI、Anthropic | API Key 认证 |
| 本地模型 | Ollama、vLLM | 本地 HTTP 端点 |
| 兼容协议 | 第三方 OpenAI 兼容 | 替换 base URL |
模型抽象的目标是避免模型绑定,提高可迁移性。不同角色可以分配不同模型:需要强推理的协调者用 Claude Opus,简单重复任务用本地 Llama,隐私敏感操作用本地模型(来源:clawdocs.org 架构概览)。
四、第三个模块:Plugin System
前两个模块搭出了核心闭环,但一个框架能否持续生长取决于扩展机制。Plugin System 让第三方在不碰核心代码的前提下注册新渠道、新工具、新 Hook。
4.1 插件系统的核心设计要素
| 设计要素 | 关键问题 | 推荐方案 |
|---|---|---|
| 插件发现 | 怎么找到第三方代码 | 清单文件声明,运行时按需加载 |
| 能力契约 | 谁定义接口标准 | 核心定义契约,插件注册实现 |
| 注册接口 | 插件怎么告诉核心自己的能力 | 集中式 API 对象注入 |
| 安全边界 | 信任哪些插件 | allowlist + 路径安全检查 |
| 生命周期 | 插件何时介入 | 命名钩子覆盖全链路 |
4.2 最小插件系统实现
// 最小 Plugin System:注册式 API + 生命周期钩子
type Plugin = {
id: string;
name: string;
register: (api: PluginApi) => void;
};
type PluginApi = {
registerTool: (tool: ToolDef) => void;
registerHook: (event: string, handler: Function) => void;
registerChannel: (channel: ChannelDef) => void;
runtime: {
tools: Map<string, ToolDef>;
hooks: Map<string, Function[]>;
channels: Map<string, ChannelDef>;
};
};
function createPluginApi(): PluginApi {
const tools = new Map<string, ToolDef>();
const hooks = new Map<string, Function[]>();
const channels = new Map<string, ChannelDef>();
return {
registerTool: (tool) => tools.set(tool.name, tool),
registerHook: (event, handler) => {
if (!hooks.has(event)) hooks.set(event, []);
hooks.get(event)!.push(handler);
},
registerChannel: (channel) => channels.set(channel.id, channel),
runtime: { tools, hooks, channels }
};
}
// 加载插件
async function loadPlugin(pluginPath: string, api: PluginApi) {
const mod = await import(pluginPath);
const plugin: Plugin = mod.default;
plugin.register(api);
}
// 触发钩子
async function triggerHook(api: PluginApi, event: string, payload: any) {
const handlers = api.runtime.hooks.get(event) || [];
for (const handler of handlers) {
await handler(payload);
}
}
4.3 插件系统的设计决策
从零搭建时,插件系统有几个关键决策需要提前确定:
- 插件运行在进程内还是隔离环境:进程内性能好但崩溃会拖垮核心,隔离环境安全但有 IPC 开销。OpenClaw 选择进程内运行,用契约测试和清单校验替代沙箱
- 插件配置如何校验:在
register调用前用 JSON Schema 校验用户配置,非法配置直接拒绝加载 - 核心与插件的依赖方向:插件只能依赖核心暴露的 API,核心不反向依赖插件。这保证了插件可以独立开发和分发
五、三个模块的协作关系
三个模块搭好后,一次完整的请求处理流程如下:
- 用户通过渠道发送消息,Gateway 接收
- Gateway 执行认证、幂等检查、路由匹配,确定目标 Agent
- Gateway 调用 Agent Runtime,传入消息和会话上下文
- Agent Runtime 组装上下文,调用 LLM 获取响应
- LLM 请求工具调用,Agent Runtime 从插件系统注册的工具中查找并执行
- 工具结果返回 LLM,循环直到产出最终回复
- Agent Runtime 将结果返回 Gateway
- Gateway 将回复发送回渠道
渠道消息 → Gateway(路由/鉴权/幂等)→ Agent Runtime(推理/工具)→ 插件系统(工具执行)
↑ | |
└────────── 最终回复 ←──────┘ |
↑ |
└─────────── 工具结果 ←───────────────────┘
| 阶段 | 主导模块 | 协作模块 |
|---|---|---|
| 消息接收 | Gateway | — |
| 路由分发 | Gateway | Plugin System(提供渠道插件) |
| 上下文组装 | Agent Runtime | Plugin System(提供记忆插件) |
| LLM 推理 | Agent Runtime | — |
| 工具执行 | Plugin System | Agent Runtime(调用工具) |
| 响应返回 | Gateway | — |
六、后续可叠加的模块
三个核心模块跑通后,以下能力都能以插件形式逐步叠加:
| 后续模块 | 叠加方式 | 前置依赖 |
|---|---|---|
| 记忆系统 | Plugin System 注册 memory 工具 | Agent Runtime 的上下文组装 |
| 多渠道接入 | Plugin System 注册 channel 插件 | Gateway 的路由能力 |
| 心跳调度 | Plugin System 注册 service | Gateway 的生命周期管理 |
| 监控告警 | Plugin System 注册 hook | 全链路钩子 |
| 审批流程 | Plugin System 注册中间件 | Gateway 的请求管线 |
OpenClaw 的实践验证了这条路径。它的记忆系统、OpenTelemetry 遥测、26 个消息平台桥接,全部以插件形式存在,核心只保留 Gateway、Agent Runtime 和 Plugin System 三件套(来源:clawdocs.org 架构概览)。
常见问题(FAQ)
Q1:为什么记忆系统不放在前三个模块里? 前期可用文件系统暂代,记忆检索可后续通过插件注册工具接入。
Q2:单进程 Gateway 能扛住高并发吗? 单进程靠事件循环处理并发,垂直扩展为主,需更高吞吐时跑多实例加负载均衡。
Q3:插件系统为什么不直接用沙箱隔离? 沙箱有 IPC 开销。OpenClaw 用契约测试和清单校验替代沙箱,换取更简的部署。