Metadata-Version: 2.5
Name: rekall-mcp
Version: 1.15.0
Summary: Persistent memory for AI assistants via MCP
Author: jfr992
License: Apache-2.0
License-File: LICENSE
Keywords: ai,claude,context,llm,mcp,memory
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.11
Requires-Dist: click>=8.0.0
Requires-Dist: fastembed<0.9,>=0.8
Requires-Dist: httpx>=0.27.0
Requires-Dist: mcp<2,>=1.0.0
Requires-Dist: networkx>=3.0.0
Requires-Dist: pydantic-settings>=2.0.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: pyyaml>=6.0.0
Requires-Dist: qdrant-client<1.18,>=1.12
Provides-Extra: dev
Requires-Dist: mypy>=1.10.0; extra == 'dev'
Requires-Dist: pre-commit>=3.7.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
Requires-Dist: pytest-benchmark>=4.0.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: ruff>=0.4.0; extra == 'dev'
Requires-Dist: tiktoken>=0.7; extra == 'dev'
Provides-Extra: gemini
Requires-Dist: google-generativeai>=0.3.0; extra == 'gemini'
Provides-Extra: ollama
Requires-Dist: ollama>=0.1.0; extra == 'ollama'
Provides-Extra: torch
Requires-Dist: sentence-transformers>=3.0.0; extra == 'torch'
Description-Content-Type: text/markdown

# Rekall MCP

**Give coding agents memory with associative recall.** Rekall works with Claude Code and Codex while keeping each harness’s native memory separate.

Rekall MCP is a persistent memory system with a **knowledge graph** layer. It stores memories as YAML + vector embeddings, connects them with typed relationships, and retrieves context using graph-enhanced semantic search.

![A real Claude Code session with Rekall: memory recall answers an incident question, then a destructive terraform command gets refused by quoting a danger-zone memory (recorded live, synthetic demo memories)](docs/demo/rekall-cc-real.gif)

![Rekall cockpit — aggregates dashboard with live recall feed and neural graph](docs/screenshots/cockpit.png)

---

## Local-First Agent Nervous System

Rekall gives local agents durable, inspectable, cross-session and cross-project memory for software work. Harness memory stores assistant preferences; Rekall stores what the work has taught the agent: decisions, root causes, procedures, danger zones, and project familiarity with provenance.

---

## Install

### Try it — no Docker, one command

```bash
claude mcp add rekall -- uvx rekall-mcp
```

That's the trial tier: stdio transport, embedded vector store at `~/.rekall/qdrant`, memories as YAML at `~/.claude/memory`. First run downloads the ~90 MB embedding model (progress on stderr). No hooks/auto-capture, single session at a time — upgrade below when it earns a daily slot.

### Daily driver — all-in-one Docker

```bash
docker run -d -v rekall-data:/data -p 127.0.0.1:8000:8000 ghcr.io/jfr992/rekall-mcp
claude mcp add --transport http rekall http://localhost:8000
```

One container, embedding model baked in, data on a named volume. Verify with `curl http://localhost:8000/health`.

### Full stack — compose (adds the cockpit UI)

```bash
git clone https://github.com/jfr992/rekall-mcp.git
cd rekall-mcp
docker compose up -d    # Qdrant (:6333) + MCP backend (:8000) + cockpit (:3333)
claude mcp add --transport http rekall http://localhost:8000
```

Data lives on named volumes (`rekall-memory`, `rekall-qdrant`). Existing installs with data at `~/.claude/` keep their bind mounts via `docker-compose.bind-mounts.example.yaml` — see [docs/MIGRATION.md](docs/MIGRATION.md). (`scripts/start-rekall.sh` remains for running the backend/UI on the host during development.)

