Content
<p align="center">
<img src="assets/ferris_is_big.png" alt="Ferris — matte gold crab on white sand under a galaxy sky" width="720"/>
</p>
<h1 align="center">mnemex</h1>
<p align="center">
<strong>Personal AI memory layer — vector store + session consolidation + MCP server wired to GitHub Copilot</strong>
</p>
<p align="center">
<img alt="Rust" src="https://img.shields.io/badge/Rust-1.96+-f74c00?style=flat-square&logo=rust&logoColor=white"/>
<img alt="MCP" src="https://img.shields.io/badge/MCP-rmcp%202.1-6366f1?style=flat-square"/>
<img alt="Embeddings" src="https://img.shields.io/badge/embeddings-nomic--embed--text--v1.5-10b981?style=flat-square"/>
<img alt="Local" src="https://img.shields.io/badge/100%25%20local-no%20API%20key-0ea5e9?style=flat-square"/>
<img alt="License" src="https://img.shields.io/badge/license-MIT-gray?style=flat-square"/>
</p>
---
## Why
Every conversation with an AI assistant starts from zero.
Context, decisions, patterns, code — all of it evaporates when the session ends.
Mnemex fixes that. It gives GitHub Copilot a **persistent, semantic memory** that survives across sessions, workspaces, and time. When I start a new conversation, Copilot can recall what we built together last week — architecture decisions, lessons learned, code patterns, open questions.
No cloud. No API keys. No dependency on someone else's deprecation schedule. Everything runs locally.
---
## Architecture
```
mnemex/
├── crates/
│ ├── mnemex-core/ # domain logic — embed + store + consolidate
│ │ └── src/
│ │ ├── error.rs # MnemexError (thiserror, no anyhow)
│ │ ├── embedder.rs # fastembed wrapper + canary hash lock
│ │ ├── store.rs # flat JSON store + cosine similarity search
│ │ └── consolidate.rs# greedy near-duplicate removal
│ └── mnemex-mcp/ # MCP server (rmcp, stdio)
│ └── src/main.rs # tools + SIGTERM consolidation hook
└── .vscode/mcp.json # VS Code wiring
```
## MCP Tools
| Tool | What it does |
|------|-------------|
| `remember(text, tags?)` | Embed and persist a memory |
| `recall(query, top_k?)` | Semantic search over all memories |
| `forget(id)` | Delete a memory by ID |
| `list_memories(limit?)` | List most recent memories |
| `consolidate(session_id?, dedup_threshold?)` | Deduplicate a session (auto on shutdown) |
| `session_id()` | Get the current session UUID |
---
## Model consistency guarantee
The embedder (`nomic-ai/nomic-embed-text-v1.5` via fastembed) is locked by a SHA-256 canary hash stored in `~/.local/share/mnemex/model_lock.json`.
If the fastembed crate version ever silently changes the model weights, the server refuses to start. This ensures insert, query, and consolidation always use the exact same embedding space.
## Session consolidation
When VS Code closes → **SIGTERM** → the server runs `consolidate_session` for the current session (greedy cosine dedup at threshold 0.92) before exiting.
Manual trigger:
```bash
# Via MCP tool
consolidate(dedup_threshold=0.92)
# Or directly
kill -SIGTERM $(pgrep mnemex-mcp)
```
---
## Setup
**System deps (Fedora/RHEL):**
```bash
sudo dnf install -y gcc gcc-c++ openssl-devel
```
**Build:**
```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
cargo build --release
```
The first run downloads the ONNX model (~522 MB) to `~/.cache/fastembed/`.
**VS Code:** the `.vscode/mcp.json` is already configured. Reload the window and the `mnemex` MCP server appears in Copilot's tool list.
---
## Data
```
~/.local/share/mnemex/
├── memories.json # flat vector store (atomic writes)
└── model_lock.json # canary hash — DO NOT edit manually
```
---
## Stack
- **[rmcp](https://crates.io/crates/rmcp)** — official Rust MCP SDK (stdio transport)
- **[fastembed](https://crates.io/crates/fastembed)** — local ONNX embeddings, nomic-embed-text-v1.5
- **[thiserror](https://crates.io/crates/thiserror)** — explicit typed errors, no anyhow
- **tokio** — async runtime
- Cosine similarity search — O(n), < 1ms at 10k entries