CODE MEMORY · GATED LEARNING

Don't retrieve code that looks relevant.
Retrieve the code that's been proven to help.

mnesio is a Rust memory layer for coding agents. It records whether the context it gave you actually worked — did the build pass, did tests go green, was the diff accepted — and compiles that evidence into retrieval policy. Every change is re-evaluated against a canary set first, and refused if it regresses.

Apache-2.0 · runs entirely on your machine · no telemetry · 30 languages · 616 tests

4.8×
fewer tokens at equal recall
symbol context vs whole-file, paired A/B on one index
4.11ms
query latency p99
at 105,000 memories indexed
526
queries across 10 pinned public repos
derived from those repos' own git history · runs in ~300s · manifest run
30
languages via tree-sitter
Rust, Python, TS, Go, Java, C#, Swift, Kotlin, Zig…

Every one of these is a number we produced ourselves and can re-run. The ones that don't flatter us are further down the page, at the same size.

THE DIFFERENCE

A memory can be wrong. Ours finds out.

A search engine ranks. A memory learns — which means it has to be able to get worse, and something has to stop that. Every other code tool ships the first half.

1

Retrieve

Hybrid search seeds the context, the call graph expands one hop, and the result is packed to a hard token ceiling. Each symbol records why it's there.

2

Observe

The agent edits. Something observable happens: the build passes, tests fail, the diff is accepted or thrown away. That signal is free and everyone throws it away.

3

Attribute

“It helped” isn't learnable. Which part helped is — seed or expansion, full body or signature. That's the join key.

4

Propose

Symbols with enough decisive evidence and a low success rate become candidate suppression rules. Five decisive outcomes minimum — one lucky trial isn't a policy.

5

Gate

Each rule is applied, the canaries are re-run, and any regression rejects it. Rules are gated cumulatively, because two harmless suppressions can together strip a query's only answer.

!
Why this can't be bolted on later. The gate needs a held-out set, a replayable log to re-evaluate against, and versioned artifacts that supersede rather than overwrite. A system that stores embeddings in a vector database has nowhere to put any of that. It isn't a feature we shipped first — it's the foundation the other six rules were written around.
FIVE MINUTES

Works in the editor you already use.

One MCP server, every client that speaks the protocol. Nothing leaves your machine — the index, the embeddings and the outcome log all live in your cache directory.

mnesio
cargo install mnesio-mcp
claude mcp add mnesio -- mnesio-mcp --repo .
# ~/.cursor/mcp.json
{ "mcpServers": { "mnesio": {
    "command": "mnesio-mcp",
    "args": ["--repo", "."] } } }
# ~/.codex/config.toml
[mcp_servers.mnesio]
command = "mnesio-mcp"
args = ["--repo", "."]
# .vscode/mcp.json
{ "servers": { "mnesio": {
    "command": "mnesio-mcp",
    "args": ["--repo", "."] } } }
# ~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "mnesio": {
    "command": "mnesio-mcp",
    "args": ["--repo", "."] } } }
# settings.json
"context_servers": { "mnesio": {
  "command": { "path": "mnesio-mcp",
    "args": ["--repo", "."] } } }

Restart Claude Code. The mnesio_code_context tool appears automatically.

Settings → MCP → the server shows as connected once the index warms.

Codex CLI reads MCP servers from config.toml on start.

Agent mode only. Copilot's inline completions do not call MCP.

Cascade picks the server up on the next conversation.

Zed calls these context servers; the protocol is the same MCP.

mnesio_code_context

Ask for the code a task needs. Returns symbols packed to your token budget, each tagged with why it was included.

mnesio_code_outcome

Report what happened after the edit. This is the tool that makes the loop close — and the one no competitor has.

mnesio_search

The general memory layer: facts, decisions, and conversation history, bi-temporal and erasable.

YOUR REPO, NOT A DEMO

Watch it learn on the code you're writing.

Point mnesio at a repository and the dashboard shows the loop running against it — outcomes recorded, rules proposed, rules refused, and whether held-out retrieval actually improved.

localhost:7777 · /api/code/curve waiting for a server
held-out canary floor refused
rules committed
rules refused
outcomes recorded
held-out delta

Run mnesio-server --repo . and reload — this panel reads your live index. It renders a flat or negative curve exactly as faithfully as a positive one.

SIDE BY SIDE

Grep, chunks, graph — and the part they all skip.

mnesio Code knowledge graph Vector RAG Grep
Unit of retrieval Symbol + its callees, packed to a budget Graph node, traversedFuzzy chunkLine match
Knows if it helped Yes — build, tests, diff accepted NoNoNo
Improves from that Yes — compiled into retrieval policy NoNoNo
Can refuse its own change Yes — canaries re-run, any regression rejects n/an/an/a
History Append-only, bi-temporal, replayable Rebuilt per runOverwritten on upsertNone
Answer “what did it know at T?” Yes — snapshot_as_of(T) NoNoNo
Erasure Crypto-shred; log stays append-only Delete + reindexDelete + reindexn/a
Freshness while you edit Automatic — stale index can't be served Manual re-runManual re-indexAlways
Runs on your machine YesYes Usually a hosted DBYes

