Technology Stack
Every technology choice here is deliberate. This document explains what we use, why, and how it maps to the architecture.
Decision Summary
| Layer | Technology | Rationale |
|---|---|---|
| Auth | Supabase Auth | Batteries-included; JWT; email/OAuth; RLS integration; DID layer added when federation ships |
| Database | Supabase (PostgreSQL) | pgvector, pgcrypto, pg-boss all run on Supabase PG; zero additional infra |
| Storage | Supabase Storage | S3-compatible; RLS enforced; CDN-ready |
| Event bus | NATS JetStream | Single binary; pub/sub + work queues + persistence; Kafka-compatible if needed |
| Job queue | pg-boss | Runs on Supabase PG — zero new infra; ACID; good enough until higher throughput is needed |
| Job queue (future) | BullMQ + Redis | Aspirational 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. |
| Search | Meilisearch | Self-hosted; instant search; typo-tolerant; sub-50ms at millions of docs |
| Browser subscriptions | Supabase Realtime | WebSocket push from Supabase PG to browser — replaces polling |
| Plugin sandbox | isolated-vm | True V8 isolate — separate heap, enforced CPU/memory limits; not worker_threads |
| AI routing | OpenRouter | Single API key to all major cloud LLMs; no per-provider key management |
| Local LLM | Ollama | Run Llama/Mistral/Phi locally; used for sensitive data routing |
| Voice/SMS/Video | Twilio BYOK | User provides their own Twilio account; no Twilio dependency on the platform |
| Email/Calendar/Contacts | Microsoft Graph API | Covers Outlook, Exchange, Office 365, Teams presence; OAuth2 |
| Web framework | Next.js 15 | App Router; React Server Components; streaming; Vercel-deployable |
| UI components | shadcn/ui + Tailwind | Unstyled, accessible, copy-paste; consistent with Android design language |
| API server | Fastify | Fastest Node.js HTTP server; plugin architecture; TypeScript native |
| Android | Kotlin + Jetpack Compose | Native; Twilio Android SDKs require native; best background task support |
| Windows agent | Tauri (Rust + WebView2) | Small binary; no Electron overhead; Playwright sidecar for browser automation |
| Monorepo | pnpm workspaces + Turborepo | Fast installs; cached builds; shared packages |
| Infra (local dev) | Docker Compose | NATS + 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 — Full-Text Search
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 Service | Used For |
|---|---|
| PostgreSQL | Primary knowledge graph database; pgvector embeddings; pg-boss jobs; Vault secrets |
| Auth | User authentication (email, OAuth providers); JWT-based sessions |
| Storage | File storage (attachments, voice recordings, documents); RLS-protected |
| Realtime | WebSocket 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-vm | worker_threads | |
|---|---|---|
| Memory | Completely separate V8 heap | Shared process heap |
| CPU limits | Enforced wall + CPU time | Not enforced |
| Memory limits | Enforced max heap size | Not enforced |
| Host access | Cannot access host globals | Can access SharedArrayBuffer, etc. |
| Node.js APIs | Not available | Available |
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 Product | SDK | Used By |
|---|---|---|
| Voice (outbound calls) | Twilio Voice JS SDK (WebRTC) | Web app |
| Voice (mobile calls) | Twilio Voice Android SDK | Android app |
| SMS | Twilio REST API | Hub (via Twilio plugin) |
| Video | Twilio Video JS SDK | Web app |
| Video (mobile) | Twilio Video Android SDK | Android app |
| Webhooks | Twilio webhook → Hub API | Hub 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 / Image | Version | Location |
|---|---|---|
| pnpm | 9.0.0 | root packageManager |
| Node engines | >=20.0.0 | root |
| TypeScript | ^5.5.0 | root + all packages |
| Turborepo | ^2.0.0 | root |
| Next.js | ^15.0.0 | apps/web |
| React | ^18.3.0 | apps/web |
| Tailwind | ^3.4.0 | apps/web (devDep) |
| Fastify | ^4.28.0 | apps/hub |
@fastify/cors | ^9.0.0 | apps/hub |
@fastify/formbody | ^7.4.0 | apps/hub |
@fastify/jwt | ^8.0.0 | apps/hub |
isolated-vm | ^5.0.0 | apps/hub |
nats | ^2.28.0 | apps/hub |
pg-boss | ^10.0.0 | apps/hub |
ioredis | ^5.9.3 | apps/hub |
meilisearch | ^0.44.0 | apps/hub + packages/ai |
@supabase/supabase-js | ^2.45.0 | apps/hub + apps/web + packages/ai |
@supabase/ssr | ^0.5.0 | apps/web |
twilio | ^5.0.0 | apps/hub |
twilio-client | ^1.15.1 | apps/web |
stripe | ^22.0.0 | apps/hub |
| Microsoft Graph client | ^3.0.0 | apps/hub |
openai | ^4.52.0 | packages/ai |
zod | ^3.23.0 | hub, web, ai, shared |
pino | ^9.0.0 | apps/hub |
vitest | ^2.0.0 (web pins ^2.1.9) | all packages |
playwright | ^1.59.1 | apps/web |
@xyflow/react | ^12.10.2 | apps/web |
recharts | ^3.8.1 | apps/web |
lucide-react | ^0.400.0 | apps/web |
@changesets/cli | ^2.30.0 | root |
husky | ^9.0.0 | root |
| Docker: NATS | nats:2.10-alpine (-js -m 8222) | docker-compose.yml |
| Docker: Meilisearch | getmeili/meilisearch:v1.12 | docker-compose.yml |
| Docker: Redis | redis:7-alpine | docker-compose.yml |
| Docker: LightRAG | lightrag/lightrag:latest | docker-compose.yml |
Note: BullMQ is intentionally absent — see the pg-boss section above.