CORTEX Memory MCP
by alainrc2005
README.md
<div align="center">
# ๐ง CORTEX Memory MCP
**A production-grade, long-term semantic memory server for AI agents.**
Built on [Model Context Protocol](https://modelcontextprotocol.io), LangGraph.js, Qdrant, and local ONNX embeddings.
[](https://modelcontextprotocol.io)
[](https://www.typescriptlang.org/)
[](https://qdrant.tech/)
[](https://github.com/langchain-ai/langgraphjs)
[](LICENSE)
</div>
---
## โจ What is CORTEX?
CORTEX is a **persistent semantic memory layer** for LLM-based agents. Instead of losing context between conversations, CORTEX lets your AI assistant remember decisions, facts, patterns, and past sessions โ and recall them intelligently using **hybrid search** (dense + sparse BM25).
Think of it as a **knowledge base with a brain**: it stores decisions, facts and patterns permanently, surfaces the most relevant ones through intelligent ranking, consolidates duplicates, and uses a Knowledge Graph to understand how entities relate to each other.
> **Design principle**: In software development, memories don't expire with time. A decision made 6 months ago is still valid today. CORTEX never deletes memories due to age โ it only surfaces them by relevance. The only way a memory is invalidated is when it is explicitly contradicted by a newer one (via `consolidate`).
### Why CORTEX over naive RAG?
| Feature | Naive RAG | CORTEX |
|---|---|---|
| Search strategy | Dense-only | **Hybrid BM25 + dense + cross-encoder rerank** |
| Memory decay | None | **Bayesian + FSRS (spaced repetition)** |
| Deduplication | None | **LLM-powered consolidation** |
| Session continuity | None | **Episodic memory with event timeline** |
| Entity relationships | None | **Knowledge Graph (Kuzu Cypher)** |
| Operator profile | None | **Persistent preferences + patterns** |
| LLM dependency for embeddings | Required | **100% local ONNX (no GPU needed)** |
---
## ๐๏ธ Architecture
```mermaid
graph TB
subgraph MCP["MCP Interface (stdio)"]
TOOLS["22 Tools exposed\nvia MCP Protocol"]
end
subgraph CORTEX["CORTEX Core"]
direction TB
OBS["observe\nLangGraph workflow"]
RECALL["recall\nHybrid Search"]
CONS["consolidate\nLangGraph workflow"]
CTX["get_context_for\nRAG pipeline"]
end
subgraph EMBED["Embedding Layer (local, no GPU)"]
DENSE["all-MiniLM-L6-v2\n384d ยท ONNX"]
SPARSE["SPLADE_PP_en_v1\nSparse BM25-like ยท ONNX"]
end
subgraph STORE["Storage"]
QDRANT["Qdrant\nVector DB\nDense + Sparse indexes"]
KUZU["KuzuDB\nKnowledge Graph\nCypher queries"]
end
subgraph LLM["LLM (Ollama - optional)"]
QWEN["qwen3\nScoring ยท Tagging\nConsolidation ยท Rerank"]
end
MCP --> CORTEX
CORTEX --> EMBED
CORTEX --> LLM
EMBED --> STORE
CORTEX --> STORE
style MCP fill:#6C47FF,color:#fff
style CORTEX fill:#1C3C3C,color:#fff
style EMBED fill:#0F4C81,color:#fff
style STORE fill:#DC244C,color:#fff
style LLM fill:#2D2D2D,color:#fff
```
---
## ๐ Memory Layers
CORTEX organizes memory across **4 complementary layers**:
```mermaid
graph LR
subgraph L1["โก Layer 1 โ Temp Buffer"]
QB["quick_observe\nbatch_observe\nInstant, no LLM"]
end
subgraph L2["๐ง Layer 2 โ Semantic Memory"]
OB["observe\nFull pipeline:\nEmbed + Score + Tag + Link"]
end
subgraph L3["๐ผ Layer 3 โ Episodic Memory"]
EP["start_session\nlog_event\nrecall_sessions\nTimeline of work sessions"]
end
subgraph L4["๐ธ๏ธ Layer 4 โ Knowledge Graph"]
KG["Entities + Relations\ngraph_neighbors\ngraph_timeline\ngraph_query (Cypher)"]
end
QB --"auto-indexed\n(scheduler, ~60s)"--> OB
OB --> L3
OB --> L4
style L1 fill:#F59E0B,color:#000
style L2 fill:#6C47FF,color:#fff
style L3 fill:#0F4C81,color:#fff
style L4 fill:#065F46,color:#fff
```
---
## ๐ Recall Pipeline
When you call `recall`, CORTEX runs a multi-stage pipeline to surface the most relevant memories:
```mermaid
flowchart TD
Q["User Query"] --> E1["Dense Embedding\nall-MiniLM-L6-v2 ONNX"]
Q --> E2["Sparse Embedding\nSPLADE_PP_en ONNX"]
E1 --> PRE1["Qdrant Prefetch\nDense top-N\n(cosine similarity)"]
E2 --> PRE2["Qdrant Prefetch\nSparse top-N\n(BM25 IDF)"]
PRE1 --> RRF["Reciprocal Rank Fusion\nfusion: rrf"]
PRE2 --> RRF
RRF --> DECAY["Temporal Decay Scoring\nBayesian ร FSRS\nper engrama type"]
DECAY --> RERANK["Cross-Encoder Rerank\nqwen3 via Ollama\nbatch, single call"]
RERANK --> FINAL["Final Ranked Results\n40% semantic ยท 40% rerank\n20% decay"]
FINAL --> UPDATE["Update Access Stats\naccessCount ++ ยท lastAccessed"]
UPDATE --> RESULT["๐ Results returned\nto agent"]
style Q fill:#6C47FF,color:#fff
style RRF fill:#DC244C,color:#fff
style FINAL fill:#065F46,color:#fff
```
---
## ๐ Features
### Core Memory (`observe` / `recall`)
- **Full LangGraph workflow** on `observe`: score โ tag โ embed โ link โ upsert
- **Hybrid search**: dense (cosine) + sparse (BM25/IDF) fused via Reciprocal Rank Fusion
- **Cross-encoder reranking** with a local LLM (qwen3) in a single batch call
- **Cross-project search**: queries the project collection + global in one pass
### Retrieval Priority (not expiration)
> **CORTEX does not delete memories.** Decay is a **retrieval priority signal**, not a forgetting mechanism. A memory with a low decay score still exists โ it simply ranks lower if a more recent and frequently accessed memory is equally relevant. If the semantic match is strong enough, any memory surfaces regardless of age.
Two priority engines, chosen automatically by engrama type:
- **Bayesian** (`DECISION`, `FACT`, `ERROR`, `PATTERN`): the priority of technical memories is anchored to importance and access frequency. A high-importance decision holds its position for months without being accessed. It is only *invalidated* โ never deleted โ when `consolidate` detects a contradiction and marks it `status: superseded`.
- **FSRS-inspired** (`PREFERENCE`, `CONTEXT`): conversational context and preferences get a mild recency boost. Frequently revisited preferences gain stability. Lower-traffic ones rank below newer signals โ but are never removed.
### Engrama Types
| Type | Priority Engine | Semantic Weight | Use Case |
|---|---|---|---|
| `DECISION` | Bayesian | 55% | Architecture choices, design decisions |
| `FACT` | Bayesian | 65% | Technical facts, API docs, config |
| `ERROR` | Bayesian | 55% | Bugs found, anti-patterns |
| `PATTERN` | Bayesian | 50% | Recurring behaviors detected |
| `PREFERENCE` | FSRS-inspired | 75% | Operator style preferences |
| `CONTEXT` | FSRS-inspired | 75% | Conversational context |
### Episodic Memory (`start_session` / `log_event` / `recall_sessions`)
- Tracks **work sessions** with structured event timelines
- Event types: `DECISION`, `ERROR`, `SOLUTION`, `INSIGHT`, `CONTEXT_CHANGE`
- Sessions are **searchable by semantic similarity** (fastembed ONNX, no LLM)
- Auto-closes previous open sessions when a new one starts
- Injected into `get_context_for` as recent session summaries
### Knowledge Graph (`graph_*`)
- Built on **KuzuDB** โ an embedded graph database with Cypher support
- Auto-populated when `observe` extracts entities from memories
- Query neighbors, timelines, or run arbitrary Cypher queries
- Included in `get_context_for` context block automatically
### Two-Speed Ingestion + Automatic Indexing
```
Fast path (no LLM, no embedding, instant):
quick_observe โ temp_memories buffer
Full pipeline (embedding + LLM scoring):
observe โ project collection (direct)
Automatic upgrade (no manual step required):
Maintenance Scheduler โ runs autoIndexTemp() every cycle:
โข T+60s after server start: rescues orphaned buffer memories
from previous sessions (full drain, all projects)
โข Every 1 hour: indexes up to 10 pending memories per project
โข Manual call: index_temp is still available for on-demand indexing
Each batch: 1 ONNX call + 1 LLM scoring call + 1 Qdrant upsert
```
### Operator Profile
- Automatically updated by `detect_patterns`
- Stores: coding preferences, active projects, recurring behavioral patterns
- Injected at session start via `get_context_for`
---
## ๐ Getting Started
### Prerequisites
| Dependency | Version | Notes |
|---|---|---|
| Node.js | โฅ 18 | ESM support required |
| Qdrant | โฅ 1.9 | With sparse vector support |
| **LLM** *(choose one)* | โ | OpenRouter API key **or** local Ollama **or** `none` |
| TypeScript | 5.3 | Dev only |
### 1. Install Qdrant
```bash
docker run -p 6333:6333 -p 6334:6334 \
-v $(pwd)/qdrant_storage:/qdrant/storage \
qdrant/qdrant
```
### 2. Choose your LLM backend
CORTEX routes all LLM operations (scoring, tagging, reranking, consolidation) through a single router controlled by `CORTEX_LLM_BACKEND`. Three backends are supported:
#### Option A โ OpenRouter (cloud, recommended for CPU-only machines)
No local GPU required. Use any model available on [openrouter.ai](https://openrouter.ai) โ free `:free` models work out of the box.
```env
CORTEX_LLM_BACKEND=openrouter
OPENROUTER_API_KEY=sk-or-v1-...
OPENROUTER_MODEL=google/gemma-4-27b-it:free # any OpenRouter model slug
OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
```
#### Option B โ Ollama (local, GPU recommended)
Fully offline. `qwen3` or any model you have pulled locally.
```bash
# Install Ollama: https://ollama.com
ollama pull qwen3
```
```env
CORTEX_LLM_BACKEND=ollama
OLLAMA_URL=http://localhost:11434 # default if omitted
```
> โ ๏ธ On CPU-only machines Ollama can take 90โ130 s per LLM call. Use OpenRouter or `none` if latency matters.
#### Option C โ none (fastest, no LLM at all)
All LLM operations fall back to safe defaults: `importance: 5`, `type: FACT`, no tags. Embeddings and vector search still work normally.
```env
CORTEX_LLM_BACKEND=none
```
#### Auto-detection logic
If `CORTEX_LLM_BACKEND` is not set, CORTEX auto-detects:
1. If `OPENROUTER_API_KEY` is defined โ uses **openrouter**
2. Otherwise โ falls back to **ollama**
#### Reranker flag
```env
# true = activates LLM cross-encoder reranking after fastembed (better precision)
# false = fastembed ONNX only (<1s, sufficient for collections < 200 engramas)
CORTEX_RERANKER_ENABLED=true
```
---
### 3. Install CORTEX
```bash
git clone https://github.com/your-org/cortex-memory-mcp.git
cd cortex-memory-mcp
npm install
```
### 4. Configure `.env`
Minimal setup:
```env
# โโ Qdrant โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
QDRANT_URL=http://localhost:6333
QDRANT_API_KEY=your_api_key_here # omit if no auth
FASTEMBED_CACHE_DIR=./.fastembed_cache # ONNX model cache
# โโ LLM Backend โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# "openrouter" | "ollama" | "none"
# Auto-detected: openrouter if OPENROUTER_API_KEY is set, otherwise ollama
CORTEX_LLM_BACKEND=openrouter
# โโ OpenRouter (if CORTEX_LLM_BACKEND=openrouter) โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
OPENROUTER_API_KEY=sk-or-v1-...
OPENROUTER_MODEL=google/gemma-4-27b-it:free
OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
# โโ Ollama (if CORTEX_LLM_BACKEND=ollama) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# OLLAMA_URL=http://localhost:11434
# โโ Reranker โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
CORTEX_RERANKER_ENABLED=true
```
### 5. Build & Run
```bash
npm run build
npm start
```
On first startup, CORTEX will download the ONNX models (~130 MB total) and warm them up in the background. All subsequent calls are instant.
### 6. Add to your MCP client (Claude Desktop / Cursor / etc.)
```json
{
"mcpServers": {
"cortex-memory": {
"command": "node",
"args": ["/absolute/path/to/cortex-memory-mcp/dist/server.js"],
"env": {
"QDRANT_URL": "http://localhost:6333",
"QDRANT_API_KEY": "your_key"
}
}
}
}
```
---
## ๐ Tool Reference
### ๐ฃ Semantic Memory
#### `observe`
Save a fact, decision, or context to long-term memory. Runs the full LangGraph pipeline: score โ tag โ embed (dense + sparse) โ link to related memories โ persist.
```json
{
"projectName": "my-project",
"content": "Decided to use PostgreSQL over MongoDB for the user table due to relational integrity requirements"
}
```
**Response:**
```
โ
Engrama guardado.
โข ID: 550e8400-e29b-41d4-a716-446655440000
โข Importancia: 8/10
โข Tipo: DECISION
โข Tags: postgresql, database, architecture
Vinculado con 2 memorias relacionadas.
```
---
#### `recall`
Retrieve semantically relevant memories using hybrid search (BM25 + dense + cross-encoder reranking). Results are weighted by relevance and temporal decay.
```json
{
"projectName": "my-project",
"query": "which database did we choose for users?",
"limit": 5
}
```
**Response:**
```
๐ 3 memorias para "which database did we choose for users?" en my-project (hybrid BM25+dense, reranked):
1. [DECISION] [imp:8/10] โ
8.2 Decided to use PostgreSQL over MongoDB for the user table...
Tags: postgresql, database, architecture [2 vรญnculos]
2. [FACT] [imp:6/10] โ
6.1 PostgreSQL connection pool configured with max 20 connections
Tags: postgresql, config
3. [ERROR] [imp:7/10] โ
5.9 MongoDB ObjectId serialization caused issues with our REST API
Tags: mongodb, api, bug
```
---
#### `get_context_for`
**The main RAG entry point.** Call this at the start of every session to inject relevant memory, recent episodes, and Knowledge Graph context into the system prompt.
```json
{
"projectName": "my-project",
"message": "Let's continue working on the authentication module",
"maxItems": 6
}
```
**Response (inject into system prompt):**
```markdown
## ๐ง Memoria CORTEX โ Proyecto: my-project
*(6 engramas mรกs relevantes, reranked)*
โข [DECISION] We use JWT with 15-min access tokens + 7-day refresh tokens (imp:9/10)
โข [ERROR] Refresh token rotation failed when Redis went down โ added fallback to DB (imp:8/10)
โข [FACT] Auth middleware is in src/middleware/auth.ts (imp:6/10)
...
## ๐ผ Sesiones recientes
โข **12/06/2026** (47 min): Implementar refresh tokens JWT
โ Decidimos usar Redis para blacklist de tokens revocados
โ Soluciรณn: fallback a PostgreSQL si Redis no responde
> Usa este contexto como conocimiento previo. No lo repitas textualmente.
```
---
#### `consolidate`
Merge duplicate or contradictory memories using LLM-powered analysis. Superseded memories are marked and excluded from future searches.
```json
{ "projectName": "my-project" }
```
---
#### `detect_patterns`
Analyze recent memories to extract behavioral patterns, coding preferences, and recurring issues. Saves them as high-importance `PATTERN` engramas and updates the Operator Profile.
```json
{
"projectName": "my-project",
"limit": 50
}
```
**Response:**
```
๐ 5 patrones detectados en "my-project" โ guardados + Operator Profile actualizado:
1. Prefers TypeScript strict mode with explicit return types
2. Always adds error boundaries around external API calls
3. Uses environment variables for all configuration, never hardcodes
4. Writes tests before refactoring existing code
5. Recurrently encounters timezone issues with date handling
```
---
### ๐ก Fast Buffer (no LLM, no embedding)
#### `quick_observe`
Save a memory **instantly** โ no LLM, no embedding. Goes to a temporary buffer that is **automatically indexed by the maintenance scheduler** (within ~60s on startup, or the next hourly cycle). You never need to call `index_temp` manually unless you want immediate indexing.
```json
{
"projectName": "my-project",
"content": "Found that rate limiter needs to be per-IP not per-user"
}
```
---
#### `batch_observe`
Save multiple memories in one call. All go to the temporary buffer (no LLM, no embedding).
```json
{
"projectName": "my-project",
"memories": [
"Redis maxmemory set to 2gb with allkeys-lru policy",
"Nginx configured as reverse proxy on port 80/443",
"PM2 process manager used for Node.js clustering"
]
}
```
---
#### `list_pending`
Show what's waiting in the temporary buffer to be indexed.
```json
{ "projectName": "my-project" }
```
**Response:**
```
๐ฅ 3 memorias pendientes en temp_memories:
**my-project** (3 pendientes):
1. [13/06/2026, 08:15] Redis maxmemory set to 2gb with allkeys-lru policy
ID: abc-123...
2. [13/06/2026, 08:15] Nginx configured as reverse proxy on port 80/443
ID: abc-124...
```
---
#### `index_temp`
Process pending buffer memories with full embedding + LLM scoring. **Optional** โ the maintenance scheduler does this automatically every hour (and 60s after server start). Call manually when you need immediate indexing without waiting for the next scheduler cycle.
```json
{
"projectName": "my-project",
"batchSize": 10,
"skipScoring": false
}
```
**Response:**
```
๐ 3/3 memorias indexadas en "my-project" con scoring qwen3 (batch):
โข [FACT] imp:7/10 โ Redis maxmemory set to 2gb with allkeys-lru policy
โข [FACT] imp:6/10 โ Nginx configured as reverse proxy on port 80/443
โข [FACT] imp:5/10 โ PM2 process manager used for Node.js clustering
(โ
todo indexado)
```
---
#### `recall_hybrid`
Search **both** the temp buffer (keyword) and indexed memory (semantic) in one call. Best for finding something you saved recently.
```json
{
"projectName": "my-project",
"query": "redis configuration",
"limit": 5
}
```
---
### ๐ผ Episodic Memory
#### `start_session`
Open a new work session. Records the topic/goal and auto-closes any previously open session.
```json
{
"projectName": "my-project",
"context": "Implement OAuth2 Google login flow"
}
```
**Response:**
```
โถ๏ธ Sesiรณn episรณdica iniciada.
โน๏ธ Sesiรณn anterior cerrada (47 min, 8 eventos)
โข ID: 7f3a1c9d-...
โข Proyecto: my-project
โข Contexto: Implement OAuth2 Google login flow
```
---
#### `log_event`
Record a structured event in the active session.
```json
{
"projectName": "my-project",
"sessionId": "7f3a1c9d-...",
"eventType": "DECISION",
"description": "Using Passport.js instead of custom OAuth handler โ better maintained"
}
```
Event types:
- ๐ต `DECISION` โ Architecture or design choice made
- ๐ด `ERROR` โ Bug or problem encountered
- ๐ข `SOLUTION` โ How a problem was resolved
- ๐ก `INSIGHT` โ Important realization or learning
- ๐ `CONTEXT_CHANGE` โ Shift in focus or requirements
---
#### `recall_sessions`
Find past sessions relevant to a query using semantic search.
```json
{
"projectName": "my-project",
"query": "authentication JWT decisions",
"limit": 3
}
```
**Response:**
```
๐ผ 2 sesiones relevantes para "authentication JWT decisions" en my-project:
1. ๐ผ **11/06/2026** (47 min) โ Implementar refresh tokens JWT
๐ต Decidimos usar Redis para blacklist de tokens revocados
๐ข Fallback a PostgreSQL si Redis no responde
2. ๐ผ **09/06/2026** (31 min) โ Setup inicial del mรณdulo de auth
๐ต JWT elegido sobre session-based auth por arquitectura stateless
๐ด Token expiry de 1h causรณ problemas en mobile โ reducido a 15min
```
---
### ๐ธ๏ธ Knowledge Graph
#### `graph_neighbors`
Show entities directly connected to a node in the Knowledge Graph.
```json
{
"projectName": "my-project",
"entity": "PostgreSQL",
"limit": 10
}
```
**Response:**
```
๐ธ๏ธ Vecinos de "PostgreSQL" en my-project:
โข [USES] โ user_table โ primary storage for user accounts
โข [REPLACES] โ MongoDB โ chosen for relational integrity
โข [CONNECTS_TO] โ connection_pool โ max 20 connections
โข [REFERENCED_BY] โ auth_middleware
```
---
#### `graph_timeline`
View the temporal evolution of an entity โ what relationships were recorded and when.
```json
{
"projectName": "my-project",
"entity": "Redis",
"limit": 20
}
```
---
#### `graph_query`
Run a raw Cypher query against the Knowledge Graph (KuzuDB). For advanced users.
```json
{
"cypher": "MATCH (n:Entity)-[r]->(m:Entity) WHERE n.project = 'my-project' RETURN n.name, type(r), m.name LIMIT 20"
}
```
---
### ๐ง Management
#### `cortex_status`
Get a full system health overview.
```
## ๐ง CORTEX Status
*v3.0.0 โ LangGraph.js + Qdrant + fastembed*
**Colecciones activas**: 4
โข my-project: 147 engramas
โข social: 896 engramas
โข global: 12 engramas
โข pets: 4 engramas
**Buffer temporal (temp_memories)**: โ
vacรญo
**Operator Profile**: โ
(actualizado 13/06/2026)
```
---
#### `get_operator_profile`
Retrieve the persistent operator profile (coding preferences, active projects, detected patterns).
---
#### `get_all_memories`
List all memories for a project, ranked by decay score.
```json
{ "projectName": "my-project", "limit": 20 }
```
---
#### `update_memory`
Update an engrama's content. Recalculates embedding and LLM score.
```json
{
"id": "550e8400-...",
"content": "Decided to use PostgreSQL 16 with pgvector extension for embeddings storage"
}
```
---
#### `delete_memory`
Delete a single engrama by ID.
```json
{ "id": "550e8400-..." }
```
---
#### `delete_all_memories`
Delete all memories for a project. **Irreversible** โ requires explicit confirmation.
```json
{
"projectName": "my-project",
"confirm": true
}
```
---
#### `export_memories`
Export all memories as JSON for backup or migration.
```json
{ "projectName": "my-project" }
```
---
## ๐ก Recommended Session Workflow
Here is the recommended pattern for using CORTEX within an AI agent session:
```mermaid
sequenceDiagram
participant U as User
participant A as AI Agent
participant C as CORTEX
U->>A: New conversation starts
A->>C: get_context_for(project, first_message)
C-->>A: Relevant memories + recent sessions + KG context
A->>C: start_session(project, "Today's goal")
Note over A: Agent now has full context
loop During conversation
A->>C: quick_observe(fact) or observe(decision)
A->>C: log_event(sessionId, DECISION/ERROR/SOLUTION)
end
U->>A: Conversation ends
A->>C: detect_patterns(project) [optional, periodically]
Note over C: Session auto-closes on next start_session
```
---
## ๐งฎ Retrieval Priority Score Reference
> **Important distinction**: CORTEX's "decay" is not biological forgetting. It is a **dynamic retrieval priority score** (0โ1). A score of 0.1 does not mean the memory is gone โ it means it ranks lower than a score of 0.9 in the final result list. All memories are permanent unless explicitly superseded or manually deleted.
### Bayesian Priority (technical memories)
Used for `DECISION`, `FACT`, `ERROR`, `PATTERN`.
```
ฮฑ = accessCount + importance/2 โ grows with use and relevance
ฮฒ = max(0.1, daysSince ร (10 / importance)) โ time introduces uncertainty,
but importance resists it
score = ฮฑ / (ฮฑ + ฮฒ) โ E[ฮธ] of the Beta distribution
```
**Example:** A `DECISION` with `importance: 9` stays above 0.85 for ~60 days
without any access. A `FACT` with `importance: 3` drops faster โ but
still exists and will surface if semantically matched by a query.
### FSRS-inspired Priority (conversational memories)
Used for `PREFERENCE`, `CONTEXT`.
```
stability = log(1 + accessCount) ร (importance / 5)
priority_score = exp(โdaysSince / max(stability, 1))
```
Frequently revisited preferences gain stability and hold their rank.
Less-accessed context fades in ranking โ but remains retrievable.
### Combined Score (final ranking)
```
With LLM reranker: 0.40 ร semantic + 0.40 ร rerank + 0.20 ร priority
Without reranker: semantic_weight ร semantic + decay_weight ร priority
(weights vary by engrama type โ see table above)
```
---
## ๐ ๏ธ Migration: Adding Sparse Index to Existing Collections
If you have existing Qdrant collections without sparse vectors, use the included migration script:
```bash
# Dry run โ see what would be migrated
node migrate_sparse_index.mjs --dry-run
# Migrate all collections
node migrate_sparse_index.mjs
# Migrate a single collection
node migrate_sparse_index.mjs --only=cortex_hotetec
# Skip a collection
node migrate_sparse_index.mjs --skip=temp_memories
```
> โ ๏ธ The script recreates collections (required by Qdrant โ sparse vectors cannot be added to existing collections). It exports all points, deletes the collection, recreates it with `sparse_vectors.bm25` declared, then re-imports. Dense vectors are preserved; sparse vectors will be populated on the next upsert.
---
## ๐ฆ Tech Stack
| Component | Technology | Purpose |
|---|---|---|
| Protocol | MCP SDK 1.29 | Expose tools to AI agents |
| Workflow | LangGraph.js 0.0.25 | `observe` & `consolidate` pipelines |
| Vector DB | Qdrant 1.9+ | Dense + sparse storage & search |
| Graph DB | KuzuDB 0.11 | Knowledge Graph with Cypher |
| Dense Embedding | fastembed all-MiniLM-L6-v2 | 384d, ONNX, local, no GPU |
| Sparse Embedding | fastembed SPLADE_PP_en_v1 | BM25-like, ONNX, local, no GPU |
| LLM | OpenRouter / Ollama / none | Scoring, tagging, reranking, consolidation |
| Language | TypeScript 5.3 + ESM | |
---
## ๐ Project Structure
```
cortex-memory-mcp/
โโโ src/
โ โโโ server.ts # MCP server โ all 22 tool handlers
โ โโโ bootstrap.ts # Server initialization
โ โโโ services/
โ โ โโโ qdrant.ts # Vector DB client, search, sparse index
โ โ โโโ fastembed.ts # ONNX embedding (dense + sparse)
โ โ โโโ ollama.ts # LLM scoring, tagging, reranking
โ โ โโโ decay.ts # Bayesian + FSRS temporal decay
โ โ โโโ episode.ts # Episodic memory (sessions + events)
โ โ โโโ kuzu.ts # Knowledge Graph (KuzuDB + Cypher)
โ โโโ graph/
โ โ โโโ observe/ # LangGraph observe workflow
โ โ โโโ consolidate/ # LangGraph consolidation workflow
โ โโโ types/
โ โโโ engrama.ts # Engrama type definitions
โ โโโ episode.ts # Episode type definitions
โโโ migrate_sparse_index.mjs # Migration script for sparse indexes
โโโ test_cortex.mjs # Integration test suite
โโโ eval/
โ โโโ harness.mjs # Evaluation harness
โโโ .env # Configuration (not committed)
```
---
## ๐งช Running Tests
```bash
npm test
```
The test suite covers: `observe`, `recall`, `quick_observe`, `index_temp`, `recall_hybrid`, `get_context_for`, `consolidate`, `start_session`, `log_event`, `recall_sessions`, and `cortex_status`.
---
## ๐ค Contributing
Contributions are welcome! Please:
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'feat: add amazing feature'`)
4. Push and open a Pull Request
---
## ๐ License
MIT โ see [LICENSE](LICENSE) for details.
---
<div align="center">
Built with โค๏ธ for developers who want their AI to actually **remember**.
</div>
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues