OpenClaw 101|02|Gateway
Gateway 是 OpenClaw 的常驻控制面:它统一连接渠道、客户端、节点、自动化和 Agent Runtime,把聊天助手变成可持续运行的 Personal Agent OS。
上一篇我们把 OpenClaw 看成一个 Personal Agent OS:不是一个聊天机器人外壳,而是一套长期运行的个人 Agent 操作层。
这一篇拆第一块核心边界:Gateway。
如果没有 Gateway,Agent 很容易退化成一个“每个入口各跑各的”的工具:CLI 有一段历史,Web UI 有一段历史,Telegram 有一段历史,移动设备又是另一套连接。每个入口都能调用模型,但没有一个地方真正拥有系统状态。
OpenClaw 的选择相反:先有一个常驻 Gateway,再让渠道、客户端、节点、自动化和 Agent Runtime 都围绕它协作。
这篇要回答的问题是:为什么一个个人 Agent 需要 Gateway?Gateway 到底管什么?它和 Agent Loop、Session、Plugin、Node 的边界在哪里?

读完本文,你应该能回答
- Gateway 为什么不是普通 HTTP server 或 webhook handler?
- 为什么不能让每个客户端各自运行 Agent?
- request / response / event 三类消息如何把长任务变得可观察?
- Gateway 和 Session、Plugin、Node 的边界分别在哪里?
本篇在系列中的位置
控制面篇,解释所有渠道、客户端、节点和自动化为什么需要统一入口。OpenClaw 101 的主线是:先看控制面,再看执行面和状态边界,再进入上下文、能力系统、长期记忆、自动化、真实设备、扩展和 QA。下一篇进入 Agent Loop,看 Gateway accepted 之后一次 run 如何真正执行。
贯穿案例
后文会反复使用同一个任务来落地抽象机制:用户在手机上对 OpenClaw 说:“帮我看一下这个 repo 的测试为什么失败;如果需要跑命令就先做,修好后在聊天里提醒我。”
在本篇中,重点观察这个任务在 Gateway 这一层会遇到的边界:谁接收它,谁拥有状态,谁能触发工具,谁记录结果,以及失败后从哪里恢复。
控制面事件流
| 环节 | 读者应该抓住的问题 |
|---|---|
| 接入 | Channels、Clients、Nodes、Automation 接入同一个常驻进程 |
| 标准化 | 把不同入口变成统一 request / event |
| 调度 | 创建 run、返回 accepted、维持 runId |
| 广播 | 把 assistant/tool/lifecycle 事件送回订阅者 |
| 治理 | 处理鉴权、幂等、状态和插件边界 |
Gateway 是控制面
可以把 OpenClaw Gateway 理解成一个 always-on control plane。
它不是简单的 HTTP server,也不是只负责转发消息的 webhook handler。它长期运行,持有连接,维护状态,接收 request,推送 event,并协调 Agent run。
它连接的对象至少包括五类:
- Channels:Telegram、Slack、Discord、iMessage、Matrix、Teams、WhatsApp 等聊天渠道。
- Clients:CLI、TUI、Control UI、WebChat、桌面应用。
- Nodes:macOS、iOS、Android、headless node,提供设备能力。
- Automation:cron、webhook、heartbeat、background task。
- Agent Runtime:真正执行 context assembly、model call、tool call、streaming、persistence 的运行时。
这就是 Gateway 的第一性职责:把真实世界里的入口和 Agent 内部运行时隔开。
真实世界不稳定:聊天渠道会重连,客户端会断开,节点权限需要配对,自动化任务可能超时,模型调用可能持续很久。Gateway 作为中间层,把这些不稳定性收敛到一套协议和状态模型里。
为什么不是每个客户端直接跑 Agent
一个常见设计是:CLI 跑一个 Agent,Web UI 跑一个 Agent,聊天机器人也跑一个 Agent。看起来简单,但很快会出现四个问题。
第一,状态分裂。
同一个用户在 CLI 里说过的事,Telegram 不知道;Telegram 里产生的工具结果,Web UI 不知道。除非你额外实现一套同步层,否则每个入口都会变成一座孤岛。
第二,连接重复。
聊天渠道、模型 provider、插件 runtime、memory index、设备能力都可能需要长期连接或初始化。如果每个客户端各自维护一份,资源浪费只是小问题,真正麻烦的是状态冲突。
第三,安全边界模糊。
谁有权连接?谁有权调用节点上的 screen/camera/location?谁能触发 shell command?如果权限分散在每个客户端里,审计和收敛会很困难。
第四,长任务无法自然交接。
一个 Agent run 可能比客户端连接活得更久。用户在手机上发起任务,然后在 Web UI 看进度,再让结果发到群里。没有 Gateway,这种跨 surface 的交接会变得非常脆弱。
Gateway 解决这些问题的方式是:让所有 surface 连接同一个长期运行的状态中心。
下面的伪代码是机制抽象,不对应 OpenClaw 的真实 API 或文件结构。
type Gateway = {
sessions: SessionStore
transcripts: TranscriptStore
channels: ChannelRegistry
nodes: NodeRegistry
plugins: PluginRegistry
automation: TaskScheduler
runtime: AgentRuntime
}
这不是为了“多套一层”,而是为了给 Agent 系统一个稳定的控制面。
WebSocket 协议:request、response、event
OpenClaw Gateway 使用 WebSocket 连接控制面客户端和节点。
它的协议模型可以简化成三类帧:
type RequestFrame = {
type: "req"
id: string
method: string
params: unknown
}
type ResponseFrame = {
type: "res"
id: string
ok: boolean
payload?: unknown
error?: { message: string; code?: string }
}
type EventFrame = {
type: "event"
event: string
payload: unknown
seq?: number
stateVersion?: number
}
request / response 适合一次性动作:查询状态、发送消息、启动 agent run、调用 node command。
event 适合持续变化:agent streaming、presence、health、chat、cron、shutdown。
这种拆分很重要。Agent run 不应该被强行塞进一个同步 response 里。一个 run 可能先被接受,然后持续产生 assistant delta、tool event、lifecycle event,最后才结束。
所以 Gateway 里的 agent 请求更像后台任务提交:
Client -> Gateway: req agent
Gateway -> Client: res accepted { runId }
Gateway -> Client: event agent lifecycle:start
Gateway -> Client: event agent assistant delta
Gateway -> Client: event agent tool:start
Gateway -> Client: event agent tool:end
Gateway -> Client: event agent lifecycle:end
这让 CLI、Web UI、聊天渠道都可以用同一套 event stream 观察 Agent,而不是每个入口自己实现一套 streaming 协议。

