Bridge Protocol (Legacy): TCP JSONL, Pairing, and Scoped RPC

This page documents the historical bridge protocol used by legacy OpenClaw nodes. It covers transport, handshake, and pairing details for reference only.

Read this when

  • Investigating old node client code or archived pairing logs
  • Auditing what the legacy node surface used to expose

Warning

The TCP bridge has been removed. Current OpenClaw builds no longer include the bridge listener, and bridge.* configuration keys are absent from the schema. This page is kept for historical reference only. For all node and operator clients, use the Gateway protocol.

Why it existed

  • Security boundary: exposed a narrow allowlist rather than the full gateway API surface.
  • Pairing and node identity: the gateway owned node admission, tied to a per-node token.
  • Discovery UX: nodes could find gateways over Bonjour on a local network, or connect directly across a tailnet.
  • Loopback WS: the full WebSocket control plane stayed local unless tunneled through SSH.

Transport

  • TCP, one JSON object per line (JSONL).
  • Optional TLS (bridge.tls.enabled: true).
  • Default listener port was 18790.

When TLS was active, discovery TXT records included bridgeTls=1 and bridgeTlsSha256 as a non-secret hint. Bonjour and mDNS TXT records are unauthenticated; clients could not treat the advertised fingerprint as an authoritative pin without separate out-of-band verification.

Handshake and pairing

  1. Client sends hello with node metadata and a token (if already paired).
  2. If not paired, the gateway replies with error (NOT_PAIRED / UNAUTHORIZED).
  3. Client sends pair-request.
  4. Gateway waits for approval, then sends pair-ok and hello-ok.

hello-ok was used to return serverName; hosted plugin surfaces are now advertised through pluginSurfaceUrls on the current Gateway protocol (Canvas and A2UI use pluginSurfaceUrls.canvas).

Frames

Client to gateway:

  • req / res: scoped gateway RPC (chat, sessions, config, health, voicewake, skills.bins).
  • event: node signals (voice transcript, agent request, chat subscribe, exec lifecycle).

Gateway to client:

  • invoke / invoke-res: node commands (canvas.*, camera.*, screen.record, location.get, sms.send).
  • event: chat updates for subscribed sessions.
  • ping / pong: keepalive.

Allowlist enforcement lived in src/gateway/server-bridge.ts (removed).

Exec lifecycle events

Nodes emitted exec.finished to surface completed system.run activity, mapped to system events by the gateway (legacy nodes could also emit exec.started). exec.denied marked a denied system.run attempt as a terminal denial without queuing a system event or waking agent work.

Payload fields (all optional unless noted):

FieldNotes
sessionKeyRequired. Agent session for event correlation and, for exec.finished, system event delivery.
runIdUnique exec id for grouping.
commandRaw or formatted command string.
exitCode, timedOut, outputCompletion details (finished only).
reasonDenial reason (denied only).

Historical tailnet usage

  • Bind the bridge to a tailnet IP: bridge.bind: "tailnet" in ~/.openclaw/openclaw.json (historical only; bridge.* is no longer valid config).
  • Clients connected using MagicDNS name or tailnet IP.
  • Bonjour does not cross networks; wide-area DNS-SD or a manual host and port was required otherwise.

Versioning

The bridge was implicit v1, with no min or max negotiation. Current node and operator clients use the WebSocket Gateway protocol, which does negotiate a protocol version range.