Hub — Core Platform
The hub is the architectural heart of Made Open. It runs all backend services, hosts all plugins, maintains the event bus, and exposes the Sovereign API to clients.
What It Is
The hub is a single Fastify process containing all backend service modules. Services are internal modules, not separate processes — this reduces operational complexity during early development. Services split into separate processes only when load demands it.
apps/hub/
├── src/
│ ├── main.ts # Process entry point — bootstraps all services
│ ├── api/ # Fastify routes (REST API + webhook receivers)
│ └── services/
│ ├── plugins/ # Plugin lifecycle, V8 sandboxing (PluginManager)
│ ├── connector/ # Connector plugin orchestration (ConnectorBridgeService)
│ ├── channels/ # Channel plugin orchestration
│ ├── etl/ # Raw data → unified entity model
│ ├── data/ # Knowledge graph CRUD
│ ├── search/ # Meilisearch client + indexing
│ ├── rules/ # Rules DSL evaluation engine
│ ├── policy/ # Access control + audit log
│ ├── event-bus/ # NATS JetStream client
│ └── … # 86 service directories total
├── plugins/ # Plugin source (loaded by PluginManager at runtime)
│ ├── connectors/ # 8 connectors: ms-graph, twilio, fitbit,
│ │ # google-fit, plaid, nutritionix, usda-food, device
│ └── channels/ # 2 channels: twilio, whatsapp
├── package.json
└── tsconfig.json
Complete Service Inventory
The hub contains 86 service directories under apps/hub/src/services/. See ../02-architecture/service-reference.md for the full list with dependencies and startup order.
Services by phase group:
| Group | Phases | Count | Examples |
|---|---|---|---|
| Core | 1-2 | ~18 | EventBus, DataService, PolicyService, PluginManager, ETL, Rules, Channels |
| AI | 3 | 5 | AgentService, ToolRegistry, MultiAgentOrchestrator, AgentMemoryService |
| Federation | 4 | 5 | DIDService, VCService, ActivityPubService, FederationService |
| Marketplace + Governance | 5-6 | 10 | MarketplaceService, ReputationService, DAOService, VotingService |
| Web3 | 8 | 2 | WalletService, TimeCreditBridgeService |
| Infrastructure | 9-34 | 38+ | NotificationService, CacheService, RateLimiterService, OAuthService, BillingService, AudioETLService, DeviceCommandService |
Tech Stack
| Layer | Technology |
|---|---|
| HTTP server | Fastify |
| Event bus | NATS JetStream |
| Job queue | pg-boss (on Supabase PG) |
| Plugin sandbox | isolated-vm (V8 isolates) |
| Database | Supabase (PostgreSQL + pgvector) |
| Credential store | Supabase Vault |
| Storage | Supabase Storage |
| Search | Meilisearch client |
| Language | TypeScript |
Internal Service Modules
Plugin Manager
The Plugin Manager owns the full plugin lifecycle:
- Discovery: Scans
plugins/directory forplugin.jsonmanifests - Validation: Validates manifest schema; verifies permissions are declared
- Sandboxing: Creates a V8 isolate via
isolated-vmfor each plugin - Configuration: Retrieves credentials from Supabase Vault; injects into
PluginContext - Health monitoring: Tracks plugin health; restarts crashed plugins with backoff
The Plugin Manager enforces the PluginContext API — the only interface a plugin has to the outside world. Plugins cannot import Node.js modules or access the host process.
Connector Service
Manages connector plugins. For each connector plugin:
- Schedules periodic sync jobs (cron via pg-boss)
- Receives webhook push notifications from external services (MS Graph change notifications, etc.)
- Publishes
connector.DataReceivedevents to NATS when raw data arrives - The ETL Service processes these events asynchronously
Channel Service
Manages channel plugins (Twilio, WhatsApp). Routes inbound and outbound communication:
- Receives provider webhooks → normalizes → publishes
communication.*events - Routes outbound messages through the appropriate channel plugin
- Maintains call sessions, generates WebRTC access tokens (calling service:
apps/hub/src/services/calling/)
ETL Service
Transforms raw connector data into unified entities:
connector.DataReceived
│
▼
ProcessIncomingDataJob (background queue)
│
▼
ETL Service
├─ normalize (clean fields, standardize formats)
├─ enrich (resolve existing person by email match)
├─ deduplicate (merge with existing entity if same external ID)
└─ output unified entities → Data Service
Data Service
The single point of truth for all knowledge graph reads and writes. All other services use the Data Service API — no service accesses the database directly except the Data Service.
interface DataService {
create<T extends Entity>(entityType: string, data: Partial<T>): Promise<T>;
update<T extends Entity>(entityType: string, id: string, data: Partial<T>): Promise<T>;
delete(entityType: string, id: string): Promise<void>;
query<T extends Entity>(entityType: string, filter: Filter): Promise<T[]>;
getById<T extends Entity>(entityType: string, id: string): Promise<T | null>;
}
Publishes data.EntityCreated / data.EntityUpdated / data.EntityDeleted events after every write.
The Data Service also exposes getClient() for direct Supabase client access (used by credential routes for Vault RPC operations) and count(table, filter?) for efficient row counting via Supabase's { count: 'exact', head: true }.
Search Service
Subscribes to data.Entity* events and keeps Meilisearch in sync. Handles:
- Index creation and schema configuration
- Document upserts and deletions
- Search query proxying from the REST API
Rules Service
Evaluates all enabled rules against every relevant event:
communication.CallStarted received
│
▼
Rules Service: load all enabled rules with trigger=communication.CallStarted
│
├─ Evaluate each rule's condition_ast against the event + current context
│ ├─ Built-in operators: person group, location, time-of-day, channel type
│ └─ Plugin-provided operators: location.IsUserAtVenue, ai.classify_text
│
└─ For matching rules: enqueue action jobs
└─ SendOutgoingMessageJob, PlaceCallJob, etc.
Policy Service
Enforces zero-trust access control and writes to the audit log. Every service calls Policy before accessing data. Policy decisions are:
- Synchronous for interactive operations
- Logged asynchronously (audit log write doesn't block the request)
Job Queue (pg-boss)
Three queues with different SLAs:
| Queue | Latency target | Example jobs |
|---|---|---|
realtime | < 100ms | Call routing, rule evaluation |
interactive | < 5s | Send SMS, AI query |
background | Minutes | MS Graph sync, search indexing |
Event Bus (NATS)
NATS JetStream client with:
- Publisher: typed
emitEvent()function used by all services - Subscriber: service registrations during startup
- Streams:
platform-events(30 day retention),connector-data(7 day),audit-log(1 year)
Sovereign API (REST)
The hub exposes a REST API for clients (web, android) and external webhooks:
POST /api/auth/* (proxy to Supabase Auth)
GET /api/persons List contacts
GET /api/persons/:id Get contact detail
POST /api/persons Create contact
PATCH /api/persons/:id Update contact
GET /api/conversations List conversations
GET /api/conversations/:id Get conversation with messages
POST /api/conversations/:id/reply Send message in conversation
POST /api/calls/place Place outbound call
GET /api/calls/token WebRTC access token (Twilio)
GET /api/rules List rules
POST /api/rules Create rule
PATCH /api/rules/:id Update rule
DELETE /api/rules/:id Delete rule
GET /api/capabilities Active capabilities for current session
POST /api/credentials Add a credential to the wallet
DELETE /api/credentials/:type Remove a credential
GET /api/credentials List credential metadata for authenticated user
GET /api/stats Dashboard statistics (contacts, messages, rules, credentials)
GET /api/search?q= Full-text search
POST /device-events Device sensor data (Android, Windows)
POST /webhooks/twilio/voice Twilio voice webhook
POST /webhooks/twilio/sms Twilio SMS webhook
POST /webhooks/ms-graph MS Graph change notification
First Testable Workflow
# NOTE: `make quickstart` seeds a demo user (demo@made-open.dev / demopassword)
# with pre-loaded credentials, contacts, rules, and conversations.
# Steps 4-7 below are already done for the demo user.
1. docker-compose up # Start NATS + Meilisearch
2. supabase start # Start local Supabase
3. pnpm dev # Start hub
4. POST /api/credentials # Add Twilio Account SID + Auth Token
5. GET /api/capabilities # Verify: voice, sms, video now active
6. POST /api/credentials # Add MS Graph OAuth token
7. GET /api/capabilities # Verify: contacts, calendar, email now active
8. Wait 30 seconds # MS Graph connector syncs contacts
9. GET /api/persons # Verify: contacts from Exchange appear
10. POST /api/rules # Create rule: if caller = Partner, send SMS
11. Trigger inbound Twilio call # Via test webhook
12. Verify: SMS sent (Communication Service log)
13. Verify: audit_log has entries for each action
14. Verify: NATS has communication.CallStarted event
This is the core integration proof. Everything downstream depends on this working.
Running Locally
# One-command setup (recommended)
make quickstart
# or: bash scripts/quickstart.sh
# Manual setup
docker-compose up -d # NATS + Meilisearch
supabase start # Local Supabase stack
pnpm install
pnpm dev # Hub starts on :4101
# The quickstart script auto-generates .env files with correct Supabase keys.
# For manual setup, copy .env.example and fill in keys from `supabase status`.