Files
nexusAI/docs/services/memory-service.md

4.7 KiB

Memory Service

Package: @nexusai/memory-service
Location: packages/memory-service
Deployed on: Mini PC 1 (192.168.0.81)
Port: 3002

Purpose

Responsible for all reading and writing of long-term memory. Acts as the sole interface to both SQLite and Qdrant — no other service accesses these stores directly.

Dependencies

  • express — HTTP API
  • better-sqlite3 — SQLite driver
  • @qdrant/js-client-rest — Qdrant vector store client
  • dotenv — environment variable loading
  • @nexusai/shared — shared utilities and constants

Environment Variables

Variable Required Default Description
PORT No 3002 Port to listen on
SQLITE_PATH Yes Path to SQLite database file
QDRANT_URL No http://localhost:6333 Qdrant instance URL

Internal Structure

src/
├── db/
│   ├── index.js       # SQLite connection + initialization
│   └── schema.js      # Table definitions, indexes, FTS5, triggers
├── episodic/
│   └── index.js       # Session + episode CRUD and FTS search
├── semantic/
│   └── index.js       # Qdrant collection management, upsert, search, delete
├── entities/          # Entity + relationship CRUD (upcoming)
└── index.js           # Express app + route definitions

SQLite Schema

Five core tables:

  • sessions — top-level conversation containers, identified by an external_id
  • episodes — individual exchanges (user message + AI response) tied to a session
  • entities — named things the system learns about (people, places, concepts)
  • relationships — directional labeled links between entities
  • summaries — condensed episode groups for efficient context retrieval

An episodes_fts virtual table enables keyword search across all episodes. Three triggers (episodes_fts_insert, episodes_fts_update, episodes_fts_delete) keep the FTS index automatically in sync with the episodes table.

SQLite Configuration

  • journal_mode = WAL — non-blocking reads during writes
  • foreign_keys = ON — enforces referential integrity and cascade deletes
  • PRAGMAs are set via db.pragma() separately from db.exec()

Qdrant / Semantic Layer

Three collections are initialized on service startup (created if they don't already exist):

Collection Purpose
episodes Embeddings for individual conversation exchanges
entities Embeddings for named entities
summaries Embeddings for condensed episode summaries

All collections use 768-dimension vectors with Cosine similarity, matching the output of the nomic-embed-text embedding model via Ollama.

Vector dimension and distance metric are defined in @nexusai/shared constants (QDRANT.VECTOR_SIZE, QDRANT.DISTANCE_METRIC) — not hardcoded in this service.

Semantic Layer Operations

Each collection exposes three operations via helper functions in src/semantic/index.js:

  • Upsert — stores a vector with a payload containing the SQLite row ID, enabling lookups back to the full content after a vector search
  • Search — returns the top-k most similar vectors, with optional Qdrant filter
  • Delete — removes a vector point by ID

The wait: true flag is used on all write operations so the caller receives confirmation only after Qdrant has committed the change.

Hybrid Retrieval Pattern

Qdrant and SQLite work as a pair — neither operates in isolation:

  1. Query is embedded and searched in Qdrant → returns IDs + similarity scores
  2. IDs are used to fetch full content from SQLite
  3. Results are ranked and assembled into a context package

Endpoints

Health

Method Path Description
GET /health Service health check

Sessions

Method Path Description
POST /sessions Create a new session
GET /sessions/:id Get session by internal ID
GET /sessions/by-external/:externalId Get session by external ID
DELETE /sessions/:id Delete session (cascades to episodes + summaries)

POST /sessions body:

{
  "externalId": "unique-session-id",
  "metadata": {}
}

Episodes

Method Path Description
POST /episodes Create a new episode
GET /episodes/search?q=&limit= Full-text search across episodes
GET /episodes/:id Get episode by ID
GET /sessions/:id/episodes?limit=&offset= Get episodes for a session
DELETE /episodes/:id Delete an episode

POST /episodes body:

{
  "sessionId": 1,
  "userMessage": "Hello",
  "aiResponse": "Hi there!",
  "tokenCount": 10,
  "metadata": {}
}

Semantic (Qdrant) and entity REST endpoints will be documented as they are built out.