Made Open

Technology Stack

Every technology choice here is deliberate. This document explains what we use, why, and how it maps to the architecture.

Decision Summary

LayerTechnologyRationale
AuthSupabase AuthBatteries-included; JWT; email/OAuth; RLS integration; DID layer added when federation ships
DatabaseSupabase (PostgreSQL)pgvector, pgcrypto, pg-boss all run on Supabase PG; zero additional infra
StorageSupabase StorageS3-compatible; RLS enforced; CDN-ready
Event busNATS JetStreamSingle binary; pub/sub + work queues + persistence; Kafka-compatible if needed
Job queuepg-bossRuns on Supabase PG — zero new infra; ACID; good enough until higher throughput is needed
Job queue (future)BullMQ + RedisAspirational graduation path from pg-boss when throughput becomes a bottleneck. Not yet a hub dependency — only ioredis ^5.9.3 is present in apps/hub/package.json at this commit.
SearchMeilisearchSelf-hosted; instant search; typo-tolerant; sub-50ms at millions of docs
Browser subscriptionsSupabase RealtimeWebSocket push from Supabase PG to browser — replaces polling
Plugin sandboxisolated-vmTrue V8 isolate — separate heap, enforced CPU/memory limits; not worker_threads
AI routingOpenRouterSingle API key to all major cloud LLMs; no per-provider key management
Local LLMOllamaRun Llama/Mistral/Phi locally; used for sensitive data routing
Voice/SMS/VideoTwilio BYOKUser provides their own Twilio account; no Twilio dependency on the platform
Email/Calendar/ContactsMicrosoft Graph APICovers Outlook, Exchange, Office 365, Teams presence; OAuth2
Web frameworkNext.js 15App Router; React Server Components; streaming; Vercel-deployable
UI componentsshadcn/ui + TailwindUnstyled, accessible, copy-paste; consistent with Android design language
API serverFastifyFastest Node.js HTTP server; plugin architecture; TypeScript native
AndroidKotlin + Jetpack ComposeNative; Twilio Android SDKs require native; best background task support
Windows agentTauri (Rust + WebView2)Small binary; no Electron overhead; Playwright sidecar for browser automation
Monorepopnpm workspaces + TurborepoFast installs; cached builds; shared packages
Infra (local dev)Docker ComposeNATS + Meilisearch via containers; no cloud required for dev

Backend Infrastructure

NATS JetStream — Event Bus

NATS JetStream is the non-negotiable backbone. Every significant state change in the platform is published as an immutable event to NATS.

NATS JetStream server
    ├─ Streams: platform-events, connector-data, audit-log
    ├─ Consumers: per-service subscriptions with cursor tracking
    └─ Work queues: for competing-consumer job patterns

Why not Kafka? NATS is a single binary with no JVM, no ZooKeeper, no broker cluster complexity. It scales to Kafka-level throughput when needed, and the event contract is identical — migrating the transport to Kafka requires no service changes.

Why not Supabase Realtime for the event bus? Supabase Realtime is a WebSocket gateway over PostgreSQL's LISTEN/NOTIFY — great for pushing DB changes to browsers, not suitable for the backend event bus. Backend services subscribe to NATS. The browser subscribes to Supabase Realtime.

pg-boss — Job Queue

pg-boss is a PostgreSQL-backed job queue. It runs entirely on the same Supabase PostgreSQL instance the platform already uses.

-- pg-boss creates its own schema
-- All job state stored in PostgreSQL
-- Zero additional infrastructure required

Why pg-boss over BullMQ? Redis is not required. pg-boss uses Supabase PG — one less service to operate. It supports all three queue priorities (realtime/interactive/background), ACID guarantees, and thousands of jobs/day. BullMQ + Redis remains the planned graduation path when throughput demands it, but at this commit BullMQ is not a direct dependency of apps/hub — only ioredis ^5.9.3 is wired in. A Redis container is provisioned in docker-compose.yml (redis:7-alpine) in preparation.

Meilisearch runs as a Docker container alongside NATS. It provides instant, typo-tolerant full-text search across all platform entities.

# docker-compose.yml
meilisearch:
  image: getmeili/meilisearch:v1.12
  environment:
    MEILI_MASTER_KEY: ${MEILI_MASTER_KEY}
  volumes:
    - meili_data:/meili_data

Why not PostgreSQL full-text search? PostgreSQL FTS is good but lacks: relevance ranking, typo tolerance, faceted search, and instant-search performance. Meilisearch handles millions of documents at <50ms. The Search Service syncs entities to Meilisearch on every data.EntityCreated / data.EntityUpdated event.


