docs: unify ws protocol + platform guides

This commit is contained in:
Peter Steinberger
2026-01-19 08:54:21 +00:00
parent 66193dab92
commit 35e7c62e78
6 changed files with 120 additions and 61 deletions

View File

@@ -5,7 +5,7 @@ read_when:
---
# Gateway architecture
Last updated: 2026-01-05
Last updated: 2026-01-19
## Overview
@@ -14,10 +14,9 @@ Last updated: 2026-01-05
- 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`).
- **Nodes** (macOS/iOS/Android) use the **Bridge** protocol (TCP JSONL) instead
of the WebSocket control plane.
- **Nodes** (macOS/iOS/Android/headless) also connect over **WebSocket**, but
declare `role: node` with explicit caps/commands.
- One Gateway per host; it is the only place that opens a WhatsApp session.
- A **bridge** (default `18790`) is used for nodes (macOS/iOS/Android).
- A **canvas host** (default `18793`) serves agenteditable HTML and A2UI.
## Components and flows
@@ -33,14 +32,13 @@ Last updated: 2026-01-05
- Send requests (`health`, `status`, `send`, `agent`, `system-presence`).
- Subscribe to events (`tick`, `agent`, `presence`, `shutdown`).
### Nodes (macOS / iOS / Android)
- Connect to the **bridge** (TCP JSONL) rather than the WS server.
### Nodes (macOS / iOS / Android / headless)
- Connect to the **same WS server** with `role: node`.
- Pair with the Gateway to receive a token.
- Expose commands like `canvas.*`, `camera.*`, `screen.record`, `location.get`.
Protocol details:
- [Gateway protocol](/gateway/protocol)
- [Bridge protocol](/gateway/bridge-protocol)
### WebChat
- Static UI that uses the Gateway WS API for chat history and sends.
@@ -77,6 +75,7 @@ Client Gateway
must match or the socket closes.
- Idempotency keys are required for sideeffecting methods (`send`, `agent`) to
safely retry; the server keeps a shortlived dedupe cache.
- Nodes must include `role: "node"` plus caps/commands/permissions in `connect`.
## Protocol typing and codegen
@@ -92,6 +91,7 @@ Client Gateway
ssh -N -L 18789:127.0.0.1:18789 user@host
```
- The same handshake + auth token apply over the tunnel.
- TLS + optional pinning can be enabled for WS in remote setups.
## Operations snapshot

View File

@@ -1,18 +1,17 @@
---
summary: "Bridge protocol (nodes): TCP JSONL, pairing, scoped RPC"
summary: "Bridge protocol (legacy nodes): TCP JSONL, pairing, scoped RPC"
read_when:
- Building or debugging node clients (iOS/Android/macOS node mode)
- Investigating pairing or bridge auth failures
- Auditing the node surface exposed by the gateway
---
# Bridge protocol (Node transport)
# Bridge protocol (legacy node transport)
The Bridge protocol is a **narrow, authenticated** transport for nodes
(iOS/Android/macOS node mode). It keeps the Gateway WS control plane loopbackonly
and exposes only a scoped set of methods for nodes.
The Bridge protocol is a **legacy** node transport (TCP JSONL). New node clients
should use the unified Gateway WebSocket protocol instead.
If you are building an operator client (CLI, web UI, automations), use the
If you are building an operator or node client, use the
[Gateway protocol](/gateway/protocol).
## Why we have both

View File

