Skip to main content
Glama
README.md
# ctrl-memory

**Lightweight, local-first MCP memory server for LLM agents.**

Store, search, and retrieve agent memories with zero external dependencies. Plug it into Hermes, Claude Code, Cursor, or any MCP-compatible client.

```bash
pip install ctrl-memory
ctrl-memory-mcp
```

---

## ✨ Features

- **Zero-dependency core** — pure Python, no databases, no external services
- **Two backends** — JSON files (MVP, zero deps) or SQLite (production, WAL mode)
- **Semantic search** — optional `sentence-transformers` for cosine similarity ranking
- **Hybrid retrieval** — keyword recall + vector re-ranking for best precision/recall
- **Fuzzy matching** — Damerau-Levenshtein typo tolerance built in
- **Scope filtering** — tag-based domain isolation
- **Supersession awareness** — automatically filters outdated/obsolete facts
- **Hermes plugin** — auto-prefetch context, auto-capture conversation turns
- **MCP stdio server** — plug into any MCP-compatible client
- **User isolation** — each user's memory is fully separated
- **Cross-session persistence** — memories survive between conversations

---

## 🚀 Quick start

### One-liner install (recommended)

```bash
curl -fsSL https://raw.githubusercontent.com/ctrlProgrammer/ctrl-memory-system/main/install.sh | bash
```

This creates an isolated virtual environment in `~/.local/share/ctrl-memory/`, installs ctrl-memory with semantic search, and makes the `ctrl-memory-mcp` command available globally. No `pip install --user` or `sudo` needed.

After install, open a new terminal (or `exec $SHELL`) and run:

```bash
ctrl-memory-mcp
```

### Manual install with pip

```bash
# Core (zero deps)
pip install ctrl-memory

# With semantic search
pip install "ctrl-memory[embeddings]"
```

> ⚠️ If your system restricts global pip installs (PEP 668), use the one-liner above or install inside a virtual environment:
> ```bash
> python3 -m venv .venv
> source .venv/bin/activate
> pip install "ctrl-memory[embeddings]"
> ```

### pipx (alternative)

```bash
pipx install "ctrl-memory[embeddings]"
```

### Run the MCP server

```bash
ctrl-memory-mcp
```

The server listens on **stdin/stdout** (MCP stdio transport). It starts when a client connects and exits when the client disconnects — no background daemon.

### Check if it's working

In one terminal, start the server:

```bash
ctrl-memory-mcp
```

In another terminal, run:

```bash
# 1. Initialize the session
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{}}}' | ctrl-memory-mcp

# 2. Add a fact
echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"add_memory","arguments":{"user_id":"alice","content":"Alice prefers Fastify over Express"}}}' | ctrl-memory-mcp

# 3. Search
echo '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"search_memory","arguments":{"user_id":"alice","query":"web framework"}}}' | ctrl-memory-mcp
```

> 💡 For Hermes users: the plugin activates **automatically** when you start a new conversation. No need to run `ctrl-memory-mcp` separately. Memory is stored per-user across sessions.

---

## 🔄 Updating

```bash
# If you used the one-liner installer — just re-run it (upgrades in-place):
curl -fsSL https://raw.githubusercontent.com/ctrlProgrammer/ctrl-memory-system/main/install.sh | bash

# If you used pip:
pip install --upgrade "ctrl-memory[embeddings]"

# If you used pipx:
pipx upgrade ctrl-memory
```

---

## 📦 Backends

### JSON backend (default)

- Zero dependencies
- One file per user: `~/.ctrl-memory/<user_id>.json`
- Auto-increment IDs, append-only writes
- Best for: MVP, personal use, <1000 facts

### SQLite backend

- Single `.db` file with WAL mode
- Indexed queries, ACID transactions
- Embedding storage for semantic search
- Best for: production, multi-user, >1000 facts

```bash
ctrl-memory-mcp --backend sqlite
```

---

