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 > lastKnownSeqand 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.