Memory
A durable, queryable, evolving knowledge base per user — the fourth primitive next to Database, Storage and Auth. mem0-compatible REST surface, multi-signal retrieval, full audit history, per-tenant isolation.
What it does, in one paragraph
You hand it conversation messages. It (a) asks an LLM to extract durable facts and entities, (b) decides — fact by fact — whether each should be added, used to update an existing memory, used to delete a now-obsolete one, or skipped because we already know it. Writes go into a per-tenant mem.memories table (with pgvector embeddings + entity links + audit history). Reads fuse vector cosine, BM25 keyword search and entity-link boost into a single ranked list.
Data layout
mem.memories— fact text + vector(N) embedding + owner triple + metadatamem.memory_history— append-only ADD/UPDATE/DELETE audit logmem.entities— extracted entities withlinked_memory_ids[]mem.session_messages— rolling N-message window per conversation
Retrieval pipeline
- Embed query (cached in-process via Caffeine)
- Vector search top-K with cosine distance (pgvector HNSW index)
- BM25 keyword search using
ts_rank_cdagainst a GIN index - Entity boost: extract query entities, look them up in
mem.entities, distribute spread-attenuated boost to linked memories - Fuse:
combined = (sim + bm25_norm + entity_boost) / max_possible - Return top-K ranked
Authorization
Every mem endpoint goes through MemoryAuthScope:
- service_role apikey — sees the whole tenant; can pin any userId
- Authenticated user — userId is forced from the JWT
sub; if the body asks for a different userId, 403 - Anonymous (apikey only, no Bearer) — refused
Defense in depth: the repository layer also takes the owner into the WHERE clause (findByIdForOwner), so even if the service layer ever has a bug, the SQL refuses cross-owner reads.
Providers
Chat LLM and embedding provider are independent, both configurable via application.yml:
nubase:
mem:
enabled: true
chat-provider: openai # openai | anthropic | generic
embedding-provider: openai # openai | generic
embedding:
model: text-embedding-3-small
dimensions: 1536 # baked into vector(N) at tenant init
chat:
model: gpt-4o-mini
search:
default-top-k: 5
entity-boost-enabled: true
fts-config: simple # english / zhparser for Chinese
The generic provider talks any OpenAI-compatible API: DashScope, DeepSeek, Moonshot, vLLM, Ollama. Anthropic uses the Claude Messages API and translates the system message + JSON mode automatically.
Operations
- Migrate existing tenants:
POST /auth/v1/admin/init/mem-schema?dbKey=<tenant>— idempotent, backfills themem.*schema and grants on tenants that predate the memory module. - Reset:
POST /mem/v1/reset(service_role) truncates everymem.*table for the current tenant. The Studio Settings page gates this behind a type-the-project-name confirmation. - Disable globally: Set
nubase.mem.enabled=false— API returns 404, new tenants skip mem schema entirely, migrate endpoint refuses. - pgvector required: when
enabled=truethe tenant init fail-fasts if pgvector isn't available on the Postgres server. Recommended image:pgvector/pgvector:pg15.