Gateway Module — `memory/`
1. Purpose
The persistent-memory tier of the Lisa Memory Gateway. It owns three durable
document types — preference, knowledge, summary — stored in SQLite with
Voyage-AI embeddings for semantic recall and an FTS5 sidecar for keyword recall.
It also owns the shared database substrate: memory/db.ts (1,258 lines) is
the single schema-owning module for the entire gateway — every other module's
tables, virtual tables, triggers, and migrations are declared here, not in the
consuming module. The module exposes an EmbeddingProvider (Voyage → keyword
fallback, resilient wrapper) that psychic-cache, context, and vault-index all
consume for their own embeddings.
Architectural note (code-vs-doc): the module's REST surface is not defined
inside memory/. The memory/index.ts barrel is a factory (createMemoryLayer)
plus a background-embedding helper — it exports no Express router. The
/api/health, /api/health/detail, and GET/POST/PUT/DELETE /api/memory[/:id]
endpoints are declared inline in the top-level server/index.ts (≈ lines
531–731), wired directly against memory.memoryRepo / memory.embeddingRepo.
This is the one gateway module whose HTTP surface lives outside its own folder.
2. File Inventory
| File | Lines | Responsibility |
|---|---|---|
db.ts | 1,258 | Gateway-wide SQLite schema owner: all CREATE TABLE/CREATE VIRTUAL TABLE/CREATE TRIGGER/CREATE INDEX DDL + idempotent migrations for every module (memory, psychic_cache, agent_memory, vault_*, knowledge_graph, cross_references, dispatch, sessions, activity, source_weights, context_assembly_log, curator, compute_capture, skills_telemetry). openDatabase() factory |
repositories.ts | 310 | createMemoryRepository (CRUD + FTS on memory_documents/memory_fts) + createEmbeddingRepository (KNN on vec_embeddings via sqlite-vec). Read-path Zod content validation (Preference/Knowledge/SummaryContentSchema) |
embeddings.ts | 288 | createVoyageProvider (batched, retry/backoff, VoyageAuthError 401 sentinel), createKeywordFallbackProvider (trigram/unigram hash TF, L2-normalised), createResilientProvider (primary→fallback with onAuthFailure/onPrimaryRecovered hooks, dim pad/truncate) |
cache.ts | 198 | createLRUCache (Map insertion-order LRU + TTL sweep) + createCachedMemoryRepository (read-through decorator on findById/findByIds/findByType, invalidate-on-write) |
index.ts | 143 | createMemoryLayer factory (assembles providers + repos + LRU caches) + scheduleExchangeEmbedding background helper |
types.ts | 134 | Interfaces: EmbeddingProvider, MemoryDocument(+content unions), MemoryRepository, EmbeddingRepository, Cache, MemoryLayer |
| Total | 2,331 | (REST routes for this module live in server/index.ts, not counted here) |
3. Public API Surface
REST Endpoints
All routes declared inline in server/index.ts. Auth posture per AK-415/435/436:
/api/health is the only public GET (liveness only); every other GET requires a
Bearer; every mutation requires the write Bearer only.
| Method | Path | Auth | Body / Query | Response | Side Effects |
|---|---|---|---|---|---|
| GET | /api/health | public (health only) | — | {ok, gateway_uptime_seconds} (liveness-only; recon fields stripped) | none |
| GET | /api/health/detail | Bearer | — | {ok, uptime, version, schema_version, status, timestamp, memory.provider, modules{}} | none |
| GET | /api/memory | Bearer | ?limit&offset&type | {data: MemoryDocument[]} | none |
| GET | /api/memory/:id | Bearer | — | {data: MemoryDocument} / 404 | none |
| POST | /api/memory | Bearer | CreateMemoryInputSchema (Zod discriminated content union) | 201 {data: MemoryDocument} / 400 | DB insert + FTS insert; async embed → vec_embeddings |
| PUT | /api/memory/:id | Bearer | UpdateMemoryInputSchema (partial content/tokenCount) | {data: MemoryDocument} / 404 / 400 | DB update + FTS update; async re-embed if content changed |
| DELETE | /api/memory/:id | Bearer | — | {data:{deleted:true}} / 404 | DB delete + FTS delete + vec delete |
POST /api/memory/searchwas removed (D27) — sole consumer of the deletedcreateContextRetrievalService. Canonical retrieval is nowPOST /api/context/assemble.
MCP Tools
Consumed via the persistent-memory dual-write path.
| Tool Name | Raw payload? | Endpoint | Notes |
|---|---|---|---|
commit_memory | No (flat) | POST /api/memory | Nested content object ({type, content:{…}}) |
commit_memory_raw | Yes ({payload}) | POST /api/memory | Staleness-immune twin |
update_memory | No | PUT /api/memory/:id | — |
| (list/get/delete) | — | GET/DELETE /api/memory[/:id] | No dedicated MCP tool; REST-only |
4. Internal API
Exported for cross-module use:
openDatabase(): Database— the shared handle every module's factory receives (db.ts).createMemoryLayer(config): MemoryLayer—{embeddingProvider, embeddingRepo, memoryRepo, close()}.scheduleExchangeEmbedding(layer, opts)— fire-and-forget knowledge-doc embedding of a chat exchange (skips <minTokens, default 50).createVoyageProvider/createKeywordFallbackProvider/createResilientProvider+ResilientProviderHooks,VoyageAuthError— imported byserver/index.tsfor the degraded-alert wiring.createMemoryRepository/createEmbeddingRepository— consumed by context adapters.- Type re-exports:
MemoryLayer,MemoryDocument,MemoryContent,EmbeddingProvider, etc. (context, psychic-cache, vault-index all importEmbeddingProviderfrom here).
5. Background Services
- Async embedding on write —
setImmediateafterPOST/PUT /api/memoryand insidescheduleExchangeEmbedding; embedding failure is caught + logged, never blocks the response (FTS row still lands). - LRU + TTL caches —
docCache(max 1,000, 5-min TTL),typeCache(max 200, 2-min TTL). Eviction is lazy (onset/get), no timer.typeCache.clear()on any write for conservative invalidation. - Resilient provider recovery —
onPrimaryRecoveredfires on first Voyage success after a failure, clearing the degraded-suppression flag (AK-321/H-07). No timer; edge-triggered.
6. Data Contracts
Read-path content validation (repositories.ts):
PreferenceContentSchema={key, value, source, confidence:number}KnowledgeContentSchema={topic, content, sourceSessionIds:string[]=[], lastReferencedAt:string=''}SummaryContentSchema={summary, keyTopics[], decisionsReached[], unresolvedQuestions[]}
Write-path (server/index.ts):
CreateMemoryInputSchema={type:enum, content:union(3 above), tokenCount?, userId?, sessionId?}UpdateMemoryInputSchema={content?:union, tokenCount?}
VoyageResponseSchema (embeddings.ts) = {data:[{embedding:number[]}], usage:{total_tokens}}.
Owned tables (declared in db.ts): memory_documents (+4 indexes),
conversation_summaries, memory_fts (fts5), vec_embeddings (vec0, 512-dim),
schema_meta. Note db.ts also declares every other module's tables.
7. Dependencies
- Gateway modules consumed:
shared/search-utils(buildFts5Query). Depended-on-by: psychic-cache, context, vault-index, agent-memory, dispatch, sessions, activity, etc. (all consumeopenDatabase+EmbeddingProvider). - External libraries:
better-sqlite3,sqlite-vec(vec0 virtual tables),zod, Voyage AI HTTP API (api.voyageai.com/v1/embeddings), Nodecrypto. - Environment variables:
VOYAGE_API_KEY(credential-store-first: systemdLoadCredential→ env fallback; empty → keyword-fallback-only boot),voyageModel(defaultvoyage-3-lite, 512-dim),dbPath(the gateway SQLite datastore).
8. Test Coverage
| Layer | File | Tests |
|---|---|---|
| LRU + cached repo | test/cache.test.ts | 23 |
| Voyage/keyword/resilient providers | test/embeddings.test.ts | 21 |
| PUT validation (D884/AK-323) | test/memory-update-validation.test.ts | 1 describe block (multiple assertions) |
MCP commit_memory_raw | test/commit-memory-raw.test.ts | — (raw-twin contract) |
No dedicated memory/repositories.ts suite; the read/write CRUD is exercised
indirectly via context + search + validation suites. Coverage of db.ts
migrations is via cross-module suites, not a targeted migration test.
9. Known Limitations
- REST surface lives in
server/index.ts, not the module — the one gateway module with no self-contained router. A future refactor should extract acreateMemoryRouterfor symmetry; until then, changes to memory endpoints require editing the 900+-line root file. db.tsis a god-file — 1,258 lines owning every module's schema. High blast radius: any migration bug affects the whole gateway. Accepted for single-DB-handle simplicity, but flagged for the LisaOSMap dependency graph.- Keyword fallback quality — trigram/unigram hashing produces low-quality vectors; recall degrades (never fails) when Voyage is unreachable.
- No direct-repository unit suite for
repositories.ts— CRUD correctness is inferred from consumers.
10. Change History
| Date | Dispatch | Summary |
|---|---|---|
| 2026-07-04 | 2295 | Initial per-module spec filed (audit campaign Phase 4, §5.2 template). Documented inline-REST-in-root and db.ts-god-file findings |
| (historical) | D27 | Removed POST /api/memory/search + createContextRetrievalService + memory/retrieval.ts (dead code) |
| (historical) | D80 (AK-264) | Zod discriminated union on POST /api/memory content |
| (historical) | D321/H-07 | VoyageAuthError 401 sentinel + resilient-provider degraded-alert hooks |
| (historical) | D884 (AK-323) | Zod validation on PUT /api/memory/:id |
| (historical) | AK-321 C5 | Credstore-first VOYAGE_API_KEY loading (fail-closed audit) |