Made Open

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:

GroupPhasesCountExamples
Core1-2~18EventBus, DataService, PolicyService, PluginManager, ETL, Rules, Channels
AI35AgentService, ToolRegistry, MultiAgentOrchestrator, AgentMemoryService
Federation45DIDService, VCService, ActivityPubService, FederationService
Marketplace + Governance5-610MarketplaceService, ReputationService, DAOService, VotingService
Web382WalletService, TimeCreditBridgeService
Infrastructure9-3438+NotificationService, CacheService, RateLimiterService, OAuthService, BillingService, AudioETLService, DeviceCommandService

Tech Stack

LayerTechnology
HTTP serverFastify
Event busNATS JetStream
Job queuepg-boss (on Supabase PG)
Plugin sandboxisolated-vm (V8 isolates)
DatabaseSupabase (PostgreSQL + pgvector)
Credential storeSupabase Vault
StorageSupabase Storage
SearchMeilisearch client
LanguageTypeScript

Internal Service Modules

Plugin Manager

The Plugin Manager owns the full plugin lifecycle:

  1. Discovery: Scans plugins/ directory for plugin.json manifests
  2. Validation: Validates manifest schema; verifies permissions are declared
  3. Sandboxing: Creates a V8 isolate via isolated-vm for each plugin
  4. Configuration: Retrieves credentials from Supabase Vault; injects into PluginContext
  5. 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.DataReceived events 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:

QueueLatency targetExample jobs
realtime< 100msCall routing, rule evaluation
interactive< 5sSend SMS, AI query
backgroundMinutesMS 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`.