Skip to content

Deployment profiles

Engram runs in two contexts: a single-operator host that wants persistence without an auth stack, and a multi-tenant server with real infrastructure. Instead of two builds or a thicket of feature flags, one binary reads DEPLOYMENT_PROFILE and resolves a ProfileCapabilities record (packages/config/src/profile.ts) that every module consults at boot.

Both profiles run on the same backing service — PostgreSQL with the pgvector extension, the only external dependency. What differs is whether the multi-tenant auth/organization stack is wired in.

Capability lite standard
requiresDatabase yes yes
persistent yes yes
multiTenant no yes
  • lite — single-user local durability on Postgres. The auth/organization stack is not wired: no OAuth sessions, no per-agent API-key enforcement, no rate limiting. For a personal, always-on server.
  • standard — the default. The same Postgres storage plus the full multi-tenant stack: authentication, per-agent API keys, organizations, and rate limiting.

The default is standard; the config loader fails fast at boot when DATABASE_URL is missing, rather than failing at first use.

The value enterprise is a deprecated alias for standard (it maps to the same capabilities), kept so existing deployments keep booting. The old memory profile — zero external services, in-process adapters — was removed: every profile now runs on Postgres.

Capabilities drive wiring, not scattered ifs

Section titled “Capabilities drive wiring, not scattered ifs”

AppModule.forRoot() consults the capability record once. Because both profiles set requiresDatabase, PrismaModule and AuthModule are always imported; AuthModule then gates its OAuth sessions, rate limiting, and the multi-tenant boot fail-safe on multiTenant internally. Services take their optional dependencies with @Optional() injection, so the same MemoryService code path works whether or not the auth stack is present. The alternative — checking process.env.DEPLOYMENT_PROFILE at each call site — is exactly the drift-prone pattern this design avoids: capability checks happen at composition time, in one place.

MemoryController.getMcpTools() builds the MCP manifest through a capability-aware filter, so a profile never advertises a tool it cannot serve. Because both profiles are Postgres-backed, every tool — including the async reindex queue (queue_reindex_memories, get_reindex_status, cancel_reindex_job, retry_reindex_job), consolidate_corpus, and the Postgres-only export_memories / import_agent_memory — is currently served in both. The filter hook is retained so a future profile can exclude tools again without re-plumbing the call site.

Only standard sets multiTenant: true, and that flag is load-bearing for security: it is what arms the boot-time auth fail-safe — a multi-tenant server over streamable-http refuses to start unauthenticated. lite is exempt because there is no cross-tenant data to protect.

Profiles are a ladder, not a fork: start on lite for a personal server, and move to standard when multi-tenancy or scale does. Both share identical Postgres storage, so the move is a configuration change — enabling the auth stack — not a data migration. The runbook lives in Migrate lite to standard.