Gateway Architecture
WebSocket gateway architecture, components, and client flows.
Last updated: 2026-01-22
Overview
- A long-lived gateway owns all messaging interfaces (via WhatsApp
Baileys, GrammY Telegram, Slack, Discord, Signal, iMessage, WebChat).
- Control plane clients (macOS app, CLI, Web UI, automation) connect to the
gateway via WebSocket on the config-bound host (default
''127.0.0.1:18789'').
- Nodes (macOS/iOS/Android/headless) also connect via WebSocket, but
use an explicit uppercase/command declaration ''role: node''.
- One gateway per host; this is the only place WhatsApp sessions are opened.
Components and Flow
#
Gateway (Daemon)
- Maintains provider connections.
- Exposes typed WS API (requests, responses, server-sent events).
- Validates inbound frames against JSON schemas.
- Emits ''agent'', ''chat'', ''presence'', ''health'', ''heartbeat'', ''cron'' events, etc.
#
Clients (mac app / CLI / Web admin)
- One WS connection per client.
- Sends requests (''health'', ''status'', ''send'', ''agent'', ''system-presence'').
- Subscribes to events (''tick'', ''agent'', ''presence'', ''shutdown'').
#
Nodes (macOS / iOS / Android / headless)
- Connect to ''the same WS server'' using ''role: node''.
- Provides device identity in ''connect''; pairing is ''device-based'' (role ''node'') and
approval exists in device pairing store.
- Exposes commands like ''canvas.*'', ''camera.*'', ''screen.record'', ''location.get''.
Protocol details:
#
Web Chat
- Static UI for chat history and sending using Gateway WS API.
- In remote setups, connects via the same SSH/Tailscale tunnel as other
clients.
Connection Lifecycle (Single Client)
Client Gateway
| |
|| (or res error + close)
| (payload=hello-ok carries snapshot: presence + health)
| |
|< event:presence -----| (final: {runId,status,summary})
| |Wire Protocol (Summary)
- Transport: WebSocket, text frames with JSON payloads.
- First frame ''must'' be ''connect''.
- After handshake:
- Requests: ''{type:"req", id, method, params}'' β ''{type:"res", id, ok, payload|error}''
- Events: ''{type:"event", event, payload, seq?, stateVersion?}''
- If ''OPENCLAW_GATEWAY_TOKEN'' (or ''--token'') is set, ''connect.params.auth.token''
must match or the socket will close.
- Side-effect methods (''send'', ''agent'') require idempotency keys
for safe retries; server keeps a short-lived deduplication cache.
Pairing + Local Trust
- All WS clients (operators + nodes) include ''device identity'' on ''connect''.
- New device IDs require pairing approval; gateway issues a device token
for subsequent connections.
- Local connections (loopback or gateway host's own tailnet address) can
be auto-approved to keep same-host UX smooth.
- ''Non-local'' connections must sign ''connect.challenge'' nonce and require
explicit approval.
- Gateway auth (''gateway.auth.*'') still applies to ''all'' connections, local or
remote. Details: ''Gateway Protocol'', ''Pairing'', ''Security''.
Protocol Types and Code Generation
- TypeBox schemas define the protocol.
- JSON schemas are generated from these schemas.
- Swift models are generated from JSON schemas.
Remote Access
- Preferred: Tailscale or VPN.
- Alternative: SSH tunnel
''ssh -N -L 18789:127.0.0.1:18789 user@host''
- Same handshake + auth tokens apply over tunnel.
- TLS + optional pinning can be enabled for WS in remote setup.
Operational Snapshot
- Start: ''openclaw gateway'' (foreground, logs to stdout).
- Health: ''health'' over WS (also included in ''hello-ok'').
- Supervision: launchd/systemd for auto-restart.
Invariants
- Only one gateway per host controls one Baileys session.
- Handshake is mandatory; any non-JSON or non-connect first frame is hard-close.
- Events are not replayed; clients must catch up gaps.