total-agent-memory
MCP serverDocs & knowledgePersistent local memory for coding agents: temporal knowledge graph, procedural and episodic recall
Unavailable. This server has no hosted endpoint yet, so ahel can't serve it.
Connect ahel once, and every AI you use reads what you have installed.
From the project's README
As published by vbcherepanov/total-agent-memory in README.md.
The only memory layer that learns how you work — not just what you said. Persistent, local memory for AI coding agents: Claude Code, Codex CLI, Cursor, any MCP client. Temporal knowledge graph · procedural memory · AST codebase ingest · cross-project analogy · 3D WebGL visualization.
Why this, not mem0 / Letta / Zep / Supermemory / Cognee? → docs/vs-competitors.md
v13.0.0 — MCP 2026-07-28, and honest benchmarks (2026-08-27)
Upgrade if you installed after the MCP Python SDK went 2.0. The 2.x line dropped the
@Server.list_tools()/@Server.call_tool()decorators this server was built on, and the dependency was floored atmcp[cli]>=1.0.0— so every freshpip/uvx/npx/brew/dockerinstall resolved 2.x and died at import. Existing installs kept working only because their pinned 1.x never moved.
Protocol. Tools now register through whichever API the installed SDK
exposes, and the server serves both protocol eras from one process: the
stateless 2026-07-28 revision — tools/list, server/discover and
tools/call with no initialize handshake, protocol metadata per request —
alongside the legacy handshake for clients on older SDKs. JSON-answering tools
return structuredContent, so clients stop re-parsing strings, and all 74
tools carry readOnlyHint / destructiveHint / idempotentHint annotations
that clients use to decide what runs without a confirmation prompt.
Claude Code plugin. The MCP server, the memory-protocol skill and the
seven capture hooks now install in one step:
/plugin marketplace add vbcherepanov/total-agent-memory
/plugin install total-agent-memory@vbcherepanov
LongMemEval now measures the product. The runner had its own
self-contained BM25 / RRF / MMR / CrossEncoder stack, so the published 96.2%
described an algorithm rather than this software. A new --modes store — now
the default — ingests each haystack into a real Store and queries
Recall.search. Re-measured: 95.1% R@5, 27.6 ms per query.
Benchmarks that no longer measure themselves. Recall.search bumps
recall_count on every row it returns, and the scorer adds
recall_boost = min(0.3, recall_count * 0.05). Spaced repetition is wanted in
normal use and fatal for measurement: successive runs against one database
scored 0.547 → 0.565 → 0.588 → 0.607 R@5 without a line of retrieval code
changing. Both runners now pass record_usage=False, a clean run and a re-run
are byte-identical, and every number below was re-measured on that basis. The
LoCoMo runner had also been printing categories 2 and 3 under each other's
labels.
BEAM (ICLR 2026) is now part
of the suite — retrieval across its ten memory abilities at the 100K / 500K /
1M scales, graded against each probe's source_chat_ids with no LLM in the
loop.
And ~3 GB it could not reach (13.0.2). The base install resolved
sentence-transformers, transformers, FlagEmbedding and peft, each of
which resolves torch, which on Linux resolves the entire nvidia-cu* set: 147
packages and ~3,108 MB of wheels against 97 and ~113 MB without them. The
Glama build sandbox simply ran out of disk unpacking
nvidia-cudnn-cu13. Yet the default configuration cannot touch any of it —
MEMORY_MODE=fast disables the reranker, and the same mode's
MEMORY_ALLOW_OLLAMA_IN_HOT_PATH=false is the flag that gates the
SentenceTransformer fall-through in Recall._compute. The stack now lives in
a rerank extra, and the mirror of the dependency-drift test keeps it there.
Every installer was also warming all-MiniLM-L6-v2 — the fallback model, not
the one the server embeds with — into a cache nothing reads.
The server was carrying ~450 MB it never used. chromadb and
sentence_transformers were imported at module scope, both are fallback paths,
and the second pulls in torch — so every user paid for a stack that fastembed
made unnecessary. Deferring them took import server from 558 MB to 116 MB
and a serving process from 1367 MB to 909 MB. Reported by d.snezhinskiy.
A failed fastembed init also stops being a single log line: it now names the
cache and the memory cost, because a macOS-purged model cache is the usual
reason a memory server suddenly wants 1.5 GB.
Bugs worth naming — all of the "works in a checkout, silently dead when
installed" kind. tree-sitter-language-pack was in no requirements file, so
"AST codebase ingest, 9 languages" degraded to whole-file chunks for everyone.
vocabularies/ and filters/ never made it into the wheel or the image, so
canonical tag normalisation ran against an empty vocabulary and every
memory_save(filter=…) was a no-op. The enrichment worker shared the Store's
sqlite connection — safe for reads, not for writes — and long ingests died on
cannot start a transaction within a transaction. Migration 028 failed on every fresh
database and could never record itself, so it retried on every startup forever
(root cause spotted by @juicetin in #12: two owners for one schema change). And
ai_layer/verifier.py looked for NLI calibrations at the pre-.tam path.
Full notes in CHANGELOG.md.
Earlier releases: v12.4.0 ·
v12.0.0 ·
v11.0.
Table of contents
- v13.0.0 — what changed
- The problem it solves
- 60-second demo
- Benchmarks — how it compares
- LoCoMo · BEAM · LongMemEval
- Competitor comparison
- What you get
- Architecture
- Install
- Quick start
- CLI:
lookup-memoryfor sub-agents - MCP tools reference
- TypeScript SDK
- Dashboard
- Update
- Upgrading from v8.x to v9.0
- Upgrading from v7.x to v8.0
- Ollama setup
- Configuration
- Performance tuning
- Roadmap
- Support the project
- Philosophy & license
The problem it solves
AI coding agents have amnesia. Every new Claude Code / Codex / Cursor session starts from zero. Yesterday's architectural decisions, bug fixes, stack choices, and hard-won lessons vanish the moment you close the terminal. You re-explain the same things, re-discover the same solutions, paste the same context into every new chat.
total-agent-memory gives the agent a persistent brain — on your machine, not in someone else's cloud.
Every decision, solution, error, fact, file change, and session summary is:
- Captured — explicitly via
memory_saveor implicitly via hooks on file edits / bash errors / session end - Linked — automatically extracted into a knowledge graph (entities, relations, temporal facts)
- Searchable — 6-stage hybrid retrieval (BM25 + dense + graph + CrossEncoder + MMR + RRF fusion), 95.1% R@5 on public LongMemEval
- Private — 100% local. SQLite + FastEmbed + optional Ollama. No data leaves your machine.
60-second demo
You: "remember we picked pgvector over ChromaDB because of multi-tenant RLS"
Claude: ✓ memory_save(type=decision, content="Chose pgvector over ChromaDB",
context="WHY: single Postgres, per-tenant RLS")
[3 days later, different session, possibly different project directory:]
You: "why did we pick pgvector again?"
Claude: ✓ memory_recall(query="vector database choice")
→ "Chose pgvector over ChromaDB for multi-tenant RLS. Single DB
instance, row-level security per tenant."
It's not just retrieval. It's procedural too:
You: "migrate auth middleware to JWT-only session tokens"
Claude: ✓ workflow_predict(task_description="migrate auth middleware...")
→ confidence 0.82, predicted steps:
1. read src/auth/middleware.go + tests
2. update session fixtures in tests/
3. run migration 0042
4. regenerate OpenAPI spec
similar past: wf#118 (success), wf#93 (success)
Benchmarks — how it compares
Everything below is retrieval: does the memory surface the passage that contains the answer, in the top-K? That is the part this project owns — answer quality is bounded above by it, and it can be graded with no LLM in the loop, which makes the numbers deterministic, free, and reproducible on your machine.
Two things to read them honestly:
- These are the default
fastprofile — FastEmbed, no reranker, no LLM anywhere in the path. That is what you get afterinstall.sh, not a tuned configuration. - Every runner passes
record_usage=False.Recall.searchnormally bumpsrecall_count, and the scorer addsrecall_boost = min(0.3, recall_count × 0.05)— so before v13, each re-run against the same database scored higher than the last, partly measuring its own history. A clean run and a re-run are now byte-identical.
LoCoMo — snap-research/locomo
1,536 gradable questions across 10 long-running conversations (5,882 turns ingested), plus 446 adversarial questions scored separately.
| Category | N | R@1 | R@5 | R@10 | MRR |
|---|---|---|---|---|---|
| single-hop | 282 | 0.202 | 0.500 | 0.638 | 0.332 |
| temporal | 321 | 0.411 | 0.689 | 0.735 | 0.524 |
| multi-hop | 92 | 0.163 | 0.413 | 0.435 | 0.256 |
| open-domain | 841 | 0.363 | 0.633 | 0.712 | 0.479 |
| overall | 1,536 | 0.331 | 0.607 | 0.687 | 0.448 |
Latency p50 18.2 ms, p95 55.4 ms. Temporal is the strongest category — the bi-temporal knowledge graph earns its keep. Multi-hop is the weakest and is the v13.1 target.
Reproduce: python benchmarks/locomo_bench.py --wipe →
benchmarks/results/v13-locomo-retrieval.json
BEAM — Beyond a Million Tokens, ICLR 2026
BEAM is the benchmark that starts where context windows stop: conversations of
100K / 500K / 1M tokens (a separate 10M set goes further), probed across ten
distinct memory abilities. Scored here against each probe's source_chat_ids.
Scale 100K — 20 conversations, 5,732 messages, 355 gradable probes:
| Ability | N | R@1 | R@5 | R@10 | MRR |
|---|---|---|---|---|---|
| contradiction_resolution | 40 | 0.700 | 1.000 | 1.000 | 0.824 |
| temporal_reasoning | 40 | 0.475 | 0.975 | 1.000 | 0.689 |
| knowledge_update | 40 | 0.550 | 0.925 | 0.950 | 0.719 |
| multi_session_reasoning | 40 | 0.375 | 0.675 | 0.850 | 0.486 |
| information_extraction | 40 | 0.400 | 0.625 | 0.725 | 0.503 |
| summarization | 36 | 0.167 | 0.444 | 0.556 | 0.267 |
| preference_following | 39 | 0.077 | 0.282 | 0.410 | 0.169 |
| event_ordering | 40 | 0.025 | 0.150 | 0.200 | 0.074 |
| instruction_following | 40 | 0.025 | 0.075 | 0.150 | 0.054 |
| overall | 355 | 0.313 | 0.575 | 0.651 | 0.423 |
Latency p50 17.7 ms. The shape is the useful part: contradiction
resolution, temporal reasoning and knowledge update are effectively solved,
while instruction_following and event_ordering are near-zero — those probes
ask whether a stated instruction was followed or in what order things
happened, and semantic similarity to the question does not find the message
where the instruction was given. Retrieval is the wrong primitive there, and
that is the roadmap item.
Scale 500K — 35 conversations, 38,058 messages, 629 gradable probes:
| Ability | N | R@1 | R@5 | R@10 | MRR |
|---|---|---|---|---|---|
| contradiction_resolution | 70 | 0.714 | 0.943 | 0.971 | 0.828 |
| knowledge_update | 69 | 0.464 | 0.855 | 0.899 | 0.617 |
| temporal_reasoning | 70 | 0.500 | 0.786 | 0.871 | 0.625 |
| multi_session_reasoning | 70 | 0.357 | 0.614 | 0.729 | 0.470 |
| information_extraction | 70 | 0.271 | 0.443 | 0.571 | 0.354 |
| preference_following | 70 | 0.071 | 0.300 | 0.471 | 0.168 |
| summarization | 70 | 0.100 | 0.286 | 0.414 | 0.174 |
| instruction_following | 70 | 0.029 | 0.157 | 0.257 | 0.086 |
| event_ordering | 70 | 0.014 | 0.029 | 0.186 | 0.042 |
| overall | 629 | 0.280 | 0.490 | 0.596 | 0.373 |
Scale 1M — 35 conversations, 74,630 messages, 625 gradable probes:
| Ability | N | R@1 | R@5 | R@10 | MRR |
|---|---|---|---|---|---|
| knowledge_update | 70 | 0.529 | 0.886 | 0.929 | 0.677 |
| contradiction_resolution | 70 | 0.686 | 0.871 | 0.914 | 0.772 |
| temporal_reasoning | 70 | 0.371 | 0.686 | 0.800 | 0.508 |
| multi_session_reasoning | 70 | 0.214 | 0.429 | 0.600 | 0.315 |
| information_extraction | 70 | 0.157 | 0.371 | 0.500 | 0.250 |
| summarization | 66 | 0.015 | 0.288 | 0.515 | 0.147 |
| preference_following | 69 | 0.029 | 0.246 | 0.406 | 0.134 |
| event_ordering | 70 | 0.000 | 0.157 | 0.329 | 0.069 |
| instruction_following | 70 | 0.029 | 0.086 | 0.200 | 0.061 |
| overall | 625 | 0.227 | 0.448 | 0.578 | 0.327 |
How it scales, and what that exposed
| Scale | Messages | R@5 | search p50 | ingest |
|---|---|---|---|---|
| 100K | 5,732 | 0.575 | 17.7 ms | 25.6 msg/s |
| 500K | 38,058 | 0.490 | 58.5 ms | 10.8 msg/s |
| 1M | 74,630 | 0.448 | 411.5 ms | 5.0 msg/s |
Recall decays gracefully — 13× the haystack costs 12.7 points of R@5, and the abilities that hold up (knowledge update, contradiction resolution) hold up at every scale. The two curves that do not decay gracefully are the interesting part, and they have separate causes.
Ingest — found and fixed. Throughput fell 5× across the three scales on
identical code. The cause was ours: graph/auto_link.py runs on every save and
constructed a fresh ConceptExtractor each time. The node-name cache lives on
the instance, so it was thrown away immediately and the whole graph_nodes
table was re-read per write — 1,000 saves triggered 1,000 full table reads
(~139 million rows at the 139k nodes this ingest reaches). Fixed in v13.0.1;
counting reads rather than timing makes the check load-independent, and it is
now 1 read per 1,000 saves. The ingest column above was measured before
that fix and is kept as the record of the problem.
Search — open. p50 grew 7× between 500K and 1M for 2× the data.
Store._binary_search loads the binary vectors of every active record into
numpy on each query, so search is linear in store size. That is a different
problem from the ingest one and is not fixed; an ANN index over the binary
vectors is the obvious answer and has not been built yet. Stated rather than
buried, because 411 ms is a real number a user would feel.
Reproduce: python benchmarks/beam_bench.py --scale 100K --wipe →
v13-beam-100K.json ·
v13-beam-500K.json ·
v13-beam-1M.json
LongMemEval — xiaowu0162/longmemeval-cleaned
470 questions across six question types, re-measured for v13 through the
product: each question's haystack is ingested into a real Store and queried
with Recall.search, the same path an agent takes.
| Question type | Count | R@5 (recall_any) |
|---|---|---|
| knowledge-update | 72 | 100.0% |
| multi-session | 121 | 98.3% |
| single-session-user | 64 | 95.3% |
| single-session-assistant | 56 | 94.6% |
| temporal-reasoning | 127 | 92.9% |
| single-session-preference | 30 | 80.0% |
| total | 470 | 95.1% |
Also recall_all@5 85.7% (every required fragment, not just one), NDCG@5
88.9%, 27.6 ms per query.
This replaces the 96.2% we published before, and the difference matters more than the 1.1 points. Until v13 this runner used its own self-contained BM25 / RRF / MMR / CrossEncoder stack, so the number described an algorithm, not this software.
--modes storedrives the shipping path and is now the default. The old modes remain for ablations.For reference on the same set, Mastra "Observational" reports 95.0% and Supermemory 85.4% — both cloud services.
Reproduce: python benchmarks/longmemeval_bench.py --modes store →
evals/longmemeval-2026-08-27-v13-store.json
On end-to-end accuracy numbers
Systems in this space usually publish LoCoMo accuracy — a generator answers from the retrieved context and an LLM judges it. We publish it too, with the two caveats that make it meaningful.
One LLM-judged run is a sample, not a measurement. Temperature 0 does not
make the API deterministic and OpenAI documents seed as best-effort, so the
runner takes --seed and we report three runs:
| Category | N | mean | min | max | spread |
|---|---|---|---|---|---|
| single-hop | 282 | 0.366 | 0.358 | 0.372 | 0.014 |
| temporal | 321 | 0.426 | 0.424 | 0.427 | 0.003 |
| multi-hop | 96 | 0.292 | 0.281 | 0.302 | 0.021 |
| open-domain | 841 | 0.570 | 0.567 | 0.573 | 0.006 |
| adversarial | 446 | 0.904 | 0.899 | 0.908 | 0.009 |
| overall (no adversarial) | 1,540 | 0.486 ± 0.002 | 0.484 | 0.488 | 0.005 |
| overall (all) | 1,986 | 0.579 ± 0.002 | 0.578 | 0.582 | 0.004 |
gpt-4o generator, gpt-4o-mini judge, seeds 1/2/3. Retrieval was byte-identical across all three — only generation and judging vary.
The judge needed two guards, and they point opposite ways.
Refusals scored as correct answers. On ~100 of the 1,540 non-adversarial
questions per run, the judge answered YES to "Not mentioned in the
conversation." against golds like Sweden, June 2023, Single — F1 exactly
0.00. Almost certainly the adversarial rule bleeding across, since the judge is
told to accept a refusal when the gold also indicates no information. Per
category the inflation runs 3.2 pp (open-domain) to 14.3 pp (temporal).
Hallucinations scored as correct abstentions. 99.6% of LoCoMo's adversarial golds are the empty string. The judge accepts almost any fluent answer against an empty reference, so 27–30 invented answers per run scored correct — inflating the one category we used to lead on.
Both are rules rather than judgements — on categories 1–4 the gold is a fact, so a refusal cannot be right; with an empty gold, only a refusal can be — so both now run deterministically at judging time. Effect: no-adv 0.551 → 0.486, adversarial 0.966 → 0.904, all 0.645 → 0.579. The table above is corrected.
How noisy is the rest? Aligning all 1,986 questions across the three seeds:
| share | |
|---|---|
| generator's answer differed between seeds | 12.5% |
| judge's verdict differed | 5.1% |
| judge flipped on an identical answer | 2.7% |
The aggregate holds within ±0.005 because those flips roughly cancel, not because the instrument is precise. Quoting one run to three decimals — as we did before — is not supported by the data.
Not comparable to the 90%+ figures some competitors publish: different generators, judges, prompts and question subsets. And on this evidence, an unguarded LLM judge can be worth six points on its own. The retrieval numbers above remain our primary metric because they are checkable without an API key.
benchmarks/results/v13-locomo-llm-3seeds.json ·
Runner: benchmarks/locomo_bench_llm.py
Do the retrieval numbers mean anything? — negative controls
A retrieval score with no floor under it is not a claim. Every LoCoMo run now scores three degenerate baselines on the same questions:
| Baseline | R@1 | R@5 | R@10 |
|---|---|---|---|
| random — ten turns from the same conversation | 0.001 | 0.012 | 0.023 |
| first — the ten earliest turns | 0.000 | 0.023 | 0.039 |
| recency — the ten most recent turns | 0.001 | 0.003 | 0.011 |
| the pipeline | 0.331 | 0.607 | 0.687 |
27× the best degenerate baseline. The controls run in the same pass as the metric, so the floor ships with the number rather than living in a script somebody stops running.
Latency profile
p50 (warm) ▌ 0.065 ms
p95 (warm) ▌▌ 2.97 ms
LoCoMo ▌▌▌ 18.2 ms/query ← full hybrid retrieval over 5,882 records
BEAM 100K ▌▌▌ 17.7 ms/query ← over 5,732 messages
LongMemEval ▌▌▌▌▌ 38.8 ms/query ← includes embedding + CrossEncoder rerank
p50 (cold) ▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌ 1333 ms ← first query after process start
Warm / cold reproducible from evals/results-2026-04-17.json.
Competitor comparison
We're not replacing chatbot memory — we're occupying the coding-agent + MCP + local niche.
Shortened here. Read the whole README on GitHub.
Signals
- GitHub stars
- 67
- Forks
- 17
- Last commit
- Sep 2026
Advanced
- Delivery
- total-agent-memory MCP server → your ahel gateway (mcp.ahel.ai) → every connected AI client.
- Catalog kind
- mcp-server
- Gateway key
io-github-vbcherepanov-total-agent-memory- Source
- github.com/vbcherepanov/total-agent-memory