Connect Handshake:不要相信裸连接
Gateway 的第一帧必须是 connect。
这是一个小细节,但背后是很重要的安全模型:任何客户端或节点在进入系统之前,都要声明身份、角色、能力,并通过认证或配对。
简化后,连接过程像这样:
async function handleConnection(socket: WebSocket) {
const first = await socket.readJson()
if (first.type !== "req" || first.method !== "connect") {
socket.close("first frame must be connect")
return
}
const identity = await authenticate(first.params.auth)
const device = await verifyOrPairDevice(first.params.device)
const role = first.params.role ?? "client"
if (role === "node") {
await registerNode({ identity, device, caps: first.params.caps })
} else {
await registerClient({ identity, device })
}
socket.send({
type: "res",
id: first.id,
ok: true,
payload: { hello: "ok", features: discoverFeatures(role) },
})
}
这一步至少解决三件事:
- Auth:连接者是否能进入 Gateway。
- Pairing:新设备是否被允许成为受信设备。
- Role and capabilities:这是普通客户端,还是提供能力的 node?它暴露哪些 commands?
Node 的 role 尤其关键。一个 macOS node 或移动 node 可能能提供 screen、camera、location、notification、canvas 等能力。如果这些能力没有设备级身份和权限边界,Agent 就会变成一个很难审计的远程控制系统。
Gateway 的 connect handshake 把“谁连进来了、它能做什么、它是否被配对过”放在协议入口处,而不是等到工具调用时才临时判断。
Session Store 属于 Gateway
Gateway 另一个重要职责是拥有 session store 和 transcripts。
这意味着 session 不是客户端本地状态,也不是某个聊天渠道私有状态。CLI、Control UI、WebChat、聊天渠道看到的历史,都应该回到 Gateway 这个事实源。
为什么这很重要?因为 OpenClaw 不是单入口系统。
一个用户可能在私聊里启动任务,在 Control UI 里看流式进度,再用 CLI 查询 status,最后让 Agent 把结果发到另一个渠道。只要 session store 属于 Gateway,这些 surface 都是在观察同一个运行时事实。
Session ownership 还让 reset、cleanup、compaction、transcript write lock 有统一位置。
async function routeInboundMessage(message: InboundMessage) {
const sessionKey = resolveSessionKey(message)
const session = await gateway.sessions.open(sessionKey)
await gateway.queue.enqueue(sessionKey, async () => {
const run = await gateway.runtime.start({
session,
input: message.bodyForAgent,
replyRoute: message.replyRoute,
})
return run
})
}
这里的关键不是 start(),而是 resolveSessionKey()。
不同来源对应不同 session 策略:
- DM 可以共享主 session,也可以按 sender / channel 隔离。
- Group 和 room 通常按 conversation 隔离。
- Cron job 通常是一次性运行边界。
- Webhook 通常按 hook scope 隔离。
如果 session store 分散在客户端或渠道插件里,这些策略就会越来越难保持一致。
Idempotency:副作用必须能安全重试
Gateway 处理很多有副作用的动作:发送消息、启动 Agent run、调用节点命令、触发工具执行。
网络世界里 request 可能超时,但服务端已经执行了动作。如果客户端重试一次,就可能发两条消息、启动两个 run、重复执行一段自动化。
所以 Gateway 对 side-effecting methods 需要 idempotency key。
简化逻辑是:
async function handleSideEffectingRequest(req: RequestFrame) {
const key = req.params.idempotencyKey
if (!key) {
throw new Error("missing idempotency key")
}
const cached = await dedupeCache.get(key)
if (cached) {
return cached.response
}
const response = await performSideEffect(req)
await dedupeCache.set(key, { response, ttl: "short" })
return response
}
这类机制在 demo 里不显眼,但在真实消息系统里非常关键。没有 idempotency,一个断线重连就可能变成重复回复;没有 dedupe,一个渠道 redelivery 就可能触发第二次 Agent run。
Gateway 是最适合做这件事的地方,因为它站在所有 surface 和 runtime 之间。
Agent Events:把运行时变成可观察对象
Gateway 不只是启动 Agent run,还要把 run 变成可观察对象。
OpenClaw 的 Agent stream 可以粗略分为三类:
- lifecycle:start、end、error。
- assistant:模型输出的文本增量或块。
- tool:工具调用的 start、update、end。
这种 event 分类让不同 UI 可以选择自己的呈现方式:
- CLI 可以打印流式文本和工具摘要。
- Control UI 可以显示完整 run timeline。
- 聊天渠道可以选择只发送最终回复,或发送 block streaming。
- 自动化系统可以等 lifecycle end,再触发后续任务。
换句话说,Gateway 把 Agent Runtime 的内部过程变成了一个对外可订阅的事件流。
这也是后续 Observability 文章会继续展开的主题:一个 Agent 系统如果只能看到最终文本,就很难 debug。你需要看到 run 什么时候开始、模型什么时候卡住、工具调用是否超时、transcript 是否写入、最终回复是否送达。
Nodes:Gateway 也是设备能力入口
OpenClaw 的 Gateway 不只服务聊天渠道,也连接 nodes。
Node 是一种特殊 WebSocket client:它声明 role: node,带上 device identity、caps、commands、permissions,然后通过 Gateway 暴露设备能力。
典型能力包括:
- screen record / screen inspect
- camera capture
- location get
- notification send
- canvas host
- mobile app bridge
这让 Agent 不只是“会聊天”,而是可以在被授权的前提下使用真实设备能力。
但这也提高了安全要求。一个能访问 screen 或 camera 的 Agent 系统,必须把 node pairing、permission、command routing、audit 放进控制面。Gateway 正是这个边界。
Plugins:Gateway 不应该硬编码世界
Gateway 连接很多东西,但它不能把每个 provider、channel、tool、memory backend 的细节都写死在 core 里。
这就是 plugin system 的作用。
Gateway 需要知道:
- 哪个 plugin 拥有哪些 capability。
- 哪个 channel plugin 能处理某类 message action。
- 哪个 provider plugin 能提供某个 model。
- 哪些 hooks 要拦截 agent lifecycle、tool call、message sending。
- 哪些 manifest metadata 足够用于 discovery 和 validation。
但 Gateway 不应该为了回答这些问题就加载所有 runtime code。
OpenClaw 的方向是 manifest-first:先读 manifest 和 metadata,做 discovery、validation、activation planning;只有真正需要执行能力时,才进入 runtime loading。
这条边界非常重要。否则 Gateway 会变成一个巨大的 import center:每次启动都加载所有聊天渠道、所有模型 SDK、所有媒体 provider、所有 memory backend。系统会慢,失败面会大,插件也更难独立演进。
Gateway 的边界
总结一下,Gateway 应该拥有什么?
- 连接生命周期。
- WebSocket 协议。
- auth、pairing、device identity。
- session store 和 transcript 事实源。
- request / response / event dispatch。
- channel、node、automation、runtime 的协调。
- side-effecting request 的 idempotency。
- Agent event stream 的外部观察面。
Gateway 不应该拥有什么?
- 具体模型 provider 的业务逻辑。
- 每个聊天渠道的私有发送实现。
- 每个 tool 的内部执行细节。
- 每个 memory backend 的索引策略。
- 每个 plugin 的 runtime 生命周期细节。
它应该做控制面,而不是吞掉所有实现。
这就是 Gateway 设计最难的地方:它必须足够中心化,才能统一状态、权限、事件和恢复;又必须足够克制,不能把所有 capability 都变成 core 逻辑。
评估清单
看一个 Agent 框架的 Gateway 或 server 层,可以问这些问题:
- 是否有一个长期运行的事实源?还是每个客户端各自维护状态?
- request / response 和 event stream 是否分离?
- Agent run 是否支持 accepted + streaming + final lifecycle?
- session store 属于控制面,还是散落在客户端和渠道里?
- side-effecting request 是否有 idempotency?
- 新设备是否需要 pairing?node capability 是否有权限边界?
- 插件 discovery 是否能在不加载完整 runtime 的情况下完成?
- Gateway 是否能暴露足够事件用于 UI、debug 和 automation?
这些问题决定一个 Agent 系统能不能从 demo 走向长期运行。
下一篇
下一篇会进入 Agent Loop。
Gateway 负责接住世界,但真正把一条消息变成行动和回复的是 Agent Loop:context assembly、model inference、tool execution、streaming、persistence、timeout、retry、compaction 都发生在那里。
如果说 Gateway 是 OpenClaw 的控制面,Agent Loop 就是它的执行心跳。
References
- OpenClaw README
- Gateway architecture
- Gateway protocol
- Agent loop
- Session management
- Messages
- Plugin internals
- Nodes and pairing