Protocol

The WebSocket protocol — one transport for messaging, agents, history, and the daemon↔relay handshake.

Cyborg7 uses one transport: WebSocket (plus HTTP via Hono for REST endpoints). There is no SSE, no MQTT, and no separate agent broker — messaging, agent control, history, and presence all ride the same socket. Requests use a request/response pattern with a requestId; broadcasts are pushed.

Handshake

Client → Daemon   guest_hello / daemon_hello  (auth)
Daemon → Client   subscribed + initial state

A browser guest authenticates with a user JWT (guest_hello); a daemon authenticates with a daemon JWT (daemon_hello) and subscribes to its workspaces.

Daemon ↔ relay

Daemons connect to the relay and subscribe to workspace topics; the relay routes and persists.

// daemon → relay
{ "type": "relay_subscribe", "workspaceIds": ["ws_123", "ws_456"], "daemonId": "alice-laptop" }
// relay → daemon
{ "type": "relay_subscribed", "workspaceIds": ["ws_123", "ws_456"] }
Message Direction Purpose
daemon_hello Daemon → Relay Auth + subscribe to workspaces
relay_subscribed Relay → Daemon Confirm subscription
relay_forward Daemon → Relay Send a message to a workspace
relay_message Relay → Daemon Deliver a message from a workspace
relay_sync_request Daemon → Relay Request missed messages since a seq
relay_sync_response Relay → Daemon Batch of missed messages
daemon_status Daemon → Relay Heartbeat (every 15s) with agent count, queue depth
guest_hello Browser → Relay Auth a guest (user JWT, no daemon)

Sequence numbers

Each workspace has a monotonically increasing seq assigned by the relay (the single point of ordering). This gives:

  • Total ordering of messages within a workspace.
  • Efficient reconnect sync — a reconnecting daemon asks for seq > lastKnownSeq and replays only the delta.
  • No conflicts between daemons creating messages simultaneously.

Backpressure & heartbeats

A daemon reports load in its daemon_status heartbeat. When a daemon is saturated it sets accepting: false; the relay then stops routing new agent prompts to it and tells clients:

// relay → client
{ "type": "daemon_busy", "daemonId": "alice-laptop", "queueDepth": 15, "retryAfterMs": 5000 }

Plain liveness uses ping/pong; daemon connections use the richer daemon_status every 15 seconds.

See knowledge/03-protocol.md in the repo for the full message catalog.