iT邦幫忙

0

寫在 30 天走進 OpenClaw 之後:所有東西都要經過它,那它是誰?gateway 的定位

  • 分享至 

  • xImage
  •  

開場故事

我把 18th 鐵人賽那 30 篇文章全部抓出來,搜了一個關鍵字。

gateway 出現在 18 篇裡面。

聽起來覆蓋率很高。但我一篇一篇看過去之後,發現一件有點尷尬的事——那 18 次出現,全部長這樣:

  • src/gateway/session-lifecycle-state.ts(路徑)
  • GatewayDrainingError(錯誤類別名)
  • isGatewaySubordinateWorkAdmissionClosed()(函式名)
  • 「外部入口 / CLI / Gateway」(架構圖裡的一個方塊)

沒有任何一篇解釋過它是什麼。

它就這樣以一個專有名詞的姿態,在 30 天裡飄過 18 次。讀者大概會以為自己知道那是什麼——畢竟「閘道」嘛,聽起來就是入口。我自己在寫的時候也是這麼混過去的。

然後我做了一件事:數檔案。

src/agents/      1136 個檔案
src/gateway/      604 個檔案   ← 第二大
src/plugin-sdk/   482 個檔案
src/skills/        89 個檔案   ← 我寫了四篇以上

604 個檔案,第二大的模組,零篇。

而 89 個檔案的 skills/ 我寫了四篇還不只。

這個對比讓我意識到,那 30 天的大綱反映的不是這個倉庫的形狀,是我動筆前腦子裡的形狀。所以這個系列的第一篇,就從那個最大的洞開始補。

今天要解的問題

  • gateway 到底是什麼?它為什麼需要 604 個檔案?
  • 為什麼 CLI、channel、node、plugin 全部都要先經過它?
  • 「一台主機只能有一個 gateway」這個限制是從哪來的?
  • 它跟我前面花了 30 天講的 agent runtime,是什麼關係?

架構總覽

官方文件開頭三句話就把定位講完了:

📄 文件:docs/concepts/architecture.md

- A single long-lived **Gateway** owns all messaging surfaces (WhatsApp via
  Baileys, Telegram via grammY, Slack, Discord, Signal, iMessage, WebChat).
- Control-plane clients (macOS app, CLI, web UI, automations) connect to the
  Gateway over **WebSocket** on the configured bind host (default
  `127.0.0.1:18789`).
- One Gateway per host; it is the only place that opens a WhatsApp session.

三個關鍵詞:single、long-lived、owns。

單一、長壽、而且是擁有者。

注意它用的動詞不是 "routes"(轉送)也不是 "manages"(管理),是 owns。這個字很重要,等一下會回來講。

先看它到底接了誰。根據同一份文件,連到 gateway 的東西分成三類:

  1. Clients(控制面) —— macOS app、CLI、web admin

    • 一個 client 一條 WS 連線
    • 送請求(health、status、send、agent、system-presence)
    • 訂閱事件(tick、agent、presence、shutdown)
  2. Nodes(裝置) —— macOS / iOS / Android / headless

    • 連到同一個 WS server,但宣告 role: node
    • 提供裝置身分,配對是裝置層級的
    • 提供 canvas.*、camera.*、screen.record、location.get 這些命令
  3. Providers(訊息來源) —— WhatsApp、Telegram、Slack、Discord、Signal、iMessage、WebChat

而 gateway 自己做的事,文件列了四項:

### Gateway (daemon)

- Maintains provider connections.
- Exposes a typed WS API (requests, responses, server-push events).
- Validates inbound frames against JSON Schema.
- Emits events like `agent`, `chat`, `presence`, `health`, `heartbeat`, `cron`.

看到這裡,我第一個念頭是:這跟我 30 天裡講的那些東西,是什麼關係?

答案是——我那 30 天講的全部住在它裡面。

為什麼非得有一個「唯一」的它

這才是 gateway 存在的真正理由,而且它不是軟體設計的品味問題,是物理限制。

文件那句話值得再念一次:

One Gateway per host; it is the only place that opens a WhatsApp session.

一個 WhatsApp session,同一時間只能有一個持有者。

你不能開兩個行程同時登入同一個 WhatsApp 帳號。Telegram、Signal、iMessage 也都有類似的約束——連線是有狀態的、是獨佔的、而且斷線重連有代價。

所以問題變成:如果我有一個 CLI、一個 macOS app、一個排程器、還有三個 agent,它們都想送訊息到同一個 WhatsApp 帳號,怎麼辦?

只能有一個東西真正握著那條連線,其他人跟它要。

那個東西就是 gateway。