## 🧠 Semantic search

When `sentence-transformers` is installed, ctrl-memory automatically enables:

- **Auto-embedding** — facts are vectorized at write time (384-dim all-MiniLM-L6-v2)
- **Hybrid search** — keyword candidates → cosine similarity re-ranking → sorted by relevance
- **Score filtering** — configurable `min_score` threshold to filter weak matches

```bash
pip install "ctrl-memory[embeddings]"
```

No flags needed — detection is automatic.

---

## 🔌 Hermes Agent plugin

ctrl-memory ships with a native Hermes Agent provider.

### Install

```bash
# Copy the plugin
cp -r hermes_provider ~/.hermes/hermes-agent/plugins/memory/ctrl-memory/
```

### Configure

Add to `~/.hermes/config.yaml`:

```yaml
memory:
  provider: ctrl-memory
  config:
    backend: sqlite       # or json (default)
    db_path: ~/.hermes/memory.db
```

The plugin provides:
- **Automatic prefetch** — relevant context injected before every LLM turn
- **Turn capture** — facts extracted from conversations and stored
- **4 tools** — `add_memory`, `search_memory`, `delete_memory`, `memory_status`

---

## 🔧 MCP tools

| Tool | Description |
|---|---|
| `add_memory` | Store a new fact with optional metadata tags |
| `search_memory` | Hybrid keyword + semantic search |
| `get_fact` | Retrieve a specific fact by ID |
| `list_facts` | List all facts for a user with pagination |
| `update_fact` | Edit an existing fact (re-embeds if semantic enabled) |
| `delete_fact` | Remove a fact (cleans up embedding) |
| `count_facts` | Get total fact count for a user |
| `search_memory_semantic` | Pure cosine similarity search (uses embeddings) |

---

## 🏗️ Architecture

```
┌─────────────────────────────────────┐
│          MCP Client                  │
│  (Hermes, Claude Code, Cursor...)   │
└──────────────┬──────────────────────┘
               │ stdio JSON-RPC
┌──────────────▼──────────────────────┐
│        mcp_server.py                │
│     MCP stdio transport layer       │
└──────────────┬──────────────────────┘
               │
┌──────────────▼──────────────────────┐
│       memory_backend.py             │
│  ┌─────────┐  ┌──────────┐         │
│  │ JSON    │  │ SQLite   │         │
│  │ Store   │  │ Store    │         │
│  └─────────┘  └────┬─────┘         │
│                    │                │
│  ┌─────────────────▼──────────┐     │
│  │    EmbeddingEngine          │     │
│  │  (optional, auto-detect)   │     │
│  └────────────────────────────┘     │
└─────────────────────────────────────┘
```

### Search flow

```
Query
  │
  ▼
1. Keyword search (token-OR with stop-word filter)
  │
  ├── No results? → Fuzzy fallback (Damerau-Levenshtein)
  │
  ▼
2. Cosine similarity re-ranking (if embeddings available)
  │
  ▼
3. Scope filtering (if tags match)
  │
  ▼
4. Supersession filtering (remove obsolete facts)
  │
  ▼
5. Sort by score, return top N
```

---

## 📊 Benchmark results

