Skip to main content
Glama

EvoMem-MCP

Memory for AI agents that knows when you changed your mind.

English · Español


You tell your coding agent: "we're using Postgres for the ledger." Two weeks later: "we moved the ledger to SQLite, Postgres was overkill."

A month after that you ask "what database does the ledger use?" Your agent's memory holds both notes, and the old one matches the question better. So the agent recommends Postgres again.

This is the default failure mode of agent memory: a pile of facts with no idea which ones are still true. EvoMem-MCP is a local MCP memory server that stores decisions as a history. Every new decision is linked to the one it replaces, the old one is kept but marked superseded, and recall always resolves to the version currently in force.

recall("ledger postgres")          # the query matches the OLD note best...
→ change: "We moved the ledger from Postgres to SQLite."     status: active
  └─ supersedes: "We picked Postgres for the ledger."        status: superseded

The result

We built a benchmark to test one hypothesis: does a memory that links decisions to the ones they replace stop an agent from acting on obsolete decisions?

The metric is ODR (Obsolete Decision Rate): the share of questions where the agent acts on a decision that was already replaced. We used 120 frozen scenarios, with 2 to 6 versions of a decision per topic plus distractors. The judge is a local 27B LLM (Bonsai 2 27B, temperature 0). It reads what memory returns and names the option it would act on.

memory

ODR ↓

95% CI

flat (all notes, no dates)

0.42

0.33–0.51

flat with dates (the fair baseline)

0.20

0.14–0.28

EvoMem: links set by the agent (remember(supersedes=…))

0.00

0.00–0.03

EvoMem: links detected automatically, LLM linker

0.00

0.00–0.03

EvoMem: links detected automatically, heuristic linker

0.008

0.00–0.05

Paired per scenario against flat with dates, EvoMem wins 24 scenarios and loses 0 (exact McNemar, p ≈ 1e-7).

The surprising part: dates are not enough

The memory was flat with dates, and the current version was right there in the context with its date in 111 of 120 scenarios. The 27B model still acted on an obsolete decision in 15% of them. Once the old note was explicitly marked status: superseded, that error went to 0.

So "just show timestamps and let the model figure it out" doesn't work reliably. An explicit "this was replaced" marker does.

Related MCP server: jevmem-mcp

How it works

flowchart LR
    A[Agent] -- "remember(text, supersedes?)" --> S[EvoMem server]
    S --> C["Cronista<br/>extracts trigger · change · consequences"]
    S --> L["Linker (optional)<br/>which active decision does this replace?"]
    C --> DB[("SQLite<br/>nodes + raw text + FTS5/BM25")]
    L --> DB
    A -- "recall(query)" --> S
    DB -- "every hit resolved to the<br/>head of its chain" --> A
  • Every note becomes a MemoryNode. Its narrative fields are trigger (why), change (what was decided), alternatives_tried, consequences and summary. The raw text is always kept and indexed, so recall never depends on the extraction being perfect.

  • supersede never deletes. The old node is kept with status: superseded and a link to its replacement. recall(include_history=True) returns the whole chain, newest to oldest, so the agent can see why things changed.

  • recall resolves to the chain head. A query that only matches an old version still returns the current one. This is what the benchmark measures.

  • Who creates the links?

    • The agent: remember(text, supersedes=id). This is the most reliable option.

    • The server, with --auto-supersede, when the agent doesn't pass an id:

      • llm uses any OpenAI-compatible endpoint, local or remote;

      • heuristic needs a change word plus overlapping vocabulary. It has no extra cost, but it is not safe on its own (see below).

  • Everything is local: one SQLite file, no cloud, no embeddings service.

Quickstart

git clone https://github.com/agunet/evomem-mcp.git && cd evomem-mcp
pip install -e .                      # or: uvx --from . evomem-mcp --db ~/.evomem/evomem.db
evomem-mcp --db ~/.evomem/evomem.db   # lite mode: no GPU, ~7 ms per remember

Claude Code:

claude mcp add evomem -- evomem-mcp --db ~/.evomem/evomem.db

Claude Desktop / any MCP client:

{ "mcpServers": { "evomem": {
    "command": "evomem-mcp",
    "args": ["--db", "/home/you/.evomem/evomem.db"] } } }

Automatic supersession with a local model (here llama.cpp; any OpenAI-compatible API works):