一旦你接受這個前提,後面所有的設計就都推得出來了:

  • 既然只有它握著連線,所有發送都得經過它 → 它需要一套 API
  • 既然大家都連它,它得活得比任何一個 client 久 → 它是 daemon,不是 CLI
  • 既然它是唯一的,啟動時就得確保沒有第二個 → 需要鎖(下一篇的主題)
  • 既然它是唯一的,它掛了全部都掛 → 需要健康檢查、需要重啟復原

604 個檔案就是這樣長出來的。它不是一個「閘道」,它是整個系統的宿主。

我 30 天寫的東西,住在它的哪裡

這是我補完這一篇之後,最大的認知調整。

18th 鐵人賽第 3 天我講「一個請求怎麼進來」,然後順著呼叫鏈一路往下追進 agent 內部——分派、工具、記憶、子代理、失敗處理。

但我從來沒往上看過。

而往上看的話,那整條鏈其實是 gateway 收到一個 WS frame 之後才開始的。我花 30 天描述的那個「任務的一生」,對 gateway 來說只是它眾多職責裡的一項:把一個 agent 請求交給 agent runtime,然後等結果回來。

用一個比喻:

  • 我前面 30 天寫的是一個員工怎麼完成一件工作
  • gateway 是那棟辦公大樓——門禁、電梯、電力、網路、收發室

員工再優秀,大樓停電就什麼都不用談。而我花了 30 天描述員工,一次都沒提過大樓。

這也解釋了為什麼 GatewayDrainingError 會出現在我講任務分派那篇的程式碼節錄裡:

if (isGatewaySubordinateWorkAdmissionClosed()) {
  throw new GatewayDrainingError();
}

當時我把它當成 admission 邏輯的一部分帶過去了。現在回頭看,那一行在說的是:

大樓要關門了,不要再放新的工作進來。

它不是 agent 的邏輯,它是宿主的邏輯。 我當時沒看出來,因為我根本不知道有宿主這回事。

604 個檔案裡面有什麼

大致掃過去,可以分成幾塊:

子區 檔案數 在做什麼
根目錄散檔 334 連線管理、事件派送、各種 policy
server-methods/ 174 WS API 的每一個方法
server/ 46 HTTP / WS 伺服器本體
worker-environments/ 40 工作執行環境

server-methods/ 那 174 個檔案特別能說明問題——一個方法一個檔案。

這代表 gateway 對外的 API 表面非常大。它不是「一個入口收所有東西」,而是「一個伺服器提供一百多種操作」。

文件那句 Validates inbound frames against JSON Schema 也值得注意:每一個進來的 frame 都要先過 schema 驗證。這在一個可以被外部裝置連線的系統裡是必要的,而這又帶出下一個問題:誰連得上它、憑什麼連。

設計取捨

  • 好處是訊息來源的獨佔連線有唯一的擁有者,不會互搶
  • 好處是所有 client 共用同一套 API,CLI 和 macOS app 不需要各自實作一遍
  • 好處是 agent runtime 可以專心做 agent 的事,連線、重連、認證都不歸它管
  • 好處是有一個明確的地方可以做全域的事:drain、健康檢查、事件廣播
  • 代價是單點故障。gateway 掛了,CLI、app、所有 channel 一起掛
  • 代價是「一台主機一個」這個限制會卡到多租戶的需求,所以又得長出 multi-gateway 模式
  • 代價是這一層的複雜度很容易被忽略——像我,忽略了 30 天

最後那一條其實是這整個系列的起點。

基礎設施做得好的時候是隱形的。 你順著任務往下追,不會撞到它;你讀 agent 的程式碼,只會看到它的名字出現在 import 裡。直到你去數檔案,才發現那是第二大的模組。

今天的結論

  • gateway 是一個 single、long-lived 的 daemon,它擁有所有訊息來源的連線,不只是轉送
  • 「一台主機一個 gateway」不是設計偏好,是因為 WhatsApp 這類 session 本質上獨佔
  • CLI、macOS app、node 裝置全部連同一個 WS server(預設 127.0.0.1:18789),靠 role 區分身分
  • 它對外的 API 很大:光 server-methods/ 就有 174 個檔案,一個方法一個檔
  • 我 18th 鐵人賽花 30 天描述的「任務的一生」,是它眾多職責裡的一項
  • GatewayDrainingError 不是 agent 的邏輯,是宿主在說「要關門了」

下一步

既然所有 client 和裝置都連同一個 WS server,那就有一個很現實的問題:

它憑什麼相信連進來的是誰?

而且我在查資料的時候,發現 OpenClaw 以前其實有兩套協定,現在只剩一套。另一套被整個拿掉了。

下一篇就從那個被刪掉的東西開始講。


圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言