Tested against **[PrecisionMemBench](https://github.com/tenurehq/precisionMemBench)** — 77 retrieval scenarios across alias resolution, fuzzy matching, scope isolation, noise resistance, supersession chains, and budget constraints.

### Score: **54 / 77** ✅ (70% passing)

| Phase | Passing | Δ |
|---|---|---|
| Base keyword search | 9 / 77 | — |
| Hybrid keyword + cosine | **42 / 77** | +33 |
| + Damerau-Levenshtein fuzzy | **43 / 77** | +1 |
| + Scope filtering | **49 / 77** | +6 |
| + Supersession filtering | **54 / 77** | +5 |

### What passes (54 tests)

| Category | Tests |
|---|---|
| **Alias resolution** | k8s → Kubernetes, GHA → GitHub Actions, ReactJS → React, POV → point of view, DLQ → dead letter queue, base class → composition-inheritance, exceptions → error handling, 2-word shingles |
| **Exact match** | repository-layer, canonical name, long query (400+ chars) |
| **Scope filtering** | Redis-in-writing returns character not datastore, code-scope doesn't leak writing, cross-scope blocked, user-edited respects scope, scope bleed protection |
| **Supersession** | SQLAlchemy superseded by MongoDB hidden, TSLint→ESLint→Biome chain resolves to terminal, resolved_at beliefs excluded, pinned+resolved excluded from questions |
| **Fuzzy matching** | All-caps case-insensitive (REACTJS), typo prefix guard, scope-aware fuzzy filtering |
| **Messy queries** | Filler-heavy extraction, compound surfaces 2 beliefs, negation still surfaces topic, all-caps case insensitive |
| **Budget/limits** | Ceiling eviction, zero graceful, one pinned wins, recency tiebreak |
| **Edge cases** | Cold start, empty query, whitespace query, short query passthrough, short query score clears, empty alias content path |
| **User isolation** | Other-user beliefs never leak |
| **Universal scope** | Persona prelude, explicit query with no relevant, zero-reinforcement fresh belief surfaces |

### What fails (23 tests) — root causes

| Cause | Tests | Why |
|---|---|---|
| **Token bleeding** | 8 | Generic query tokens match too many facts (e.g. "kube" finds multiple) |
| **Relation expansion** | 4 | "auth depends on redis" — not yet implemented |
| **Cap stress** | 3 | 6+ entities in single query needs NLP extraction |
| **Ranking weights** | 2 | canonical_name should outrank content match |
| **Multi-scope Redis** | 2 | Redis in both code+writing scopes needs per-scope dedup |
| **Fuzzy edge cases** | 2 | k9s vs k8s prefix guard, single-edit with token bleed |
| **Other** | 2 | why_it_matters not indexed, type isolation routing |

### Comparison with other providers

| Provider | Passing | Precision | Dependencies |
|---|---|---|---|
| **tenure** (reference) | **77 / 77** | 1.00 | MongoDB Atlas Search (BM25, shingles, fuzzy) |
| **ctrl-memory** | **54 / 77** | **~0.70** | **Zero external deps** |
| okf | ~30 / 77 | ~0.47 | PostgreSQL |
| supermemory | ~21 / 77 | ~0.22 | Supabase + API |
| yourmemory | ~21 / 77 | ~0.17 | MongoDB |
| mem0 | ~9 / 77 | ~0.06 | Qdrant + API |

**Second place** among all tested providers. ctrl-memory achieves this with **zero external dependencies** — no databases, no APIs, no cloud services.

---

## 🧪 Development

```bash
# Clone
git clone https://github.com/ctrl-alt-dev/ctrl-memory
cd ctrl-memory

# Setup
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[embeddings]"

# Test
python3 -m unittest discover tests -v

# Run benchmark
cd /tmp
git clone https://github.com/tenurehq/precisionMemBench
cd precisionMemBench
MEMORY_PROVIDER=ctrl-memory CTRL_MEMORY_URL=http://localhost:8000 \
  RESEED=true npx ava src/retrieval.external.eval.test.ts --timeout 10m
```

### Test suite

| File | Tests | What it covers |
|---|---|---|
| `test_memory_backend.py` | 23 | JSON store CRUD, search, user isolation |
| `test_sqlite_store.py` | 25 | SQLite store CRUD, search, embeddings |
| `test_mcp_server.py` | 24 | MCP protocol, JSON-RPC, tool dispatch |
| `test_embeddings.py` | 24 | Embedding engine, cosine similarity |
| `test_hermes_provider.py` | 24 | Plugin lifecycle, tools, prefetch |

**116 tests total** (105 run, 11 skip without sentence-transformers).

---

## 📄 License

MIT