Skip to content

Local Real-Time Server

The LocalRealtimeServer is an embedded WebSocket + HTTP server running inside the Electron main process. It provides real-time event delivery to the dashboard renderer without any cloud WebSocket connections.

Architecture

Next.js Renderer (port 3691)
  |
  | ws://127.0.0.1:3693/ws
  v
LocalRealtimeServer (port 3693)
  ├── WebSocket server (ws library)
  │   ├── Channel subscriptions (in-memory Map)
  │   ├── Broadcast to subscribers
  │   └── Ping/pong keepalive
  ├── HTTP POST /ingest (cloud event push)
  ├── HTTP GET /health
  └── SQLite event log (better-sqlite3)
      ├── 24-hour retention
      ├── UUID deduplication
      └── Offline replay

How It Works

1. Server Startup

The server starts automatically when the Electron app launches, following the same singleton pattern as OllamaProxy:

typescript
// electron/services/LocalRealtimeServer.ts
const server = getLocalRealtimeServer();
await server.start(); // Binds to 127.0.0.1:3693

2. Renderer Connection

The dashboard's WebSocket client auto-detects it's running in Electron and connects locally:

typescript
// Automatic URL resolution
const wsUrl = window.cortex
  ? 'ws://127.0.0.1:3693/ws'                          // Desktop: local
  : `wss://api.cortex.acrobi.com/api/ws?token=${jwt}`; // Browser: cloud

3. Channel Subscriptions

Same channel model as the cloud WebSocket. The renderer subscribes to specific channels:

json
{ "type": "subscribe", "channelId": "agent:my-coder-agent" }

4. Event Sources

Events flow into the local server from three sources:

SourceHowLatency
Local agent activityDirect injection from Electron main process~0ms
Cloud webhook pushPOST /ingest from cloud's activity broadcasterSub-second
Polling fallbackEventSyncBridge polls GET /api/events/since5-30s

5. Event Persistence

Every event is stored in the local SQLite database:

sql
CREATE TABLE realtime_events (
  id TEXT PRIMARY KEY,        -- UUID for dedup
  org_id TEXT NOT NULL,
  user_id TEXT NOT NULL,
  type TEXT NOT NULL,          -- e.g., "agent.completed"
  payload TEXT NOT NULL,       -- JSON
  timestamp INTEGER NOT NULL,  -- Unix ms (event time)
  received_at INTEGER NOT NULL -- Unix ms (local receipt time)
);
  • Deduplication: Same event from push + poll is stored once (by UUID)
  • Retention: Events older than 24 hours are pruned on startup
  • Replay: On restart, last 24 hours of events are available for the renderer

Security

  • Binds to 127.0.0.1 only — not accessible from the network
  • No authentication required (localhost only)
  • Event ingestion (POST /ingest) validates org_id matches the signed-in user
  • No sensitive data stored — events are the same payloads that go to the cloud WebSocket

Port Configuration

The port is configurable via the shared config:

typescript
// packages/desktop/shared/config.ts
export const LOCAL_REALTIME_PORT = 3693;

If port 3693 is in use, the server logs a warning and falls back to the next available port. The renderer discovers the actual port via IPC.

Monitoring

Check server status via IPC or HTTP:

bash
# HTTP health check
curl http://127.0.0.1:3693/health
json
{
  "status": "ok",
  "connections": 1,
  "channels": 3,
  "eventsStored": 156,
  "uptime": 3600
}

Lifecycle

EventBehavior
App launchServer starts, loads last 24h events from SQLite
Renderer connectsWebSocket upgrade, begin delivering events
Cloud push arrivesStore in SQLite, broadcast to subscribers
App sleeps/minimizesServer continues running, events accumulate
App closesServer stops, SQLite persists events for next launch
App restartsServer starts, renderer reconnects, missed events replayed

Built by Acrobi