Skip to content

Cross-User Sync

When multiple team members use the Cortex desktop app, events from one user's agents need to reach other team members. The EventSyncBridge handles this without persistent cloud WebSocket connections.

How It Works

Team Member A (Desktop)          Cloud                    Team Member B (Desktop)
========================         =====                    ========================

Agent completes task ──────> API writes to DB
                              |
                              v
                         broadcastActivity()
                              |
                     ┌────────┴────────┐
                     v                 v
              WebSocketRoom DO    Device Fanout
              (web-only users)         |
                                       v
                              POST callback URLs
                                       |
                     ┌─────────────────┘
                     v
              LocalRealtimeServer
              POST /ingest ──> SQLite ──> WebSocket broadcast
                                              |
                                              v
                                         Dashboard UI
                                         "Agent completed!"

Device Registration

On startup, the desktop app registers its callback URL with the cloud:

bash
POST /api/v1/devices/register
{
  "deviceId": "device-abc123",
  "orgId": "org-456",
  "callbackUrl": "https://device-abc123.u.acrobi.com/ingest",
  "platform": "desktop",
  "version": "1.0.0"
}

The callback URL comes from the built-in Cloudflare Tunnel, which gives each desktop app a stable public URL without port forwarding.

On shutdown, the app deregisters:

bash
DELETE /api/v1/devices/deregister
{ "deviceId": "device-abc123" }

Primary: Webhook Push

When broadcastActivity() fires in the cloud worker, it fans out to registered device callback URLs:

  1. Cloud reads registered devices for the relevant orgId
  2. POSTs the event to each device's callback URL
  3. Fire-and-forget with 2-second timeout per device
  4. Failed deliveries are not retried (polling fallback covers gaps)

Latency

  • With tunnel active: Sub-second (direct HTTP POST)
  • Tunnel unavailable: Falls back to polling (see below)

Fallback: Adaptive Polling

If the webhook push can't reach the desktop (NAT, firewall, tunnel down), the EventSyncBridge polls the cloud API:

bash
GET /api/events/since?cursor=1712150400000&orgId=org-456

Adaptive Interval

The polling interval adjusts based on activity:

ConditionPoll Interval
Events received in last poll5 seconds
No events for 1 minute10 seconds
No events for 5 minutes30 seconds
Push webhook workingPolling disabled

Deduplication

Events received via both push and poll are deduplicated by their UUID id field in the local SQLite database. The INSERT OR IGNORE pattern ensures each event is stored exactly once.

Event Envelope

All cross-user events share a common format:

typescript
interface CrossUserEvent {
  id: string;          // UUID for deduplication
  orgId: string;       // Organization scope
  userId: string;      // Who triggered the event
  type: string;        // Event type (agent.completed, task.created, etc.)
  payload: object;     // Event-specific data
  timestamp: number;   // Unix ms when event occurred
}

Tunnel Management

The desktop app includes a TunnelManager service that establishes a Cloudflare Tunnel for inbound webhook delivery:

FeatureDetail
ProtocolCloudflare Tunnel (cloudflared)
URL formathttps://<device-id>.u.acrobi.com
AuthenticationDevice-specific token, rotated on registration
Auto-startStarts when cross-user sync is enabled
FallbackIf tunnel fails, switches to polling automatically

Feature Flag

Cross-user sync via tunnel is controlled by the TUNNEL_ENABLED feature flag in packages/desktop/shared/config.ts. Currently defaults to false — polling is the active fallback.

Privacy & Security

  • Device callback URLs are only stored for the lifetime of the registration
  • Tunnel traffic is encrypted end-to-end via Cloudflare
  • Events contain only the same data that goes through the cloud WebSocket
  • No sensitive data (credentials, API keys) flows through the sync channel
  • Device registration requires authenticated user token

Built by Acrobi