Architecture Decisions

Key architectural decisions for xSwarm dashboard and security infrastructure

Architecture Decisions

This document records significant architectural decisions for the xSwarm platform.

ADR-001: Dashboard Restructure - Project-Centric Architecture

Status: Planned Date: January 2026

Context

The current dashboard has a flat, task-centric view where tasks appear at the global level. This doesn’t match the mental model where tasks belong to projects, and projects are assigned to worker machines.

Decision

Transform the dashboard from flat task-centric view to hierarchical project-centric architecture with worker capacity management and console access.

Key Changes

  • Tasks belong to projects (not shown at global level)
  • Projects get rich dashboards with activity, chat, console
  • Workers show capacity metrics and project assignments
  • Project “move” = clone repo + decrypt secrets + npm install + assign worker

Database Schema Updates

Workers Table Additions

disk_total: integer('disk_total'),      // MB
disk_free: integer('disk_free'),        // MB
ram_total: integer('ram_total'),        // MB
cpu_cores: integer('cpu_cores'),
gpu_name: text('gpu_name'),
gpu_vram: integer('gpu_vram'),          // MB
tasks_completed: integer('tasks_completed').default(0),
hours_worked: real('hours_worked').default(0),

Projects Table Additions

assigned_worker_id: text('assigned_worker_id').references(() => workers.id, { onDelete: 'set null' }),
color: text('color'),                   // Unique hex color
last_activity_at: text('last_activity_at'),

New Tables

  • chat_messages - Global and project-specific chat
  • activity_log - Project activity timeline

API Endpoints

Workers Routes

  • POST /workers/:id/heartbeat - Add capacity metrics
  • GET /workers/:id/projects - Get assigned projects

Projects Routes

  • POST /projects/:id/assign - Assign project to worker
  • GET /projects/:id/activity - Get activity timeline

New Chat Routes

  • GET /chat?project_id=xxx - Get messages
  • POST /chat - Send message
  • GET /notifications - Cross-project notifications

UI Changes

Main Dashboard

  • Remove global tasks section
  • Add project cards grid with unique colors, activity indicators
  • Add quick stats: total projects, active workers, pending issues
  • Add global chat panel (right sidebar) with project switcher
  • Add notifications feed from all projects

Per-Project Dashboard

Tabs:

  1. Overview - Header, activity timeline, current tasks, kanban link
  2. Tasks - Existing kanban view
  3. Chat - Project-specific chat panel
  4. Console - Terminal access to worker (xterm.js)
  5. Settings - Worker assignment, secrets management

Workers Page

Display per worker:

  • Capacity: CPU cores, RAM (used/total), Disk (used/total), GPU
  • Assigned Projects: List with colors
  • Activity: Tasks completed, hours worked
  • Actions: “Move Project” button

Project-Worker Assignment Flow

  1. Update projects.assigned_worker_id in DB
  2. Worker detects assignment (polling)
  3. Worker clones GitHub repo
  4. Worker fetches + decrypts secrets
  5. Worker runs install scripts
  6. Worker reports success/failure

Console Access Architecture (xterm.js)

  1. Dashboard opens xterm.js terminal
  2. WebSocket to API → routes to worker
  3. Worker spawns PTY, bridges to WebSocket

Implementation Order

  1. Sprint 1: Schema + API (Database, endpoints)
  2. Sprint 2: Main dashboard + Workers page
  3. Sprint 3: Per-project dashboard with tabs
  4. Sprint 4: Assignment flow + Console access

Consequences

  • More intuitive navigation aligned with mental model
  • Better visibility into worker capacity
  • Enables future automatic load balancing
  • Requires migration of existing data

ADR-002: Zero-Knowledge User Data Encryption

Status: Planned Date: January 2026

Context

Users store sensitive data in xSwarm: BYOK API keys (OpenAI, Anthropic), project secrets (.env files), planning discussions. The platform should not have access to this data under any circumstances.

Decision

Implement true zero-knowledge encryption where all encryption/decryption happens on the client (CLI or browser). Server only stores encrypted blobs.

Requirements

  • Only Requirement: GitHub account (already required for xSwarm)
  • No password management: Use device approval model (like Signal/WhatsApp)
  • Recovery: Single recovery code for disaster scenarios

Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│                  ADMIN DATABASE (xSwarm-readable)           │
│  - User auth (GitHub ID, email)                             │
│  - Billing (Stripe ID, subscription)                        │
│  - Project metadata (IDs, task counts for billing)          │
│  - Device registry (public keys for key transfer)           │
│  - Pending approvals (encrypted key blobs in transit)       │
└─────────────────────────────────────────────────────────────┘

                              │ User DB URL (server can connect
                              │ but only sees encrypted blobs)

┌─────────────────────────────────────────────────────────────┐
│              USER PERSONAL DATABASE (encrypted)             │
│  - Projects (name, description, repo_url encrypted)         │
│  - Tasks (title, description, criteria encrypted)           │
│  - BYOK API keys (OpenAI/Anthropic keys encrypted)          │
│  - Project secrets (.env files encrypted)                   │
│  - Planning meetings (specs, decisions encrypted)           │
└─────────────────────────────────────────────────────────────┘

Key Management: Device Approval Model

First Device Ever

  1. GitHub OAuth login
  2. System generates master encryption key
  3. System generates recovery code: “ALPHA-BRAVO-CHARLIE-DELTA”
  4. Display: “Save this recovery code somewhere safe”
  5. User confirms (checkbox)
  6. Key stored locally (~/.xswarm/key or IndexedDB)