Grep wins a row. It should — it's exact and always fresh, and a tool that pretends otherwise is selling something.

WHERE WE LOSE

The measurements that don't flatter us.

This section exists because a landing page that only publishes wins tells you nothing about whether the wins are real. Here is what our own harness says, including the parts we'd rather it didn't.

20pp

Whole-file beats us at the ceiling

Given unlimited context, reading whole files reaches a median 78% where symbol retrieval reaches 58%. Median gap across the six repos large enough to discriminate: 20 points, worst case 27. Symbol memory is cheaper and strictly worse when budget is no object.

The catch: whole-file can cost 50k+ tokens per query. Below a 16k budget it often can't fit a single file, so it scores zero. The advantage is graceful degradation, not winning. An earlier run on a private corpus put the gap at 33pp — that corpus can't be re-run, so this page quotes the one you can.

10%

Unreachable by any ranker

Median share of tasks that share no indexed term with the code they touched — 4% at best, 18% at worst across the corpus. No scoring change reaches them, so the ceiling on ranking work is nearer 90% than 100%, and symbol retrieval sits at 58%.

Published because the alternative is quietly measuring against 100% and calling the same result a bigger achievement. A further 17% of misses are rankable — reachable by better scoring, and currently the largest addressable gap.

2

Failed attempts, still in the docs

Module-level prose has no owner in a symbol index. We tried a standalone module memory (recall@1 fell 50%→25%) and a per-symbol breadcrumb (peak 88%→62%). Both regressed the exact measurement that motivated them.

Still the biggest single source of misses. Not solved, not hidden.

23–46%

Edge resolution is approximate

The parser binds calls by bare name, so it can't always tell foo() from x.foo(). Between a quarter and a half of call edges resolve. Real type resolution is not shipped.

Graph expansion still pays +3 to +11 points on every real repo we measured, which says the resolved half is the half that matters.

0.12

The loop has never actually fired

Outcomes per symbol on a benchmark derived from git history — against the 5 needed before a rule is proposed. Run on ripgrep at two sizes: zero proposals both times. Doubling the suite didn't move the ratio.

It's structural. Every query is a different commit, commits touch different code, so a symbol is seen once and never revisited — and evidence needs revisiting. The gate is proven to refuse a canary-breaking rule in tests; on a derived benchmark it has nothing to adjudicate. Real usage has the repetition; that data is being collected now, and the curve goes here whichever way it points.

3/50

The reranker regressed prose

Our content reranker lifts code retrieval 52%→62%, but on prose benchmarks it regressed one category in 3 of 50 paired runs. So it ships opt-in for prose and on-by-default only for code.

An earlier unpaired run showed a fake 6-in-12 regression rate. Index randomness, not the reranker. Every A/B here is paired now.

§
How these were produced, and how to re-run them. The corpus is a committed manifest: 10 public repositories pinned to exact commits, each capped so the whole suite finishes inside a declared budget. Queries come from each repository's own git history — a real commit subject, written by a human before mnesio existed, with the symbols that commit touched as the gold set (traced by git log -L, every symbol, never a sample).

The harness measures its own noise. Running the identical configuration three times moves any single repository's recall by up to 2pp, so nothing under 2pp is reported as a finding — and one thing already isn't. A change to call-graph resolution looked like a 2pp effect until a control run showed identical variation with nothing changed. Seeding the index wouldn't fix that: two configurations build two different graphs whatever the seed, so a single run per arm is still one sample from each. Full method and raw runs →
THE CONSTRAINTS

Seven rules nothing is allowed to break.

Written before the code, and they've cost us features. That's the point of writing them down first.

01

Nothing commits without passing the gate

Canaries, a safety probe, and a non-negative objective delta. No exceptions, no temporary bypass.

02

Never overwrite history

An update invalidates and supersedes. The parent pointer survives. The log is append-only.

03

Scope is a security boundary

Learning never crosses a scope without an explicit aggregation step.

04

The log is the only system of record

Every index — vector, BM25, graph, KV cache — is a view, rebuildable by replay.

05

The write path stays under 5 ms

Embedding, evolution and extraction are async behind bounded queues. A write never waits on a model.

06

Bound the cascades

Evolution has caps and cooldowns. A-MEM has no convergence guarantee; the bounds are what replace it.

07

Every dependency behind a trait

LlmClient, Embedder, EventLog, Retriever, CodeParser, KvBackend, Signer. All swappable.

Point it at your repository.

One command, about five minutes, entirely on your machine. If it doesn't help, the dashboard will say so — that's rather the whole idea.