@@ -8,12 +8,10 @@ read_when:
# Gateway protocol (WebSocket)
The Gateway WS protocol is the **full control plane** for Clawdbot. It is
loopback-only by default and is intended for local clients (CLI, web UI,
automations).
If you are building a **node client** (iOS/Android/macOS node mode), use the
[Bridge protocol](/gateway/bridge-protocol) instead.
The Gateway WS protocol is the **single control plane + node transport** for
Clawdbot. All clients (CLI, web UI, macOS app, iOS/Android nodes, headless
nodes) connect over WebSocket and declare their **role** + **scope** at
handshake time.
## Transport
@@ -38,7 +36,11 @@ Client → Gateway:
"platform": "macos",
"mode": "operator"
},
"role": "operator",
"scopes": ["operator.read", "operator.write"],
"caps": [],
"commands": [],
"permissions": {},
"auth": { "token": "…" },
"locale": "en-US",
"userAgent": "clawdbot-cli/1.2.3"
@@ -57,6 +59,40 @@ Gateway → Client:
}
```
### Node example
```json
{
"type": "req",
"id": "…",
"method": "connect",
"params": {
"minProtocol": 3,
"maxProtocol": 3,
"client": {
"id": "ios-node",
"version": "1.2.3",
"platform": "ios",
"mode": "node"
},
"role": "node",
"scopes": [],
"caps": ["camera", "canvas", "screen", "location", "voice"],
"commands": ["camera.snap", "canvas.navigate", "screen.record", "location.get"],
"permissions": { "camera.capture": true, "screen.record": false },
"auth": { "token": "…" },
"locale": "en-US",
"userAgent": "clawdbot-ios/1.2.3",
"device": {
"id": "device_fingerprint",
"publicKey": "…",
"signature": "…",
"signedAt": 1737264000000
}
}
}
```
## Framing
- **Request**: `{type:"req", id, method, params}`
@@ -65,6 +101,28 @@ Gateway → Client:
Side-effecting methods require **idempotency keys** (see schema).
## Roles + scopes
### Roles
- `operator` = control plane client (CLI/UI/automation).
- `node` = capability host (camera/screen/canvas/system.run).
### Scopes (operator)
Common scopes:
- `operator.read`
- `operator.write`
- `operator.admin`
- `operator.approvals`
- `operator.pairing`
### Caps/commands/permissions (node)
Nodes declare capability claims at connect time:
- `caps`: high-level capability categories.
- `commands`: command allowlist for invoke.
- `permissions`: granular toggles (e.g. `screen.record`, `camera.capture`).
The Gateway treats these as **claims** and enforces server-side allowlists.
## Versioning
- `PROTOCOL_VERSION` lives in `src/gateway/protocol/schema.ts`.
@@ -79,8 +137,22 @@ Side-effecting methods require **idempotency keys** (see schema).
- If `CLAWDBOT_GATEWAY_TOKEN` (or `--token`) is set, `connect.params.auth.token`
must match or the socket is closed.
## Device identity + pairing
- Nodes should include a stable device identity (`device.id`) derived from a
keypair fingerprint.
- Gateways issue tokens per device + role.
- Pairing approvals are required for new device IDs unless local auto-approval
is enabled.
## TLS + pinning
- TLS is supported for WS connections.
- Clients may optionally pin the gateway cert fingerprint (see `gateway.tls`
config and client TLS settings).
## Scope
This protocol exposes the **full gateway API** (status, channels, models,
chat, agent, sessions, nodes, etc.). The exact surface is defined by the
This protocol exposes the **full gateway API** (status, channels, models, chat,
agent, sessions, nodes, approvals, etc.). The exact surface is defined by the
TypeBox schemas in `src/gateway/protocol/schema.ts`.

View File

@@ -2,7 +2,7 @@
summary: "Android app (node): connection runbook + Canvas/Chat/Camera"
read_when:
- Pairing or reconnecting the Android node
- Debugging Android bridge discovery or auth
- Debugging Android gateway discovery or auth
- Verifying chat history parity across clients
---
@@ -13,40 +13,38 @@ read_when:
- Gateway required: yes (run it on macOS, Linux, or Windows via WSL2).
- Install: [Getting Started](/start/getting-started) + [Pairing](/gateway/pairing).
- Gateway: [Runbook](/gateway) + [Configuration](/gateway/configuration).
- Protocols: [Bridge protocol](/gateway/bridge-protocol) (nodes) and [Gateway protocol](/gateway/protocol) (control plane).
- Protocols: [Gateway protocol](/gateway/protocol) (nodes + control plane).
## System control
System control (launchd/systemd) lives on the Gateway host. See [Gateway](/gateway).
## Connection Runbook
Android node app ⇄ (mDNS/NSD + TCP bridge) ⇄ **Gateway bridge** ⇄ (loopback WS) ⇄ **Gateway**
Android node app ⇄ (mDNS/NSD + WebSocket) ⇄ **Gateway**
The Gateway WebSocket stays loopback-only (`ws://127.0.0.1:18789`). Android talks to the LAN-facing **bridge** (default `tcp://0.0.0.0:18790`) and uses Gateway-owned pairing.
Android connects directly to the Gateway WebSocket (default `ws://<host>:18789`) and uses Gateway-owned pairing.
### Prerequisites
- You can run the Gateway on the “master” machine.
- Android device/emulator can reach the gateway bridge:
- Android device/emulator can reach the gateway WebSocket:
- Same LAN with mDNS/NSD, **or**
- Same Tailscale tailnet using Wide-Area Bonjour / unicast DNS-SD (see below), **or**
- Manual bridge host/port (fallback)
- Manual gateway host/port (fallback)
- You can run the CLI (`clawdbot`) on the gateway machine (or via SSH).
### 1) Start the Gateway (with bridge enabled)
Bridge is enabled by default (disable via `CLAWDBOT_BRIDGE_ENABLED=0`).
### 1) Start the Gateway
```bash
clawdbot gateway --port 18789 --verbose
```
Confirm in logs you see something like:
- `bridge listening on tcp://0.0.0.0:18790 (node)`
- `listening on ws://0.0.0.0:18789`
For tailnet-only setups (recommended for Vienna ⇄ London), bind the bridge to the gateway machines Tailscale IP instead:
For tailnet-only setups (recommended for Vienna ⇄ London), bind the gateway to the tailnet IP:
- Set `bridge.bind: "tailnet"` in `~/.clawdbot/clawdbot.json` on the gateway host.
- Set `gateway.bind: "tailnet"` in `~/.clawdbot/clawdbot.json` on the gateway host.
- Restart the Gateway / macOS menubar app.
### 2) Verify discovery (optional)
@@ -54,7 +52,7 @@ For tailnet-only setups (recommended for Vienna ⇄ London), bind the bridge to
From the gateway machine:
```bash
dns-sd -B _clawdbot-bridge._tcp local.
dns-sd -B _clawdbot._tcp local.
```
More debugging notes: [Bonjour](/gateway/bonjour).
@@ -63,7 +61,7 @@ More debugging notes: [Bonjour](/gateway/bonjour).
Android NSD/mDNS discovery wont cross networks. If your Android node and the gateway are on different networks but connected via Tailscale, use Wide-Area Bonjour / unicast DNS-SD instead:
1) Set up a DNS-SD zone (example `clawdbot.internal.`) on the gateway host and publish `_clawdbot-bridge._tcp` records.
1) Set up a DNS-SD zone (example `clawdbot.internal.`) on the gateway host and publish `_clawdbot._tcp` records.
2) Configure Tailscale split DNS for `clawdbot.internal` pointing at that DNS server.
Details and example CoreDNS config: [Bonjour](/gateway/bonjour).
@@ -72,14 +70,14 @@ Details and example CoreDNS config: [Bonjour](/gateway/bonjour).
In the Android app:
- The app keeps its bridge connection alive via a **foreground service** (persistent notification).
- The app keeps its gateway connection alive via a **foreground service** (persistent notification).
- Open **Settings**.
- Under **Discovered Bridges**, select your gateway and hit **Connect**.
- If mDNS is blocked, use **Advanced → Manual Bridge** (host + port) and **Connect (Manual)**.
- Under **Discovered Gateways**, select your gateway and hit **Connect**.
- If mDNS is blocked, use **Advanced → Manual Gateway** (host + port) and **Connect (Manual)**.
After the first successful pairing, Android auto-reconnects on launch:
- Manual endpoint (if enabled), otherwise
- The last discovered bridge (best-effort).
- The last discovered gateway (best-effort).
### 4) Approve pairing (CLI)
@@ -117,7 +115,7 @@ The Android nodes Chat sheet uses the gateways **primary session key** (`m
If you want the node to show real HTML/CSS/JS that the agent can edit on disk, point the node at the Gateway canvas host.
Note: nodes always use the standalone canvas host on `canvasHost.port` (default `18793`), bound to the bridge interface.
Note: nodes use the standalone canvas host on `canvasHost.port` (default `18793`).
1) Create `~/clawd/canvas/index.html` on the gateway host.

View File

@@ -3,7 +3,7 @@ summary: "iOS node app: connect to the Gateway, pairing, canvas, and troubleshoo
read_when:
- Pairing or reconnecting the iOS node
- Running the iOS app from source
- Debugging bridge discovery or canvas commands
- Debugging gateway discovery or canvas commands
---
# iOS App (Node)
@@ -11,14 +11,13 @@ Availability: internal preview. The iOS app is not publicly distributed yet.
## What it does
- Connects to a Gateway over the bridge (LAN or tailnet).
- Connects to a Gateway over WebSocket (LAN or tailnet).
- Exposes node capabilities: Canvas, Screen snapshot, Camera capture, Location, Talk mode, Voice wake.
- Receives `node.invoke` commands and reports node status events.
## Requirements
- Gateway running on another device (macOS, Linux, or Windows via WSL2).
- Bridge enabled (default).
- Network path:
- Same LAN via Bonjour, **or**
- Tailnet via unicast DNS-SD (`clawdbot.internal.`), **or**
@@ -26,13 +25,13 @@ Availability: internal preview. The iOS app is not publicly distributed yet.
## Quick start (pair + connect)
1) Start the Gateway (bridge enabled by default):
1) Start the Gateway:
```bash
clawdbot gateway --port 18789
```
2) In the iOS app, open Settings and pick a discovered gateway (or enable Manual Bridge and enter host/port).
2) In the iOS app, open Settings and pick a discovered gateway (or enable Manual Host and enter host/port).
3) Approve the pairing request on the gateway host:
@@ -52,7 +51,7 @@ clawdbot gateway call node.list --params "{}"
### Bonjour (LAN)
The Gateway advertises `_clawdbot-bridge._tcp` on `local.`. The iOS app lists these automatically.
The Gateway advertises `_clawdbot._tcp` on `local.`. The iOS app lists these automatically.
### Tailnet (cross-network)
@@ -61,7 +60,7 @@ See [Bonjour](/gateway/bonjour) for the CoreDNS example.
### Manual host/port
In Settings, enable **Manual Bridge** and enter the gateway host + port (default `18790`).
In Settings, enable **Manual Host** and enter the gateway host + port (default `18789`).
## Canvas + A2UI

View File

@@ -57,7 +57,7 @@ The macOS app presents itself as a node. Common commands:
The node reports a `permissions` map so agents can decide whats allowed.
Node service + app IPC:
- When the headless node service is running (remote mode), it connects to the Gateway bridge.
- When the headless node service is running (remote mode), it connects to the Gateway WS as a node.
- `system.run` executes in the macOS app (UI/TCC context) over a local Unix socket; prompts + output stay in-app.
Diagram (SCI):
@@ -161,11 +161,10 @@ the Node CLIs `dns-sd` based discovery.
## Remote connection plumbing (SSH tunnels)
When the macOS app runs in **Remote** mode, it opens SSH tunnels so local UI
components can talk to a remote Gateway as if it were on localhost. There are
two independent tunnels:
When the macOS app runs in **Remote** mode, it opens an SSH tunnel so local UI
components can talk to a remote Gateway as if it were on localhost.
### Control tunnel (Gateway control/WebSocket port)
### Control tunnel (Gateway WebSocket port)
- **Purpose:** health checks, status, Web Chat, config, and other control-plane calls.
- **Local port:** the Gateway port (default `18789`), always stable.
- **Remote port:** the same Gateway port on the remote host.
@@ -174,16 +173,8 @@ two independent tunnels:
- **SSH shape:** `ssh -N -L <local>:127.0.0.1:<remote>` with BatchMode +
ExitOnForwardFailure + keepalive options.
### Node bridge tunnel (macOS node mode)
- **Purpose:** connect the macOS node to the Gateway **Bridge** protocol (TCP JSONL).
- **Remote port:** `gatewayPort + 1` (default `18790`), derived from the Gateway port.
- **Local port preference:** `CLAWDBOT_BRIDGE_PORT` or the default `18790`.
- **Behavior:** prefer the default bridge port for consistency; fall back to a
random local port if the preferred one is busy. The node then connects to the
resolved local port.
For setup steps, see [macOS remote access](/platforms/mac/remote). For protocol
details, see [Bridge protocol](/gateway/bridge-protocol).
details, see [Gateway protocol](/gateway/protocol).
## Related docs