Architecture overview
Engram is a NestJS application (apps/mcp-server) that speaks the Model
Context Protocol on one side and drives a small set of storage services on the
other. Every capability — storing, recalling, consolidating, decaying,
reindexing — is exposed as an MCP tool, and every tool boils down to the same
pipeline: validate input with a strict Zod schema → resolve the acting
tenant → call a service → persist to Postgres (source of truth) and derived
indexes.
The pages in this section explain why the system is shaped the way it is. For operational steps, see the how-to guides; for exact tool and variable listings, the reference.
System diagram
Section titled “System diagram”flowchart LR subgraph Clients A["MCP client<br/>(Claude Code, Copilot, Cursor, ...)"] end
subgraph Server["apps/mcp-server (NestJS)"] T["Transport<br/>stdio | streamable-http"] AUTH["McpAuthMiddleware<br/>JWT / API key"] RATE["McpRateLimitMiddleware"] MC["MemoryController<br/>TOOL_MANIFEST + handlers"] MS["MemoryService"] CONS["ConsolidationService<br/>DecayService<br/>CorpusConsolidationService"] end
subgraph Storage STM["STM: Postgres<br/>(memories table, TTL)"] LTM["LTM: Postgres<br/>(source of truth)"] VS["Vector store: pgvector<br/>(embedding_vec in Postgres)"] EMB["Embeddings<br/>ollama | openai | local | disabled"] end
A -->|tools/call| T --> AUTH --> RATE --> MC --> MS MS --> STM MS --> LTM MS --> VS MS --> EMB CONS --> STM CONS --> LTM(Mermaid source — renders as a diagram on GitHub and in Mermaid-aware viewers.)
The load-bearing decisions
Section titled “The load-bearing decisions”Postgres is the source of truth; everything else is derived or ephemeral.
Long-term memories live in the single Memory table
(memory model). The vector index can be
rebuilt from Postgres at any time
(reindex & backfill), and short-term
memories are deliberately allowed to expire
(memory tiers). This ordering means a lost
or corrupted vector index is an inconvenience, never data loss.
One binary, two deployment profiles. The same server runs single-user
(lite, with the auth/organization stack left unwired) or multi-tenant
(standard, the default). Both run on the same Postgres (with the pgvector
extension) — the only backing service — and differ only in whether the auth
stack is active. Modules are wired conditionally from ProfileCapabilities
(deployment profiles).
Degrade, don’t fail. Embeddings are optional at every seam: if the provider is disabled or unreachable, writes still succeed (vector-less) and recall falls back to what is available (embeddings, vector store).
The tenant boundary is the credential, not the request body. With auth
enabled, the verified key’s userId overrides anything the client sends, and
a multi-tenant HTTP server refuses to boot unauthenticated in every
NODE_ENV (auth & multi-tenancy).
Background jobs must never clobber a concurrent edit. Every lifecycle mutation — decay, dedup annotation, contradiction marking, corpus consolidation — goes through version-guarded compare-and-set writes (consolidation & decay, concurrency policy).
Section map
Section titled “Section map”| Page | Explains |
|---|---|
| Memory model | The Memory row, its metadata JSON, versioning, and the supporting tables |
| Memory tiers | STM vs LTM, TTLs, access counting, promotion |
| Deployment profiles | lite / standard and ProfileCapabilities |
| Vector store | pgvector and the runtime-managed embedding_vec column |
| Embeddings | Providers, dimensions, null-safe degradation |
| Reindex & backfill | Cursor-resumable rebuilds, the job queue |
| Auth & multi-tenancy | Boot fail-safe, per-agent keys, scopes, delegation |
| Consolidation & decay | STM→LTM promotion, corpus consolidation, dedup, contradictions |