Distributed daemons & relay
How Cyborg7's multi-daemon architecture works — daemons, the relay broker, PostgreSQL, and SQLite.
Cyborg7 uses a distributed architecture: each user/machine runs its own daemon. There is no single computer that acts as "the server." Daemons communicate via a relay that acts as a workspace-aware message broker. PostgreSQL is the shared persistent store; SQLite stays on each daemon as a local cache and offline queue.
Daemon A ──ws──┐
Daemon B ──ws──┤── Relay (message broker, pub/sub per workspace)
Daemon C ──ws──┘ │ async write-behind
▼
PostgreSQL (shared)
Why distributed
A single central daemon would be a single point of failure, add latency for local work (round-tripping to talk to an agent on your own machine), and bottleneck all agents onto one box. The distributed model is more resilient and scales naturally — any daemon can go offline without affecting the others.
The relay: from tunnel to broker
Paseo's original relay is a zero-knowledge 1:1 encrypted tunnel. Cyborg7 extends it into a workspace-aware broker:
- N daemons subscribe to workspace topics.
- The relay routes each message to all daemons (and browser guests) subscribed to that workspace.
- The relay assigns a monotonic
seqper workspace (total ordering + gap detection) and write-behinds messages to PostgreSQL asynchronously.
| The relay knows | The relay does not assign |
|---|---|
| Which daemons are connected | — |
| Which workspaces each subscribes to | — |
| Daemon health (heartbeats) | — |
| Message metadata for routing (workspace, seq) | — |
Dual storage
A DualStorage layer wraps SQLite + optional PostgreSQL, auto-detected from DATABASE_URL:
| Mode | Condition | Behavior |
|---|---|---|
| solo | no DATABASE_URL |
SQLite only — single-user, local. |
| connected | DATABASE_URL set |
SQLite cache + async PG sync — multi-daemon, shared. |
All reads go to SQLite (fast, local, always available). Writes go to SQLite first, then fire-and-forget to PostgreSQL. On reconnect, a daemon catches up by querying PG for seq > lastKnownSeq.
Two deployment shapes
- Local daemon — the full server with SQLite + optional PostgreSQL; agents run locally. Best for solo use and development.
- Cloud relay — a standalone relay (Hono HTTP + WebSocket) on a server, with PostgreSQL + Redis + S3 and no local agents. Browser/desktop guests connect here; agent prompts are forwarded to the daemon that owns the target agent.
Owner vs guest
- Owner — runs a daemon, contributes compute, runs agents locally, works offline.
- Guest — browser only (no daemon). Can chat, prompt agents, view output, and manage tasks; prompts are forwarded through the relay to a daemon that can run the agent.
Components
Daemon. Each user runs their own daemon (the Paseo server, extended with Cyborg7 workspace/channel/task semantics). It hosts the WebSocket + HTTP (Hono) endpoint that clients connect to, manages local agent processes, serves messages from its SQLite cache, and syncs with other daemons through the relay. There is no single central server — any daemon can go offline without affecting the others.
Relay (message broker). The relay is a workspace-aware pub/sub broker. Daemons subscribe to the workspace topics they belong to, and the relay routes each message to every daemon subscribed to that workspace. Traffic between daemons stays E2E encrypted (NaCl box, inherited from Paseo) — the relay knows routing metadata (which daemons, which workspaces, sequence numbers) but never message content. The relay also assigns the authoritative per-workspace sequence number, giving total message ordering, and writes messages to PostgreSQL asynchronously (write-behind).
PostgreSQL + Drizzle ORM (shared storage). The shared persistent store for everything that must be visible across daemons: workspaces, channels, messages, tasks, users, memberships, registered daemons, and the audit log. Type-safe queries and migrations via Drizzle. Schema lives in packages/server/src/server/cyborg/db/schema.ts.
SQLite (local storage). Each daemon keeps a local SQLite database (better-sqlite3) holding a recent-message cache, an offline queue, agent metadata, and local config. All reads serve from SQLite for speed and offline availability; writes go to SQLite first, then fire-and-forget to PostgreSQL. The DualStorage layer auto-detects the mode from DATABASE_URL — solo (SQLite only) when unset, connected (SQLite cache + async PG sync) when set.
Agents & Cybos. Agents are child processes spawned by the daemon. Claude agents run through the @anthropic-ai/claude-agent-sdk; other agents (Codex, GitHub Copilot, OpenCode, Cursor via ACP, Pi) run over ACP via stdio. The daemon injects a cyborg7 MCP server so agents can send messages, read channel history, and manage tasks. Cybos are customized agents defined by a cybo.json (identity, provider, model) plus a soul.md (personality), driven by the cybo / Pi runtime.
UI (Svelte 5). The client is a Svelte 5 (runes) + Tailwind v4 + shadcn-svelte collaboration shell — a configurable "blank Slack". It talks to its local daemon over a single WebSocket connection; the daemon transparently serves from SQLite or fetches from PostgreSQL. No HTTP polling, no SSE. The desktop app is the same UI wrapped in Electron, with an embedded local daemon and silent auto-update.
Message flow
| Path | Flow |
|---|---|
| Client → Daemon | UI WebSocket request → local daemon persists to SQLite |
| Human → Human | daemon → relay (pub/sub) → subscribed daemons → clients; relay write-behind to PostgreSQL |
| Human → Agent | daemon routes prompt into the local agent process (SDK / ACP stdin) |
| Agent → Channel | agent calls cyborg7_send_message (MCP) → daemon persists → relay → all members |
| Agent → Agent | via daemon + relay, persisted and visible to humans |
Database schema (key tables)
Shared state in PostgreSQL (Drizzle). A representative subset:
| Table | Purpose |
|---|---|
workspaces |
Workspace definitions and settings |
memberships |
Workspace membership and roles |
channels |
Conversation spaces (channels + DMs) |
messages |
All channel messages and DMs |
tasks |
Task board items (status, assignee, due dates) |
daemons |
Registered daemon instances per workspace. Each carries a device ID and is claimed by its owner; the Agent Access Matrix records per-member x per-daemon grants |
daemon_agents |
Which agents run on which daemon |
cybos |
Cybo definitions (identity + soul + provider/model) |
workspace_sequences |
Per-workspace monotonic seq counter for ordering |
audit_log |
Append-only log of mutations for ordering and recovery |
For the full design and rejected alternatives, see the repo's knowledge/11-distributed-architecture.md.