Supabase

Supabase provides four platform services from a single project:

Supabase ServiceUsed For
PostgreSQLPrimary knowledge graph database; pgvector embeddings; pg-boss jobs; Vault secrets
AuthUser authentication (email, OAuth providers); JWT-based sessions
StorageFile storage (attachments, voice recordings, documents); RLS-protected
RealtimeWebSocket push to browser/Android (entity updates, capability changes, live notifications)

Row Level Security

All tables have RLS enabled. Users can only access their own data. Plugin access is gated by Policy Service — no plugin can bypass RLS to read another user's data.

-- Example: persons table RLS
ALTER TABLE persons ENABLE ROW LEVEL SECURITY;
CREATE POLICY "users own their persons" ON persons
  USING (auth.uid() = owner_id);

Supabase Vault

Credentials are stored in Supabase Vault — PostgreSQL's pgsodium encryption extension:

-- Store a credential
SELECT vault.create_secret('twilio_auth_token', 'ACxxxxxxx');

-- Retrieve for plugin injection
SELECT decrypted_secret FROM vault.decrypted_secrets
WHERE name = 'twilio_auth_token';

Plugins never see credentials in plaintext. The Plugin Manager retrieves from Vault, injects into the V8 sandbox's PluginContext.config, and the plugin uses only what it declared in its manifest.


Plugin Sandbox: isolated-vm

Every plugin runs in a V8 isolate via the isolated-vm npm package. This is not the same as Node.js worker_threads.

isolated-vmworker_threads
MemoryCompletely separate V8 heapShared process heap
CPU limitsEnforced wall + CPU timeNot enforced
Memory limitsEnforced max heap sizeNot enforced
Host accessCannot access host globalsCan access SharedArrayBuffer, etc.
Node.js APIsNot availableAvailable

A plugin cannot import fs, call fetch, or read process.env. It gets only the PluginContext API — and only the parts of it that match its declared permissions.


AI: OpenRouter + Ollama

OpenRouter

OpenRouter is the single integration point for all cloud LLMs:

import OpenAI from 'openai';

const client = new OpenAI({
  baseURL: 'https://openrouter.ai/api/v1',
  apiKey: process.env.OPENROUTER_API_KEY,  // user's own key
});

const response = await client.chat.completions.create({
  model: 'anthropic/claude-3-5-sonnet',  // or any OpenRouter model ID
  messages: [...],
  stream: true,
});

The user provides their own OpenRouter API key via the Credential Wallet. The platform never pays for LLM usage.

Ollama

Ollama provides local LLM inference for privacy-sensitive queries:

// Route sensitive queries to local Ollama
const response = await fetch('http://localhost:11434/api/chat', {
  method: 'POST',
  body: JSON.stringify({
    model: 'llama3.2',
    messages: [...],
    stream: true,
  }),
});

Communication: Twilio BYOK

The user provides their own Twilio Account SID + Auth Token. The platform activates voice, SMS, and video capabilities when these credentials are present.

Twilio ProductSDKUsed By
Voice (outbound calls)Twilio Voice JS SDK (WebRTC)Web app
Voice (mobile calls)Twilio Voice Android SDKAndroid app
SMSTwilio REST APIHub (via Twilio plugin)
VideoTwilio Video JS SDKWeb app
Video (mobile)Twilio Video Android SDKAndroid app
WebhooksTwilio webhook → Hub APIHub webhook receiver

Web: Next.js 15

apps/web/
├── app/                    # App Router — layouts, pages, loading states
│   ├── (auth)/            # Auth flow (login, signup)
│   ├── contacts/          # Contact management
│   ├── calls/             # Dialer + call history
│   ├── messages/          # SMS/email threads
│   ├── calendar/          # Calendar views
│   ├── rules/             # Rule builder
│   ├── ai/                # AI chat interface
│   └── settings/          # Credential wallet, connector config
├── components/            # shadcn/ui components
└── lib/
    ├── supabase/          # Supabase client
    └── hub/               # Hub API client

Server Components handle data fetching. Client Components handle interactive UI (dialer, rule builder, AI chat). Supabase Realtime subscriptions run on the client for live updates.


Android: Kotlin + Jetpack Compose