EVOMEM_LINKER_API_KEY=... evomem-mcp --db ~/.evomem/evomem.db \
    --auto-supersede llm --linker-model <model> --linker-base-url http://localhost:8080/v1

Tools

tool

what it does

remember(raw_text, tags?, session?, relations?, supersedes?)

Stores a note as a MemoryNode. With supersedes, the note replaces that decision (or the current head of its chain).

recall(query, k=5, include_history=False, history_depth=5)

BM25 search over the fields and raw text. Each hit is resolved to its current version, optionally with its history.

get_node(node_id)

Fetches one node.

forget(node_id, purge=False)

Removes a node from recall but keeps its history. purge deletes the node and keeps the raw text.

stats()

Counts nodes by status.

Reproduce it

The scenarios are generated from a fixed seed, so you get exactly the same 120. The deterministic judge runs in about a minute on CPU:

python make_odr_scenarios.py --out data/odr_scenarios.jsonl --n 120
python benchmark_decisiones.py --scenarios data/odr_scenarios.jsonl --auto heuristic

The LLM-judge numbers above need a judge model behind an OpenAI-compatible endpoint:

python benchmark_decisiones.py --scenarios data/odr_scenarios.jsonl --judge llm \
    --judge-model <model> --judge-base-url http://localhost:8080/v1 --auto heuristic,llm

There is also a second benchmark, VDR, which measures the opposite risk: does automatic linking hide decisions that are still valid?

python make_odr_scenarios.py --vdr --out data/odr_vdr.jsonl --n 80 --seed 11

The cost of automatic linking: VDR

A linker that is too eager is dangerous. Suppose you note "we switched the staging ledger to SQLite". If the linker treats that as replacing the production ledger decision, the production decision disappears from recall, even though it is still valid.

The VDR set has 80 scenarios where each component has a decision in another scope (staging, analytics, EU region…) that stays valid the whole time. Half of them use change words on purpose to fool the linker.

linker

link precision

still-valid decisions it hides (out of 80)

perfect links (reference)

1.00

—

LLM (Bonsai 2 27B)

0.95

1

heuristic

0.70 (0.56 with change words)

8

Verdict: in these scenarios the LLM linker works without a human in the loop, at the cost of about 1.5 s per remember with a local 27B. The heuristic linker should not run unsupervised.

What this does not prove (yet)

We'd rather you hear the limits from us:

  • The scenarios are synthetic, with one judge model and one seed. Real agent logs are messier. Evaluating on real agent history is on the roadmap.

  • The "story" part is still untested. With history_depth=0 (only the current version) ODR is already 0, so the benefit measured here comes from filtering what is obsolete. It does not yet show that seeing the why behind each change helps. That needs scenarios where the reason a decision was abandoned matters ("should we try Kafka?" when Kafka was already dropped for a reason that still holds).

  • Scope confusion is a separate problem. Even with perfect links, the judge applies a production decision to analytics in 21–30% of VDR queries. An explicit scope field is planned.

  • The heuristic linker misses paraphrases. It relies on overlapping words. Pass supersedes yourself, or use the LLM linker.

Side quest: a tiny model as the note-taker

The "cronista" (note-taker) that turns text into fields comes in three versions:

cronista

how

latency (p50, RTX 3060)

lite (default)

sentence heuristics, no GPU

~7 ms

ncm

NCM 88M base model writes the summary

207 ms

ncm + structural LoRA

88M model + a 2.4M-parameter LoRA we trained on 120k synthetic examples

359 ms

What we learned, including the negative results:

  • Fine-tuning for generic summarization doesn't help. On XSum the LoRA does not beat the base model (ROUGE-1 34.3 vs 34.9). For general summaries the model is already at its ceiling, so fine-tuning is only worth it for structured extraction.

  • Structured extraction works. The trained LoRA reaches ROUGE 88–93 on trigger / change / consequences. On a validation set with different wording that it never saw it scores almost the same, so it generalizes.

  • The generated summary invents things. It gets the service and the numbers right only 26–37% of the time. We fixed two dataset bugs and retrained, and it still invents. The likely cause is that the model's discrete decoding corrupts literal names and numbers.

  • So we stopped generating the summary. By default it is assembled from the extracted fields, after matching each one to the closest sentence in the source. That summary is correct 88–93% of the time and is also the fastest option.

  • The goal of under 100 ms per remember on the GPU path is not met yet (359 ms).