> **Need Docker?** Get it free at [docker.com/get-started](https://www.docker.com/get-started/)

### Which tier?

| Tier | Install | Transport | Hooks / auto-capture | Embedder | Storage |
|---|---|---|---|---|---|
| Trial | `uvx rekall-mcp` | stdio | no | fastembed | embedded `~/.rekall/qdrant` + YAML `~/.claude/memory` |
| Daily (pip) | `uv tool install rekall-mcp && rekall serve` | HTTP loopback | yes | fastembed | same as trial |
| Daily (docker) | `docker run -v rekall-data:/data -p 127.0.0.1:8000:8000 ghcr.io/jfr992/rekall-mcp` | HTTP loopback | yes | fastembed (baked into the image) | named volume |
| Full stack | `docker compose up -d` | HTTP + cockpit | yes | per compose | external Qdrant container |

Trial-tier honesty: no hooks means nothing is captured automatically — you save and recall explicitly. Filtering is linear at embedded scale, and only one process can hold the embedded store (run `rekall serve` so sessions share one daemon). Shared-env `pip install` is unsupported; use isolated installs (`uvx` / `uv tool install`).

### Wire Claude Code (hooks + config)

The MCP server alone gives Claude memory *tools*; the hooks make memory *automatic*. From a repo checkout:

```bash
bash claude/setup/install.sh
```

Idempotent, backs up `~/.claude/settings.json` first. It wires five hooks and nine slash commands:

| Hook | Event | What it does | Kill switch |
|---|---|---|---|
| `rekall-restore.sh` | UserPromptSubmit | once-per-session status line, no injection | `REKALL_AUTOSAVE=0` |
| `rekall-observe.sh` | Stop | gated, safe-mode Haiku judge auto-saves durable observations | `REKALL_AUTOSAVE=0` |
| `rekall-session-end.sh` | SessionEnd | bounded, content-free recall-utility summary (feeds reinforcement) | `REKALL_AUTOSAVE=0` |
| `rekall-reflex.sh` | PreToolUse (Bash) | surfaces relevant memories before risky commands | `REKALL_REFLEX=0` |
| `memory-prune.sh` | SessionStart | daily gated prune housekeeping | `REKALL_AUTOSAVE=0` |

Optional sixth (manual, injects a thin project capsule at session start): `cp claude/hooks/session-start-memory.sh ~/.claude/hooks/` + a SessionStart entry — see [`claude/INSTALL.md`](claude/INSTALL.md).

Using profiles (`CLAUDE_CONFIG_DIR`)? The installer targets `~/.claude`; repeat the settings entries in each profile's `settings.json` (hook files can be shared by absolute path).

Recommended agent policy for CLAUDE.md (when to recall, what to save): copy the block from [`docs/CLAUDE_MEMORY_SETTINGS.md`](docs/CLAUDE_MEMORY_SETTINGS.md).

**Done.** Claude now remembers things between sessions — and recalls them before risky commands.

### Wire Codex (hooks + MCP)

From a repository checkout, install the first-class Codex adapter:

```bash
bash codex/setup/install.sh
```

Or register the loopback MCP manually:

```bash
codex mcp add rekall --url http://localhost:8000
```

The installer is idempotent, failure-atomic, backs up replaced configuration, preserves foreign hooks, and installs bounded `SessionStart`, `PreToolUse`, `PreCompact`, `PostCompact`, `PostToolUse`, and `SessionEnd` handlers. See [`codex/INSTALL.md`](codex/INSTALL.md) for separate MCP/REST URLs, conflict handling, kill switches, uninstall, and rollback. Codex native memory at `~/.codex/memories/` is separate; Rekall never edits it. Restart Codex after installation.

---

## How to Use

Just talk normally. Claude automatically remembers:

- **Decisions** - "Let's use PostgreSQL"
- **Preferences** - "I prefer TypeScript"
- **Lessons** - "That bug was caused by..."

To check memories: *"What do you remember about this project?"*

### Python API

```python
from memory import MemoryManager

memory = MemoryManager()

# Save (auto-links to related memories in the knowledge graph)
memory.save("Chose PostgreSQL for JSON support", type="decision", project="my-app")
memory.save("User prefers concise responses", type="preference")

# Recall (graph-enhanced: vector search + relationship traversal)
results = memory.recall("what database did we choose?")
for r in results:
    print(f"[{r['score']:.2f}] {r['content']}")

# Project context (flat or hierarchical)
context = memory.get_project_context("my-app")
```

### CLI

```bash
# Save
python -m memory.cli save "Decided to use PostgreSQL" --type decision --project my-app

# Recall
python -m memory.cli recall "database choices"
python -m memory.cli recall "recent work" --limit 3 --days 7

# Stats
python -m memory.cli stats
```

#### Operations

| Verb | What it does |
|------|-------------|
| `rekall doctor [--project P] [--json]` | Health check — exit 0 healthy, 1 degraded, 3 unreachable |
| `rekall backup [--out DIR]` | Tarball memory + Qdrant; streams artifact paths |
| `rekall migrate [--dry-run] [--no-backup]` | Migrate to hybrid schema; backs up first by default |
| `rekall startup-preview [--project P]` | Preview what the SessionStart hook would inject (approximates hook output; exit 3 if backend unreachable) |
| `rekall install-claude [--skills-only] [--hooks-only] [--skip-backend]` | Install Claude Code bundle from a repo checkout |

Software evals: `uv run --extra dev pytest tests/test_software_evals.py`
Utility report: `uv run python scripts/utility_report.py`
Conflict-edge repair: `QDRANT_URL=... uv run python scripts/repair_contradicts.py` — re-judges unrefined `contradicts` edges, dry-run by default (see [docs/TUNING.md](docs/TUNING.md))

---

## Knowledge Graph

Every memory is a node. Relationships are typed edges created automatically on save:

| Relation | Meaning | Example |
|----------|---------|---------|
| `related_to` | Semantically similar | Two PostgreSQL facts |
| `led_to` | Temporal causation | Decision led to a learning |
| `depends_on` | Structural dependency | Decision depends on requirement |
| `supersedes` | Newer replaces older | Updated decision overwrites old |
| `contradicts` | Opposing content | Conflicting memories |

### Graph-Enhanced Recall

Recall uses a 3-phase pipeline instead of flat cosine search:

```
1. SEED    - Vector search (top K x 2 candidates)
2. EXPAND  - Traverse 1-hop graph neighbors of seed results
3. RANK    - Composite: vector(40%) + importance(20%) + proximity(15%) + tier(15%) + recency(10%)
```

This finds memories that are *structurally related*, not just textually similar. Falls back to pure vector search when the graph is empty.

### Freshness — conflict detection at read time

When the same memory type appears in the result set, Rekall detects conflicting entries via graph edges and stored-vector cosine (θ ≥ 0.9). The `recall_formatted` output renders entries newest-first; outdated entries are collapsed to a stub line so the agent acts on current information only. No data is deleted — the detection is ephemeral and happens entirely at read time.

### Cockpit UI

Browse the knowledge graph at `http://localhost:3333/brain` — the Next.js cockpit ships as a container, started by `docker compose up -d` alongside Qdrant and the backend. (For UI development, `cd ui && npm run dev -- -p 3333` still works.) Surfaces:

![Recall workbench — semantic search with type facets](docs/screenshots/kb-recall.png)
![Stream — day-grouped activity timeline with the consolidation ladder](docs/screenshots/stream.png)
![Hygiene — conflict queue, prune dry-run, and lifecycle backfill](docs/screenshots/hygiene.png)

- `/brain` — force-directed graph view, nodes are memories, edges show typed relationships
- `/kb` — typed columns (decisions, requirements, preferences, learnings), plus an **Export OKF** tab that distills memory into a portable [Open Knowledge Format](https://github.com/GoogleCloudPlatform/knowledge-catalog/tree/main/okf) bundle
- `/continuity` — resume packets and handoff summaries
- `/hygiene` — pressure metrics, prune flow, lifecycle backfill

---

## Claude Code bundle (optional)

The three-container stack above gives Claude memory via MCP tools. The `claude/` bundle adds the Claude Code integration layer — auto-save hooks, slash commands, and a recommended memory policy. All of it is opt-in; nothing auto-loads.

### One-shot install

```bash
bash claude/setup/install.sh
```

Idempotent, backs up your existing `~/.claude/settings.json` first. It:

- copies five hooks to `~/.claude/hooks/` (`rekall-restore`, `rekall-observe`, `rekall-session-end`, `rekall-reflex`, `memory-prune`)
- merges `UserPromptSubmit`, `Stop`, `SessionEnd`, `SessionStart`, and `PreToolUse` (Bash matcher) entries into `~/.claude/settings.json` (deduped; repairs a wrong/missing reflex matcher and removes only obsolete Rekall-owned lifecycle entries)
- copies all nine slash commands to `~/.claude/skills/`
- verifies backend health

**Restart your Claude Code session** afterward so the slash commands load. Re-run anytime from inside Claude Code via `/rekall-setup`. Full manual steps and flags (`--skills-only`, `--hooks-only`, `--skip-backend`) are in [`claude/INSTALL.md`](claude/INSTALL.md).

### Hooks (the auto-save layer)

- **`rekall-restore.sh`** (UserPromptSubmit) — once-per-session status line (`Rekall ready — N memories…`). No context injection.
- **`rekall-observe.sh`** (Stop) — a Haiku judge that auto-saves durable observations, gated by cheap signal detection (durability keywords, new git commits, or session length) so it doesn't fire on every turn. The nested judge runs in safe mode at low effort with no tools or session persistence. Kill switch: `REKALL_AUTOSAVE=0`.
- **`rekall-session-end.sh`** (SessionEnd) — reads a bounded transcript tail (default 1 MiB), correlates recalled IDs with later edits/test passes, and posts one IDs-and-counts-only event. No transcript content is sent.
- **`rekall-reflex.sh`** (PreToolUse, Bash) — surfaces relevant memories before risky commands (destructive ops, IaC, memory-data, hooks, helm). A local word-boundary cue match gates the fetch (no network on a miss), debounced once per session per cue even on a zero-hit response. On a match it does a bounded `curl` (0.1s connect / 1s total) to `/api/memory/reflex` and injects a capped, untrusted-framed packet as `additionalContext`. It never blocks the tool call — every failure path exits 0. `REKALL_API_URL` takes precedence over the legacy `REKALL_URL`. Kill switches: `REKALL_AUTOSAVE=0` (master) or `REKALL_REFLEX=0` (dedicated).
- **`memory-prune.sh`** (SessionStart) — runs gated superseded-memory housekeeping at most once per day.

### Slash commands (manual, not auto-triggering)

| Slash command | What it does |
|---------------|--------------|
| `/memory-observe <note>` | Manual save with auto-classification |
| `/memory-recall <query>` | Graph-enhanced semantic search |
| `/memory-restore` | Manual context restore (importance-ranked) |
| `/memory-stats` | Health check + graph metrics |
| `/memory-rebuild` | Rebuild the knowledge graph |
| `/memory-consolidate` | Detect duplicate and contradictory memories |
| `/memory-skills` | Show extracted skills from memory clusters |
| `/rekall-publish` | Export memory to an OKF knowledge bundle |
| `/rekall-setup` | Re-run the bundle installer from inside Claude Code |

### Recommended CLAUDE.md policy

For the agent to use memory well — recall at session start, save conservatively — copy the policy block from [`docs/CLAUDE_MEMORY_SETTINGS.md`](docs/CLAUDE_MEMORY_SETTINGS.md) into your `~/.claude/CLAUDE.md` (global) or a project `CLAUDE.md`. It tells Claude when to call `get_cached_context()`, what's worth an `observe()`, and how to tune recall.

---

## Your Data

Everything stays on your computer in editable files:

```
~/.claude/memory/
  <project>/
    2026-02-02.yaml     <- Human-editable memories (nested per project)
  _graph.json           <- Knowledge graph (auto-managed)
```

Nothing is sent anywhere. Backup = copy the folder.

Credentials are automatically sanitized before storage:

```
Input:  "Set api_key to sk-abc123def456"
Stored: "Set api_key to [REDACTED]"
```

### Securing a non-localhost deployment

The server binds `127.0.0.1` by default and is **unauthenticated** — the trust
model is localhost. Docker sets `HOST=0.0.0.0` inside the container (required for
port-mapping); compose maps ports to localhost only. If you deliberately expose
the server on a network (`HOST=0.0.0.0` on bare metal), enable bearer auth:

```bash
export REKALL_API_TOKEN=$(openssl rand -hex 32)   # on the server
```

When set, every request except `/health` requires the token. Point clients at it:

```bash
# Claude Code
claude mcp add --transport http rekall http://localhost:8000 \
  --header "Authorization: Bearer $REKALL_API_TOKEN"
# Codex (the installer also wires authenticated REST hooks)
bash codex/setup/install.sh --bearer-token-env-var REKALL_API_TOKEN
# Cockpit: ui/.env.local
echo "NEXT_PUBLIC_REKALL_API_TOKEN=$REKALL_API_TOKEN" >> ui/.env.local
```

---

## Benchmark

Tested on [LongMemEval](https://github.com/xiaowu0162/LongMemEval) (500 questions, 6 question types). Reproducible — runner in [`benchmarks/`](benchmarks/).

End-to-end effectiveness numbers — accuracy, token cost, and the workloads Rekall loses on — live in [`BENCHMARKS.md`](BENCHMARKS.md), with committed raw evidence.

These are **R@5 retrieval-recall** numbers — "was the correct memory in the top 5 retrieved" — with no LLM at any stage. They are **not** end-to-end QA-accuracy and are **not** comparable to the QA-accuracy figures other systems (mem0, Zep) publish on LongMemEval. MemPalace's raw retrieval baseline (96.6% R@5) uses the same metric and is the closest comparison point.

Measured 2026-07-02 on v1.7.0 (`main`, 5-weight recall ranking). Hybrid (BM25 + dense) has been the product's default recall path since 2026-07-17 — the "Hybrid" rows below now describe what recall actually runs. The BM25 vocab is maintained via `POST /api/memory/resparse` (see [docs/TUNING.md](docs/TUNING.md)); drift is surfaced in the doctor's `bm25` block.

| Mode | R@5 | R@10 |
|------|-----|------|
| Dense (semantic only) | 91.7% | 96.2% |
| Hybrid (BM25 + dense) | 93.6% | 97.4% |
| Hybrid + graph | 93.6% | 97.4% |

Hybrid search catches entity-specific queries (ticket IDs, error codes) that pure semantic search misses. No LLM required, no API calls, runs entirely local. R@5 measures retrieval, not answer quality — a system can retrieve well and still answer poorly.

```bash
# Reproduce (runs against the isolated test Qdrant on :6334 — production data untouched)
bash benchmarks/download_data.sh
docker compose --profile test up -d qdrant-test
PYTHONPATH=src:. uv run python -m benchmarks.longmemeval_runner \
    benchmarks/data/longmemeval_s_cleaned.json --mode all
```

---

## How Search Works

Memories are converted to **embeddings** (vectors that capture meaning) for semantic search:

```
"Use PostgreSQL" -> [0.12, 0.45, 0.78, ...]  <- Numbers that represent meaning
```

When you ask "what database?", Claude searches by meaning, not keywords. The knowledge graph then expands results by following relationship edges to find structurally related memories.

**Embedding options** (see [docs/SETUP.md](docs/SETUP.md)):
| Provider | Runs on | Cost | Quality |
|----------|---------|------|---------|
| `sentence-transformers` | Your computer | Free | Good (default) |
| `ollama` | Your computer | Free | Better |
| `gemini` | Google Cloud | Free tier | Best |

---

## Troubleshooting

**"Connection refused"** - Make sure Docker is running: `docker compose ps`

**"Cockpit UI not loading"** - Confirm all three containers are up:
```bash
docker compose ps            # qdrant, mcp, ui should all be running
docker compose up -d ui      # (re)start just the cockpit
```

**"Claude forgets"** - Install the Claude Code bundle (`claude/` directory — skills + hooks) or add to `~/.claude/CLAUDE.md`:
```
At session start, call get_cached_context() to restore memory.
```

**Memories not found** - Rebuild the knowledge graph:
```bash
curl -X POST http://localhost:8000/api/memory/graph/rebuild
```

**Graph shows 0 edges** - Run rebuild after first install or upgrade:
```bash
curl -X POST http://localhost:8000/api/memory/graph/rebuild
```

**Restart everything:** `docker compose down && docker compose up -d`

---

<details>
<summary><b>How It Works</b></summary>

### The Flow

```
You say something important
        |
Claude saves it -> YAML file + Qdrant vector + Knowledge Graph node
        |
Auto-linker finds related memories -> Creates typed edges
        |
Later: Claude recalls by meaning + follows graph relationships
```

### Example

```
You: "Let's use PostgreSQL for JSON support"
AI:  saves to memory, creates embedding, auto-links to related memories

[3 days later]

You: "What database did we choose?"
AI:  vector search finds the memory
     graph expansion surfaces the related requirement and learnings
     "We chose PostgreSQL for its JSON support"
```

### Memory Types

| Type | Example | AI Behavior | Importance |
|------|---------|-------------|------------|
| `requirement` | "Must use Python 3.11+" | **Must** follow | 1.0 |
| `decision` | "Chose PostgreSQL" | Reference, can revisit | 0.85 |
| `preference` | "Prefers Terraform" | Suggest, offer alternatives | 0.75 |
| `learning` | "JWT bug fix" | Apply to similar cases | 0.65 |
| `fact` | "Project uses AWS" | Background context | 0.55 |
| `note` | "General observation" | Low-priority context | 0.35 |
| `session` | Session summary | Continuity context | 0.25 |

`summary` is also a valid type — generated by memory compaction (`POST /api/memory/compact`), not saved by hand.

### MCP Tools

| Tool | Purpose |
|------|---------|
| `observe(summary)` | Auto-classify and save (accepts caller `cwd` for project scope) |
| `recall_memories(query, task_hint?, session_id?)` | Graph-enhanced semantic search; `task_hint` (2+ words) surfaces memories matching your current task first |
| `recall_across_projects(query, current_project)` | Cross-project transfer recall across current, related, and global memory |
| `close_loop(memory_id, note?)` | Close an open loop: appends a RESOLVED stamp, drops it from the Open Loops capsule bucket |
| `save_memory(content, type)` | Manual save with explicit type |
| `memory_detail(memory_id)` | Single memory + neighbors + scope |
| `memory_kb(project)` | Typed slices (decisions / requirements / preferences / learnings) |
| `memory_pressure(project)` | Pressure metrics + flagged candidates |
| `memory_pressure_snapshot()` | Detailed pressure snapshot |
| `prune_plan(project, limit)` | Build prune plan (apply via REST only) |
| `backfill_lifecycle(project, dry_run)` | Tier metadata backfill on existing memories |
| `resume_packet(project)` | Continuity resume |
| `handoff_summary(project)` | Continuity summary |
| `agent_startup(project)` | Unified startup payload |
| `project_capsule(project)` | Thin project familiarity capsule |
| `publish_team_memory(project)` | Team-safe bundle of distilled project capsule and playbooks |
| `reflex_recall(text, project, session_id?)` | Cue-triggered recall before risky commands or edits |
| `memory_lifecycle()` | Behavioral classifier output |
| `memory_doctor(project)` | Trust report for YAML/Qdrant/vector/graph/provenance health |
| `get_cached_context(project)` | Flat context (prompt-cache optimized) |
| `get_hierarchical_context(project)` | Topic-grouped context tree |
| `skill_context()` | Extracted skills from memory clusters |
| `memory_stats()` | Health + graph metrics |
| `consolidate_memories()` | Detect duplicates and conflicts |
| `proactive_context_summary()` | Top signals ranked by importance x recency |
| `rebuild_knowledge_graph()` | Rebuild graph from all existing memories |
| `publish_memory(project, format)` | Export memory to an OKF knowledge bundle |
| `list_available_tools()` | List registered tool providers and status |
| `get_telemetry_summary()` | Tool-call telemetry summary |

### Team Memory Publishing

Team memory publishing emits distilled project capsules and playbook summaries. It strips known raw event-log, session transcript, private prompt, and hook payload fields from the generated bundle, but it is not a content redaction pass: review capsule/playbook text before sharing. Keep local memory as the default.

### REST API

| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/health` | GET | Health check |
| `/api/memory/save` | POST | Save a memory |
| `/api/memory/afk/save` | POST | Idempotently save an AFK operation; retries require the same normalized operation envelope |
| `/api/memory/afk/operations/{operation_id}` | GET | Fetch the exact AFK operation record (`?project=&operation_date=YYYY-MM-DD`) |
| `/api/memory/recall` | POST | Graph-enhanced search (optional `task_hint`: context-matched results first; optional `cwd`: attributes the recall event to the caller's project; optional `session_id`: carried into the memory_recalled event) |
| `/api/memory/recall/cross-project` | POST | Cross-project transfer recall |
| `/api/memory/reflex` | POST | Cue-triggered recall packet for risky commands or edits (optional `cwd`: attributes the recall event to the caller's project; optional `session_id`: carried into the memory_recalled event) |
| `/api/memory/observe` | POST | Auto-classify and save (accepts `cwd` for scope) |
| `/api/memory/stats` | GET | Statistics + graph metrics |
| `/api/memory/doctor` | GET | Trust report for YAML/Qdrant/vector/graph/provenance health |
| `/api/memory/projects` | GET | List of projects + memory counts |
| `/api/memory/context` | GET | Flat project context |
| `/api/memory/context/hierarchy` | GET | Topic-grouped (`?days=N` for date filter) |
| `/api/memory/context/smart` | GET | Token-capped smart context (`?limit=&max_tokens=`) |
| `/api/memory/context/proactive` | GET | Top signals + conflict detection |
| `/api/memory/context/skills` | GET | Inferred skill context from memory clusters |
| `/api/memory/context/startup` | GET | Unified agent startup payload |
| `/api/memory/capsule` | GET | Thin project familiarity capsule |
| `/api/memory/by-entity` | GET | Entity backlinks: memories whose `entities` contain `?entity=` (case-insensitive; `?project=&limit=`) |
| `/api/memory/detail/{id}` | GET | Full memory + v2 blocks: relationships (both in/out directions), provenance, lifecycle, storage, warnings; `neighbors` alias for backward compat |
| `/api/memory/kb` | GET | Typed slices |
| `/api/memory/pressure` | GET | Pressure metrics + flagged candidates |
| `/api/memory/resume` | GET | Resume packet for continuity |
| `/api/memory/prune/plan` | POST | Build prune plan (plan-id, 15-min TTL, 200-deletion cap) |
| `/api/memory/prune/apply` | POST | Apply plan with typed-id confirmation (REST-only) |
| `/api/memory/prune/superseded` | POST | Gated auto-prune of superseded memories (confirm-date token, ≤10/fire, ≤20/day, backup-first; REST-only) |
| `/api/memory/lifecycle/backfill` | POST | Backfill tier metadata (dry-run + execute) |
| `/api/memory/resparse` | POST | Transactional BM25 vocab refit — refuses on schema/parity divergence, fail-closed sentinel on interrupt (REST-only) |
| `/api/memory/{id}` | DELETE | Delete a single memory |
| `/api/memory/{id}/pin` | POST | Grant/revoke the identity pin (`{pinned: bool}`; human-only affordance, no MCP tool) |
| `/api/memory/{id}/dispute` | POST | Clear (or set) the disputed flag (`{disputed: bool}`; minimal resolution affordance) |
| `/api/memory/cleanup` | POST | Batch cleanup (prune superseded, age-based) |
| `/api/memory/graph` | GET | Graph visualization data |
| `/api/memory/graph/rebuild` | POST | Rebuild knowledge graph |
| `/api/memory/consolidate` | GET | Detect superseded/conflicting pairs |
| `/api/memory/recall/quick` | GET | Fast high-threshold recall for per-prompt injection |
| `/api/memory/compact` | POST | LLM-summarize old memories (dry-run by default) |
| `/api/memory/publish` | GET, POST | Export memory to an OKF v0.1 bundle (`mode=preview\|tar\|dir`) |
| `/api/memory/publish/synthesize` | POST | Start (or report) a background LLM synthesis job for a project scope |
| `/api/memory/publish/status` | GET | Poll a synthesis job's progress |
| `/api/memory/events` | GET, POST | GET: cursor-paginated event feed (`cursor=&limit=`, truncation-safe); POST: append a client-side session-summary event |
| `/api/memory/review` | POST | Record a review verdict (`keep\|fix\|kill`; kill deletes then records, fix is 501 until U3) |
| `/api/memory/sessions` | GET | Session transparency list folded from events (`?limit=`; `?project=` scopes to one project incl. its unattributed bucket, absent or `all` = every project; `after`/`before` are inclusive `YYYY-MM-DD` day bounds on each session's last activity; `window` = event-tail cap; `event_window.oldest_at` marks where the fold truncates; emits a `view_opened` counter) |
| `/api/memory/sessions/{id}` | GET | Full session detail: injected memories + recall cards with scores; unattributed recalls under `unattributed:<project>` |
| `/api/memory/feedback` | POST | One-click recall feedback (`useful\|wrong\|stale`) → `memory_feedback` event; labeled evidence only, never read into ranking |
| `/api/memory/insights` | GET | Cockpit aggregates (`?project=`): totals, per-week counts, 7d recall/miss/promotion stats with honest denominators, tier counts; `event_window` = bounded event-tail truncation info |
| `/api/memory/stream` | GET | Newest-first activity feed (`?project=&limit=&after=&before=`): `saved\|recalled\|promoted\|consolidated` rows merged from memory records + the bounded event tail; `after`/`before` are inclusive `YYYY-MM-DD` day bounds; `event_window.oldest_at` marks where recall/promotion rows truncate; working-tier saves carry `fades_in_hours` |

</details>

---

<details>
<summary><b>For Developers</b></summary>

### Local Development

```bash
pip install -e ".[dev]"
docker compose up -d qdrant
cd src && MCP_TRANSPORT=streamable-http python -m server
```

### Tests

Tests run in an isolated environment and **never affect your production data**.

```bash
# Run all tests (fast, local)
uv run --extra dev pytest -v

# Run all tests (isolated Docker)
docker compose --profile test run --rm test

# Run specific test file
docker compose --profile test run --rm test pytest tests/test_memory.py -v

# Cleanup
docker compose --profile test down
```

**What happens:**
- `qdrant-test` starts on port 6334 with ephemeral tmpfs storage
- Tests use `/tmp/test_memory` for YAML files (inside container)
- Production data at `~/.claude/memory/` and `~/.claude/qdrant/` stays untouched
- Everything auto-deletes when tests finish

### Project Structure

```
src/
├── server.py               # MCP server + REST API endpoints
├── core/                   # Embedder, VectorStore, Telemetry, utils
│   └── utils.py            # stable_hash_id() for string->int64 hashing
├── memory/
│   ├── manager.py          # MemoryManager (save, recall, get_stats)
│   ├── knowledge_graph.py  # KnowledgeGraph (networkx DiGraph, persistence)
│   ├── linker.py           # Auto-linking: classify relations on save
│   ├── graph.py            # Visualization graph builder
│   ├── cache_context.py    # Stable cacheable context + hierarchical variant
│   ├── topics.py           # Topic auto-classification (agglomerative clustering)
│   └── skills.py           # Skill extraction from memory clusters
├── crawler/                # Documentation crawler (Scrapy)
├── indexer/                # Document chunker + Qdrant indexer
└── tools/                  # MCP tool definitions
```

### Documentation

| Doc | Purpose |
|-----|---------|
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | Technical design, knowledge graph internals |
| [docs/SETUP.md](docs/SETUP.md) | Setup, embedding providers, migration |
| [docs/TUNING.md](docs/TUNING.md) | Customize what Claude remembers |
| [claude/INSTALL.md](claude/INSTALL.md) | Claude Code bundle: skills and hooks install |
| [docs/CLAUDE_MEMORY_SETTINGS.md](docs/CLAUDE_MEMORY_SETTINGS.md) | Claude-specific policy and tuning knobs |
| [docs/MIGRATION.md](docs/MIGRATION.md) | Version upgrade notes |

</details>

---

## Requirements

- Docker (or Python 3.11+)
- [uv](https://docs.astral.sh/uv/) (for the `uv run` commands used throughout)
- ~500MB disk (embedding model downloads on first use)
- macOS, Linux, or Windows (WSL)

---

## License

Apache-2.0
