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 seq per 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

  1. Local daemon — the full server with SQLite + optional PostgreSQL; agents run locally. Best for solo use and development.
  2. 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_URLsolo (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.