Skip to content

Repository files navigation

memdiff

git diff for what an AI agent knows.

Python License: MIT Adapters Status

Pick two points in an agent's run and see only what changed in its memory — facts created, reconfirmed, invalidated, superseded — each attributed to the input that caused it.


$ memdiff diff --scope user_42 --from ep2_confirm --to ep3_misread

scope user_42  ep2_confirm → ep3_misread
1 step(s), read at 2026-03-03T12:00:00+00:00

+ CREATED     Alice --MANAGES--> Dana
    from ep3_misread  “Alice manages Dana”
- INVALIDATED Alice --REPORTS_TO--> Dana
    from ep3_misread  “Alice reports to Dana”

supersedes
~ Alice --REPORTS_TO--> Dana  ⇒  Alice --MANAGES--> Dana
    inferred, high · timestamp-identity

1 created, 1 invalidated

An agent misread "I'm covering for Dana while she's on leave" and came away believing it manages its own manager. Two lines, and the turn that caused them.

Why

Execution tracers — LangSmith, Langfuse, Phoenix — show the control-flow graph: which tool ran, which step threw. None of them show the memory-content graph: what the agent believes, how that belief changed, and which input changed it.

memdiff shows the second one.

Install

Requires Python ≥3.10. Docker only if you want the bundled Neo4j.

git clone https://github.com/Aryan-Pillai7/memdiff.git
cd memdiff

python -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate
pip install -e ".[dev,api,mem0]"

cp .env.example .env               # defaults match the compose file
docker compose up -d neo4j         # Neo4j 5.26 · bolt :7687 · browser :7474

Load the demo corpus and check the connection:

python scripts/load_fixtures.py
memdiff doctor

Usage

memdiff scopes                                   # which memory partitions exist
memdiff steps --scope user_42                    # points you can diff between
memdiff diff  --scope user_42                    # the whole run
memdiff diff  --scope user_42 --from S1 --to S2  # one window
memdiff diff  --scope user_42 --json | jq        # for piping

memdiff blame 86b2a038 --scope user_42           # why does it believe this?
memdiff blame "manages Dana" --search            # look one up by wording

The window is (from, to] — what changed after --from, up to and including --to, the same convention as git diff A B. Both bounds are optional. --exit-code returns 1 when there are changes.

blame

Usually the question you arrive with is not what changed in this window but why does the agent think this:

$ memdiff blame 438fa47f --scope user_42

Alice --REPORTS_TO--> Dana   438fa47f
  “Alice reports to Dana”
  retired, asserted 2 times

asserted by  ep1_truth     2026-03-01T12:00:00+00:00
    “Dana is my manager.”
reconfirmed  ep2_confirm   2026-03-02T12:00:00+00:00
    “Dana approved my leave request.”
retired by   ep3_misread   2026-03-03T12:00:00+00:00
    “I'm covering for Dana while she's on leave.”
replaced by   86b2a038  (inferred, high · timestamp-identity)

It takes a fact id — copy the short id from diff output. --search looks one up by wording instead, and an ambiguous search lists candidates rather than guessing.

Web view

memdiff serve                                    # http://127.0.0.1:7777
memdiff serve --target bolt://localhost:7687 --target ~/.mem0   # several at once

A timeline scrubber picks the window, the diff list is the primary view, and clicking a change expands into its blame chain or a graph neighbourhood. Pass --target more than once and a store picker appears.

It reads the same library the CLI does — /diff is byte-identical to memdiff diff --json. Binds to loopback by default: there is no auth and the graph may contain private conversation history.

Supported memory systems

The adapter is chosen from the connection target — a bolt:// URI is Graphiti, a filesystem path is Mem0 — and --adapter overrides.

Graphiti Mem0
Status stable under development — known limitations
Target bolt:// / neo4j:// URI path to the store folder
Pinned graphiti-core==0.29.3 mem0ai==2.0.18, Qdrant
Shape typed graph flat records
Diff window between steps time range
Attribution provenance-exact by clock, always marked
RECONFIRMED / RETROACTIVE ✅ declared unsupported

Every absence is declared rather than rendered blank — see docs/limitations.md for what each store cannot tell you and why.

Mem0 is under development. It lacks durable step boundaries, so attribution is transaction-time only and memdiff cannot localize which conversation turn caused a belief change. The CLI warns and asks to confirm (--yes skips it); the web view shows a dismissible banner. Nothing is blocked — the limitation is stated so you can weigh it.

Development

Command What it runs
pytest -m "not integration and not seeding" Unit tests. No Neo4j, no network.
pytest -m integration Needs Neo4j. Wipes the fixture groups — reload after.
ruff check . && mypy src Lint and types.
cd web && npm test -- --run Frontend tests.
python scripts/browser_check.py Real-browser checks against a running serve.
src/memdiff/
  adapters/graphiti/   Neo4j reader, provenance index, blame
  adapters/mem0/       SQLite + Qdrant reader, change-log classifier
  core/                store-neutral taxonomy, supersession, blame
  cli/  api/           the two surfaces
web/                   React + Vite frontend
docs/                  contract, schema findings, limitations

Adding a memory system is a new adapter module plus registration — see docs/adapter-contract.md, which also records where that claim held and where it did not.

Status

Early development. Both adapters and both surfaces work end to end; nothing is API-stable yet.

License

MIT — see LICENSE.

About

A git-diff-style debugger for agent memory.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages