architecture

Relay (Router)

The stateless WebSocket router that connects nodes to clients — and nothing else.

What the Relay does

Three things, none of them stateful.

  1. Authentication. Every connection (node or client) presents an OAuth or JWT token. The relay validates it.
  2. Authorization. Every message is checked against the project’s permission model. Can this client see this Node’s Thread? If not, drop the message.
  3. Routing. Validated messages are forwarded to the intended subscriber set.

That’s it. The relay holds no Thread state, no file content, no agent memory. If you killed every relay instance and started fresh ones, every Node would reconnect and the system would be back online with no data loss.

Why this is unusual

Most “live collaboration” systems put state on the server. They have to — they’re the ones holding the document. Manifoldone doesn’t hold the document. The Node does. So the relay can stay pure.

Wire format

Messages on the wire follow the Soulstream-compatible SSE event vocabulary (init, text_delta, complete, error) extended with our own Thread/tool frames (thread_start, user_prompt_submit, pre_tool_use, post_tool_use, thread_end, recap, node_presence). The same JSON envelope flows from Node → Relay (over WebSocket) and from Relay → Dashboard (over SSE or WebSocket). The full schema lives in tech_architecture.md §3.

The trade-off

Stateless relays mean every relay instance handles its share of connections cleanly. They can be added behind a load balancer. Failover is “the WebSocket reconnects” — there’s no state to migrate.

The cost we pay is that we can’t replay full history from the relay. The relay does keep a small ring buffer per project (the last ~50 events) so a late-joining client gets recent context, but it cannot reconstruct an entire Thread. The Node can — it has the Thread log — and on reconnect it re-sends what was missed.

What runs the Relay

Node.js 20+, Fastify for the HTTP/auth surface, raw ws for the WebSocket layer. Auth is OAuth 2.1 (for human clients) and HS256 JWT (for nodes, issued during pairing). Deployment is horizontally scaled containers behind a managed load balancer (Fly.io or Railway in v0.1; Vercel doesn’t love long-lived WebSockets).

What’s not here that some teams might expect

  • No durable message persistence. The relay’s ring buffer is in memory and is lost on restart; the Node is the source of truth for Thread history.
  • No conflict resolution. That’s the VCS layer’s job.
  • No agent execution. The relay never talks to Claude. Nodes do.

Each “no” here is what lets the relay stay simple enough to scale.