New Device (Have Existing Device)

  1. GitHub OAuth login on new device
  2. New device generates keypair, registers public key
  3. “Waiting for approval from existing device…”
  4. Existing device shows: “New device requesting access [Approve] [Deny]”
  5. User clicks Approve
  6. Existing device encrypts master key with new device’s public key
  7. Encrypted blob sent via server (server can’t decrypt)
  8. New device decrypts, stores key locally

Disaster Recovery (Lost All Devices)

  1. GitHub OAuth login
  2. “No approved devices found. Enter recovery code:”
  3. User enters recovery code
  4. Master key derived from recovery code (PBKDF2)
  5. Key stored locally, device registered as approved

Browser ↔ CLI (Same Machine)

  1. CLI runs localhost HTTP server on port 19284
  2. Browser detects localhost server
  3. Browser requests key transfer
  4. CLI prompts user: “Browser requesting access [Allow]”
  5. User allows → key sent over localhost (never hits internet)

Encryption Details

Algorithm: AES-256-GCM (WebCrypto API - native in browser + Node.js) Encrypted data format:

{
  "iv": "<base64-96-bit-random>",
  "ciphertext": "<base64-encrypted-data>",
  "version": 1
}

Database Schema Changes

Admin DB Additions

// Users table additions
user_db_url: text('user_db_url'),              // Turso URL for user's encrypted DB
user_db_token: text('user_db_token'),          // Auth token for user DB
recovery_code_hash: text('recovery_code_hash'), // bcrypt hash (to verify, not derive)
encryption_version: integer('encryption_version').default(1),
// New table: devices (for device approval flow)
devices: {
  id, user_id,
  name: text,                    // "Chad's MacBook Pro"
  device_type: text,             // "cli" | "web"
  public_key: text,              // For end-to-end key transfer
  created_at, last_seen_at,
  is_approved: boolean
}
// New table: pending_approvals (temporary, during key transfer)
pending_approvals: {
  id, user_id,
  requesting_device_id,
  encrypted_key_blob: text,      // Encrypted with requester's public key
  created_at, expires_at         // Auto-expire after 10 minutes
}
// New table: projects_metadata (for billing - no sensitive data)
projects_metadata: {
  id, user_id, slug, status,
  task_count, last_activity_at,
  kanban_token, kanban_enabled
}

User DB Schema

// All *_encrypted fields store JSON: {iv, ciphertext, version}
projects: { id, slug, name_encrypted, description_encrypted, ... }
tasks: { id, project_id, status, title_encrypted, description_encrypted, ... }
byok_api_keys: { id, provider, key_encrypted, name_encrypted, ... }
project_secrets: { id, project_id, file_path, content_encrypted }
planning_meetings: { id, project_id, status, title_encrypted, ... }

Implementation Files

New Files

FilePurpose
packages/shared/crypto.jsIsomorphic encrypt/decrypt, keypair generation
packages/shared/user-db-schema.jsUser DB Drizzle schema
packages/shared/wordlist.jsRecovery code word list (BIP39 subset)
packages/api/src/db/schema.jsdevices, pending_approvals tables
packages/api/src/services/user-db.jsPer-user DB provisioning via Turso API
packages/api/src/routes/devices.jsDevice registration, approval endpoints
packages/web/src/stores/crypto.jsSvelte store for encryption key
packages/web/src/components/DeviceApproval.svelteApproval UI
packages/app/src/daemon/crypto.jsCLI encryption + localhost server

Modified Files

FileChanges
packages/api/src/db/schema.jsAdd devices, pending_approvals, projects_metadata
packages/api/src/routes/auth.jsDevice registration on login
packages/app/src/daemon/auth.jsStore/load encryption key, run localhost server
packages/app/src/index.jsHandle first-time setup flow
packages/web/src/stores/auth.jsIntegrate device approval flow

Security Considerations

  1. Master key never sent to server - only encrypted blobs transit
  2. Device keypairs for transfer - asymmetric encryption prevents server access
  3. Recovery code → key derivation - PBKDF2 with 310k iterations
  4. Unique IV per encryption - prevents pattern analysis
  5. Authenticated encryption (GCM) - detects tampering
  6. Pending approvals expire - 10 minute window limits exposure
  7. Localhost-only browser transfer - never hits internet
  8. No recovery without code - true zero-knowledge trade-off

Implementation Phases

  1. Phase 1: Crypto Module - AES-256-GCM, keypair generation, recovery codes
  2. Phase 2: Database Schema - devices, pending_approvals, user DB schema
  3. Phase 3: Device API - registration, approval, key transfer endpoints
  4. Phase 4: CLI Integration - setup flow, localhost server, encryption wrapper
  5. Phase 5: Web Integration - device approval UI, IndexedDB storage
  6. Phase 6: Testing - unit tests, integration tests, security audit

Consequences

  • True zero-knowledge: xSwarm cannot access user data
  • Frictionless day-to-day: no passwords, just device approval
  • Disaster recovery: single code to save
  • Complexity: multi-device key synchronization
  • Migration: existing users need onboarding flow

Verification Checklists

Dashboard Restructure

  • Build passes: npm run build
  • Project cards render with colors
  • Workers show capacity metrics
  • Project assignment works
  • Console tab opens terminal

Zero-Knowledge Encryption

  • First device setup shows recovery code
  • Recovery code can restore access (all devices lost)
  • New device can be approved from existing device
  • Key transfer is end-to-end encrypted (verify server logs)
  • Data in User DB is encrypted (verify via direct DB query)
  • Wrong recovery code fails gracefully
  • CLI and web can share key via localhost
  • Revoking a device removes its access
  • CLI and web use same encryption (interoperable)