The full experiment log, with every table, ablation, dead end and dataset bug, is in docs/LAB_NOTEBOOK.md (in Spanish).

Roadmap

  • Linker that can abstain: when unsure, return the candidates to the agent instead of guessing.

  • A cheaper linker: a small model, called only when the note contains a change word.

  • An explicit scope field, to fix production vs staging vs analytics mix-ups.

  • Hybrid search (BM25 + embeddings) for notes that say the same thing in other words.

  • Trajectory scenarios, to test whether seeing why a decision was dropped helps, beyond filtering what is obsolete.

  • Evaluation on real agent histories.

  • PyPI release.

Status

v0.2.1, a research prototype. The MCP server works end to end and the tests pass (python test_smoke.py, python smoke_mcp.py). The API may still change.

License

MIT

Credits

The NCM (Next-Concept Modeling) base model is by HazyResearch. EvoMem's history design is inspired by hierarchical, versioned memory (CoMeT-style supersede / merge).

Available Tools

5 tools
forgetB

Forget a node: soft (drop from recall index, keep history) or purge.

Inputs: node_id (required), purge.

get_nodeC

Read one MemoryNode by id.

Inputs: node_id (required).

recallB

Find the most relevant MemoryNodes for a free-text query.

include_history=True replaces each hit by its supersedes chain so the agent sees the story, not just the latest state (CoMeT-style), going at most history_depth versions back to keep the response small.

Inputs: query (required), k, history_depth, include_history.

rememberA

Turn a chunk of agent history / interaction text into a MemoryNode.

Extracts the narrative structure (trigger, change, consequences) with the current cronista, stores it locally, and returns the node id plus the wall latency (to verify the <100 ms target of objective 2.1). relations: optional typed edges to other node ids, e.g. {"caused_by": [id]}. supersedes: id of the node this one replaces (e.g. a decision that changed); the old node is kept as history with status=superseded. If that node was already superseded, the new one replaces the current head of its chain. Without it, the server may detect the replaced node itself (--auto-supersede).

Inputs: raw_text (required), tags, session, relations, +1 more.

statsA

Storage stats: node counts by status, raw T3 text count.

Inputs: no named parameters.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.4/5.0

    Scored across 5 tools

    Disambiguation5/5

    Each tool has a clearly distinct purpose: remember writes, recall searches by free-text, get_node fetches by id, forget removes, and stats reports storage. The recall/get_node boundary is well explained (query-based with history chain vs single-id lookup), leaving no realistic misselection.

    Naming Consistency4/5

    All names are lowercase snake_case and the core operations use bare verbs (remember, recall, forget), but get_node mixes verb_noun and stats is a bare noun. The style is slightly mixed yet entirely predictable and readable.

    Tool Count5/5

    Five tools is well-scoped for a memory server: one per core operation (write, search, read, delete, inspect). Nothing appears redundant or missing at this size.

    Completeness4/5

    The surface covers the full memory lifecycle—create, query, read, soft-delete/purge, and stats—with updates modeled through supersedes chains rather than a separate update tool. Minor gaps remain, such as no listing/enumeration or relation-traversal operation, but agents can work around these via recall and get_node.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      B
      maintenance
      Local memory for AI agents in one SQLite file on your own machine. Facts carry a validity window and a confidence score, so an outdated one is superseded and downweighted rather than deleted, and retrieval fuses BM25, vector similarity and a knowledge graph.
      4
      Business Source 1.1
    • A
      license
      A
      quality
      B
      maintenance
      Provides AI agents with a persistent long-term memory over a single SQLite file, letting them write, screen, consolidate, and recall notes across sessions without any generative LLM in the loop. Recall judges relevance, follows links between notes, and signals when memory is insufficient, so agents get one to three useful facts per question instead of a generic top-k list.
      11
      65 PyPI
      4
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables AI coding agents to store decisions, preferences, and lessons in a local SQLite store, then recall the most relevant ones on demand. Runs fully local — no cloud, API keys, or per-query cost — and also exposes forget and consolidate operations.
      2
      Apache 2.0
    • A
      license
      A
      quality
      B
      maintenance
      Gives every AI agent you use one shared, versioned memory in a local SQLite file, so a fact saved from one app is found by all the others. Runs fully offline with no API key, records which agent wrote each change, and keeps every prior version so nothing is silently overwritten.
      7
      468 npm
      15
      MIT