# 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 ### FTS5 Full-Text Search 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:** ```json { "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:** ```json { "sessionId": 1, "userMessage": "Hello", "aiResponse": "Hi there!", "tokenCount": 10, "metadata": {} } ``` > Semantic (Qdrant) and entity REST endpoints will be documented as they are built out.