Bridge Protocol (Legacy TCP JSONL) - Archived Reference

Archived documentation for the legacy bridge protocol, covering TCP JSONL transport, pairing, and scoped RPC. Intended for developers maintaining old node clients or reviewing historical gateway security.

Read this when

  • Implementing or updating gateway WS clients
  • Debugging protocol mismatches or connect failures
  • Regenerating protocol schema/models

Warning

The TCP bridge is gone. Newer OpenClaw releases no longer include the bridge listener, and the bridge.* configuration keys have been dropped from the schema. This page exists only as an archived record. For all node/operator clients, rely on the Gateway protocol.

Why it existed

  • Security boundary: only a narrow allowlist was exposed, not the entire gateway API.
  • Pairing + node identity: the gateway handled node admission, tying it to a token unique to each node.
  • Discovery UX: gateways could be found by nodes over LAN via Bonjour, or reached directly across a tailnet.
  • Loopback WS: unless tunneled through SSH, the full WS control plane remained local.

Transport

  • TCP transport, with one JSON object per line (JSONL).
  • TLS optional (bridge.tls.enabled: true).
  • Default port for the listener was 18790.

When TLS was on, discovery TXT records carried bridgeTls=1 and bridgeTlsSha256, the latter being a non-secret hint. Since Bonjour/mDNS TXT records are not authenticated, clients had no way to treat the advertised fingerprint as an authoritative pin without separate out-of-band checks.

Handshake and pairing

  1. The client transmits hello, containing node metadata and a token if pairing already happened.
  2. For unpaired nodes, the gateway answers with error (NOT_PAIRED / UNAUTHORIZED).
  3. Next, the client sends pair-request.
  4. Once approval is granted, the gateway responds with pair-ok and hello-ok.

Previously, hello-ok was used to return serverName; hosted plugin surfaces are now announced via pluginSurfaceUrls on the current Gateway protocol (Canvas/A2UI relies on pluginSurfaceUrls.canvas).

Frames

Client to gateway:

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

Gateway to client:

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

The allowlist enforcement was implemented in src/gateway/server-bridge.ts (now removed).

Exec lifecycle events

To surface completed system.run activity, nodes emitted exec.finished, which the gateway mapped to system events (legacy nodes could alternatively emit exec.started). A denied system.run attempt was marked by exec.denied as a terminal denial, with no system event queued and no agent work triggered.

Payload fields (all optional unless noted):

FieldNotes
sessionKeyRequired. Agent session used 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 by setting bridge.bind: "tailnet" in ~/.openclaw/openclaw.json (historical only; bridge.* is no longer accepted config).
  • Clients connected through a MagicDNS name or a tailnet IP.
  • Bonjour cannot traverse networks; otherwise, wide-area DNS-SD or a manual host/port was necessary.

Versioning

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

530 words · updated Aug 24, 2026