apps/android/
├── app/src/main/
│   ├── ui/
│   │   ├── contacts/      # Contact list + detail
│   │   ├── calls/         # Dialer, call screen (Twilio Voice SDK)
│   │   ├── messages/      # SMS thread view
│   │   ├── calendar/      # Calendar view
│   │   └── ai/            # AI chat
│   ├── data/
│   │   ├── repository/    # Remote (Supabase) + local (Room) data
│   │   └── sensor/        # Location, call log, WiFi SSID collection
│   └── service/
│       ├── LocationService.kt   # Background location streaming
│       └── SyncWorker.kt        # WorkManager periodic sync to hub

Why native (not React Native or Flutter)? Twilio's Android SDKs are native Kotlin. The background location streaming requires tight WorkManager integration. Battery optimization on Android requires native power management APIs. Feature parity with web is achievable in Jetpack Compose.


Windows Agent: Tauri + Playwright

apps/windows/
├── src-tauri/             # Rust backend
│   ├── src/
│   │   ├── main.rs        # System tray, window management
│   │   ├── sensor.rs      # Active app, clipboard, WiFi SSID tracking
│   │   └── sync.rs        # HTTP client → hub /device-events
├── src/                   # TypeScript/React frontend (WebView2)
│   ├── App.tsx            # Tray UI
│   └── automation/        # Playwright task configuration UI
└── playwright/            # Node.js sidecar
    └── tasks/             # Browser automation task scripts

Tauri vs. Electron: Tauri bundles WebView2 (system browser engine) + Rust backend. Final binary is ~5MB vs. Electron's ~100MB. No Node.js runtime required at user's machine (Rust compiles to native). Playwright runs as a separate sidecar process managed by the Tauri backend.


Monorepo: pnpm + Turborepo

pnpm-workspace.yaml          Declares apps/* and packages/* as workspace packages
turbo.json                   Build pipeline: shared → hub + web → tests

Why pnpm? Symlinked node_modules, not duplicated. Faster installs, smaller disk footprint. Strict mode prevents phantom dependencies.

Why Turborepo? Cached builds — if packages/shared didn't change, downstream builds skip recompilation. Remote caching available (Vercel) for CI speedup.


Dev Environment

# Full local stack
docker-compose up            # NATS JetStream + Meilisearch
supabase start               # Local Supabase instance
pnpm dev                     # Hub + Web in watch mode

# Required environment variables
SUPABASE_URL=...
SUPABASE_SERVICE_ROLE_KEY=...
NATS_URL=nats://localhost:4222
MEILI_URL=http://localhost:7700
MEILI_MASTER_KEY=...
OPENROUTER_API_KEY=...       # Optional for AI features

No cloud services required for local development. Supabase CLI provides a full local PostgreSQL + Auth + Storage + Realtime stack.


Verified Versions (as of 2026-04-12)

Pinned from package.json and docker-compose.yml at commit 46538a9:

Package / ImageVersionLocation
pnpm9.0.0root packageManager
Node engines>=20.0.0root
TypeScript^5.5.0root + all packages
Turborepo^2.0.0root
Next.js^15.0.0apps/web
React^18.3.0apps/web
Tailwind^3.4.0apps/web (devDep)
Fastify^4.28.0apps/hub
@fastify/cors^9.0.0apps/hub
@fastify/formbody^7.4.0apps/hub
@fastify/jwt^8.0.0apps/hub
isolated-vm^5.0.0apps/hub
nats^2.28.0apps/hub
pg-boss^10.0.0apps/hub
ioredis^5.9.3apps/hub
meilisearch^0.44.0apps/hub + packages/ai
@supabase/supabase-js^2.45.0apps/hub + apps/web + packages/ai
@supabase/ssr^0.5.0apps/web
twilio^5.0.0apps/hub
twilio-client^1.15.1apps/web
stripe^22.0.0apps/hub
Microsoft Graph client^3.0.0apps/hub
openai^4.52.0packages/ai
zod^3.23.0hub, web, ai, shared
pino^9.0.0apps/hub
vitest^2.0.0 (web pins ^2.1.9)all packages
playwright^1.59.1apps/web
@xyflow/react^12.10.2apps/web
recharts^3.8.1apps/web
lucide-react^0.400.0apps/web
@changesets/cli^2.30.0root
husky^9.0.0root
Docker: NATSnats:2.10-alpine (-js -m 8222)docker-compose.yml
Docker: Meilisearchgetmeili/meilisearch:v1.12docker-compose.yml
Docker: Redisredis:7-alpinedocker-compose.yml
Docker: LightRAGlightrag/lightrag:latestdocker-compose.yml

Note: BullMQ is intentionally absent — see the pg-boss section above.