Skip to main content

inspeximus — the agent memory that takes it back

Your agent's most expensive failure is not forgetting. It is confidently remembering the old answer.

Long-term memory for AI agents in one zero-dependency Python file, plus an MCP server for any client and a one-line config install for Claude Code, Cursor, Windsurf, Codex and Cline.

Correcting a fact is not the hard part, and this field already does it. Graphiti invalidates facts and leads with it; cognee ships forget as one of its four operations. When we measured mem0 and Graphiti, both kept the corrected value, which is the right thing to do. What neither has is a channel to undo that correction on command, from an instruction that names no value. Here a fact that was wrong, or true on Monday and outdated by Friday, gets corrected once, and you can still put it back afterwards.

The benchmarks ask which of two conflicting facts wins. The question after that one is whether you can take the correction back, and whether you can show what changed.

The name is from medieval charters. A king, bishop, abbot or town council opened with inspeximus, "we have inspected", reciting an older document in full to record that they had examined it, usually confirming it, and sealing the result so a later reader could check. It attested that the copy faithfully matched the original, not that the original was true. Same guarantee here, and provenance() says so in a limits field rather than leaving you to find out.

PyPI Downloads CI Claims audit Python Zero dependencies Tests License DOI

pip install inspeximus
After you correct a fact, how often does the old value come back? inspeximus 0%, Graphiti 0.x 13.3%, mem0 2.0.11 46.7%, and inspeximus with its guard disabled 100% — n=30 per system, each on its own native configuration.

The 30 seconds that matter

Every memory library can store and retrieve. The question nobody answers is what happens when a stored fact turns out to be wrong.

from inspeximus import Inspeximus

m = Inspeximus("memory.json")

m.remember("The staging database is db-3.internal", key="staging-db")
m.remember("The staging database is db-7.internal", key="staging-db")   # a correction

m.recall("which staging database")[0]["text"]
# 'The staging database is db-7.internal'          <- the correction wins, every time

m.revert("staging-db")                              # and it is reversible
m.recall("which staging database")[0]["text"]
# 'The staging database is db-3.internal'

No embedding drift, no "the LLM usually picks the newer one". The old value is retired by key, and the retirement is a record you can audit, revert, and prove.

Say the old value again and it still does not come back. That is the part a recency rule cannot do, and it is where most stores differ from this one: writing db-3 a third time, under the same key, leaves db-7 current. Going back is a decision you make on purpose, with remember(..., reaffirm=True) — the guard cannot un-supersede on its own.

The limit, because it is keyed: a statement written with no key is a new fact, not a correction, and it is outside the guard. If your pipeline re-ingests a stale document without keys, that text competes on its own merits. Both behaviours are measured in probes/does_a_restatement_take_the_key_back.py, which runs offline in a second.


When someone asks you to prove it

Turn receipts on and every write joins a hash chain. The values alone cannot tell you whether somebody edited the file behind the library's back. The chain can.

from inspeximus import Inspeximus

m = Inspeximus("memory.json", receipts=True)
m.remember("The staging database is db-3.internal", key="staging-db")
m.remember("The staging database is db-7.internal", key="staging-db")

m.verify_writes()[0]        # nothing has been touched yet
# True

# now somebody edits memory.json directly, turning db-7 into db-9
raw = open("memory.json", encoding="utf-8").read()
open("memory.json", "w", encoding="utf-8").write(raw.replace("db-7", "db-9"))

Inspeximus("memory.json", receipts=True).verify_writes()[1][0].split(": ", 1)[1]
# 'its TEXT or KEY no longer matches its write receipt (edited after write)'

provenance(key=...) answers the rest in one call: every value the key has held and the policy that retired each one, where the current value came from including taint inherited through summaries, whether the record still matches what its receipt committed to, and a limits field naming what none of it proves. Erasure works the same way. forget_subject() hard-deletes every memory attributable to a person, including the summaries that inherited it through lineage, and leaves a signed content-free tombstone, so a later reader can tell a deliberate erasure from tampering. erasure_certificate() makes that checkable by a third party with no private key and no reason to trust us.

inspeximus compliance prints the same evidence labelled by article, with its own scope attached: the agent-memory slice only, not the whole system, and not a certification.

What this is not. It is not compliance, and none of it is due yet. When the EU AI Act's high-risk obligations take effect, on 2 December 2027 for standalone Annex III systems and 2 August 2028 for those embedded in regulated products, the Act will ask for automatic event logging (Art. 12), retention of those logs (Art. 19), and accuracy, robustness and cybersecurity (Art. 15). None of those articles names memory, provenance or tamper-evidence, so what is here goes past the text rather than implementing it. docs/AI_ACT.md maps what the store already keeps onto the logging duty, and says where the mapping stops.


The next five minutes

The demo above ends at revert(). Here is what to do with it.

Put it under a real agent. Nothing to wire: remember on the way in, recall on the way out. The point is the key, because that is what makes a later correction land on the same fact instead of becoming a second one.

from inspeximus import Inspeximus

m = Inspeximus("memory.json")
user_id, choice, user_question = "u-1", "dark mode", "what does this user prefer"

m.remember(f"user prefers {choice}", key=f"pref::{user_id}")      # correcting later needs the key

context = [hit["text"] for hit in m.recall(user_question, k=5)]
print(context[0])
# user prefers dark mode

If you use a framework, there are adapters for LangChain, LangGraph, LlamaIndex, CrewAI, AutoGen, Haystack, Google ADK, OpenAI Agents and Pydantic-AI — with a ledger recording which are verified against a live install and which are recorded broken, rather than a wall of logos: docs/INTEGRATIONS.md.

Work through the examples in order. They run offline with no key, each one printing what it did:

01_basics.py remember, recall, correct, and read the history of a key
02_correction_and_erasure.py correction and erasure as separate channels, which they are
03_semantic_recall.py bring your own embedder
06_gdpr_erasure_receipt.py prove a deletion happened, to someone who does not trust you

Find your way around the code. docs/CORE_MAP.md lists every public method and the line it starts on, generated from the AST and re-checked in CI.

Then decide whether to believe any of it, using the two commands under Check us without trusting us.


The receipts

We measured the one thing the others do not publish: how often a corrected fact comes back.

Each system was run on its own native configuration, same task, same 30 trials:

system keeps the correction resurrects the old value
inspeximus 100% 0%
Graphiti 0.x (Neo4j + OpenAI) 86.7% 13.3%  95% CI [3.3, 26.7]
mem0 2.0.11 (OpenAI native) 53.3% 46.7%  95% CI [30.0, 63.3]
inspeximus, guard disabled 0% the control: this is what the guard is doing

n = 30 per system. mem0 measured at 2.0.11 (2026-07); mem0 is now on 2.0.18 and we have not re-run it — the version is stamped rather than the claim being restated as current. Full method, raw arrays and the re-runnable harness: RAMR · echo_resistance_backends_result.json

Read the Graphiti row correctly — its echo defense did not fail. Our own raw output records echo_attributable_flips: 0 out of 26 corrections that were extracted correctly before the echo ran. Graphiti's bi-temporal invalidation held every one of them. The 13.3% above is four pre-echo extraction misses — the correction never made it into the graph — which is a different failure from the one this table is about. Stated as the mechanism rather than the headline: on echo-attributable resurrection, Graphiti scores 0%, the same as us, by keeping the supersession link at write time. That is the real finding here: what separates these systems is whether the link is recorded, not who recorded it.

Two numbers you can check in three seconds, with no API key

Measured 2026-08-25 against Hindsight 0.9.2 (vectorize-io, 21k stars) and mem0, each in its own native config, n=20. These two need no judge at all — they read the raw recall payload, so nothing depends on a model reading well:

inspeximus 2.21.0 Hindsight 0.9.2 mem0
after a correction, recall returns the new value and not the old one 20 / 20 0 / 20 1 / 20
identical writes twice — same stored state? byte-identical 20 / 20 differ
model calls to do it 0 60 60

Both competitors return the corrected value and the retired one, and leave the choice to the caller. That is a defensible design — a bitemporal store handing back old and new with validity markers is being honest — but it is a different promise from ours, and the difference is whose job disambiguation is.

The first row is free to verify. No key, no server, no network:

git clone https://github.com/DanceNitra/inspeximus && cd inspeximus
python probes/integrity_bench_store_resolves.py --systems inspeximus

It finishes in milliseconds and prints store-resolved=1.00 (resolved=20 both=0 stale=0 neither=0, n=20). Adding ,mem0 or ,hindsight reproduces their columns and costs their own extractor calls. Method, caveats and the cells where we do not win.

The bottom row is the point. Turn our guard off and we score zero — so the number is the mechanism, not the benchmark being kind to us.


Use it in Claude Code (one line)

inspeximus install --ide claude     # also: cursor, windsurf, codex, cline

That wires an MCP server with 73 tools and three hooks. From the next session on, your agent starts knowing what the last one decided — no CLAUDE.md editing, no re-explaining:

  • SessionStart injects the decisions still in force
  • PostToolUse captures what actually happened, keyed by file
  • PreToolUse surfaces the decision that bears on the action before it runs

What you get

Correction as a first-class operation. remember(key=...) retires the previous value for that key. revert(key) restores it. history(key) shows the chain. All deterministic, all auditable.

Erasure that can be proven. forget_subject() hard-deletes every memory attributable to a subject — including summaries that inherited it through lineage — and leaves a signed, content-free tombstone, so a later audit can tell deliberately erased from tampered with.

Provenance you can check, not just store. check_sources() re-reads each record's origin and returns FRESH / DRIFTED / ORPHANED / UNCHECKABLE, plus four coverage numbers that are deliberately kept apart — because a source field that is 98.3% populated and 0.01% re-fetchable is a schema, not a guarantee. (Those two numbers are ours, measured on our own production store.)

Current-state applicability. evaluate_applicability() answers a different question from "is this memory true": may it drive an action here, now? Historical evidence can be perfectly valid and no longer authorized — the branch moved, the policy changed, the tenant differs, the window expired. Implements the vendor-neutral CML contract; two independent implementations agree on its frozen fixture.

Multi-tenant isolation. for_tenant("acme") gives a scoped view over one shared store, with the tenant bound into the signed message so a record cannot be moved between tenants and still verify.

Zero dependencies. One file. Semantic recall is optional (embed=your_model); the lexical fallback needs nothing. The MCP server, encryption and framework adapters are all opt-in extras.


Works with

langchain · langgraph-store · llamaindex · haystack · autogen · pydantic-ai · google-adk · memoryagentbench

13 of 13 verified against current upstream, 0 recorded broken. Three were broken a day ago and the list said so, which is the only reason you can believe this line: openai-agents was missing an attribute the SDK type-checks on, the store's single-writer guard was firing on this process's own threads under langgraph-checkpointer, and CrewAI replaced its storage protocol wholesale, so that one needed a second class rather than a repair. The counts are read from docs/integration_conformance.json by the claims audit, so this line cannot drift from what the runner last measured.

A "works with" list that only names successes is a logo wall. This one tells you which adapter will break before you build on it.


How this is tested

2,600+ tests, and a mutation gate that is the reason to believe them: 175 seeded defects, 175 killed, 0 survived. A test suite that passes is not evidence; a suite that catches every deliberate break is.

Every number on this page is registered in docs/CLAIMS.md, with the exact command that recomputes it. If one disagrees with your run, that is a bug report we want.

Check us without trusting us

Two commands. Neither needs an API key, a service, or any data of ours.

python claims_audit.py

Forty seconds. It reads every number we publish across the README, the docs and the site, and reports whether each one is registered, whether its pin still resolves, and whether a committed command recomputes it. It ends either with a list of problems or with one line:

every published number is registered, every pin resolves, every command names a real file

The counts are deliberately not quoted here. Quoting the audit's own totals inside a file the audit reads makes them change every time the documentation does, and the first draft of this section did exactly that and published stale figures. Run it and read the current ones.

What the run will show you: a handful of rows marked WITHDRAWN. Those are figures we published and then could not reproduce, kept in the register beside the probe that refutes them rather than deleted. A benchmark table is a claim about a competitor; that register is a claim about us, and it is the one we would rather you checked first.

python probes/integrity_bench_revert.py --systems inspeximus --judge local --n 5

Free, offline, deterministic, and it prints its own caveat that a local judge is not comparable with the OpenAI-judged figures in the table above. The honest instrument and the flattering one should not be the same instrument.


Documentation

Project site → the guided tour: the benchmark, the MCP surface, the governance story
Measured vs mem0 & Graphiti the resurrection table in full, with the control and the honest scope
Claude Code setup the one-line MCP install, and what each of the three hooks does
The long version every mechanism, every measurement, and the ones that failed
Full API every method, with the failure it exists to prevent
Erasure & GDPR right-to-erasure across derived summaries, with receipts
EU AI Act evidence Article 12 logging, mapped to what the store already keeps
MCP tools all 73, and what each is for
Claims ledger every published number, and the command that recomputes it
core.py, mapped every public method and where it lives, generated from the AST and checked in CI
Runnable examples working scripts rather than snippets
Framework adapters which are verified against a live install, and which are recorded broken
Changelog what changed and why, including what we got wrong

Who this is for

You are building an agent that runs for weeks, not minutes. It will learn something, and then that thing will change — a config value, a policy, a person's preference, a fact. The failure that will cost you is not the agent forgetting. It is the agent confidently remembering the old answer.

That is the failure this library is built around, and the only one we benchmark ourselves on. Most demos in this space show the write. This one shows the retraction, because that is the operation your agent will be judged by.


Citing

Archived on Zenodo with a version-independent DOI — 10.5281/zenodo.21708778. Machine-readable metadata is in CITATION.cff, so GitHub's "Cite this repository" button gives you BibTeX and APA directly.


MIT licensed. Built by Agora, an autonomous research organisation that publishes its failed replications next to its successful ones.

mcp-name: io.github.DanceNitra/inspeximus

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

inspeximus-2.21.0.tar.gz (1.2 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

inspeximus-2.21.0-py3-none-any.whl (557.1 kB view details)

Uploaded Python 3

File details

Details for the file inspeximus-2.21.0.tar.gz.

File metadata

  • Download URL: inspeximus-2.21.0.tar.gz
  • Upload date:
  • Size: 1.2 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for inspeximus-2.21.0.tar.gz
Algorithm Hash digest
SHA256 069e7eb148ea422550c9718493d715a3b6492a61b322d741f53e3fcf13937b7d
MD5 37269f56b7739e421a7d6481be30f148
BLAKE2b-256 41dd5eaf08c05773b69788c95cf8e66b9e0db92b9cd299f8ee5aab65bb9759e2

See more details on using hashes here.

Provenance

The following attestation bundles were made for inspeximus-2.21.0.tar.gz:

Publisher: release.yml on DanceNitra/inspeximus

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file inspeximus-2.21.0-py3-none-any.whl.

File metadata

  • Download URL: inspeximus-2.21.0-py3-none-any.whl
  • Upload date:
  • Size: 557.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for inspeximus-2.21.0-py3-none-any.whl
Algorithm Hash digest
SHA256 879a763b2c58437db4c6786140ba454d12a09b73fc49979689d083886408b98a
MD5 1ecf45f93da42371a622f217467e65d8
BLAKE2b-256 05ab4d129e5772b08ca101cf189373970473a444f0d38a14cf9318acb9471c9b

See more details on using hashes here.

Provenance

The following attestation bundles were made for inspeximus-2.21.0-py3-none-any.whl:

Publisher: release.yml on DanceNitra/inspeximus

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

2.26.1

2 files

2.26.0

2 files

2.25.0

2 files

2.24.1

2 files

2.24.0

2 files

2.23.0

2 files

This release

2.21.0 This release

2 files

2.20.1

2 files

2.20.0

2 files

2.19.1

2 files

2.19.0

2 files

2.18.0

2 files

2.17.1

2 files

2.17.0

2 files

2.16.0

2 files

2.15.0

2 files

2.14.0

2 files

2.13.0

2 files

2.12.1

2 files

2.12.0

2 files

2.11.0

2 files

2.10.6

2 files

2.10.5

2 files

2.10.4

2 files

2.10.3

2 files

2.10.2

2 files

2.10.0

2 files

2.9.1

2 files

2.9.0

2 files

2.8.2

2 files

2.8.1

2 files

2.8.0

2 files

2.7.0

2 files

2.6.1

2 files

2.6.0

2 files

2.5.0

2 files

2.4.1

2 files

2.4.0

2 files

2.3.2

2 files

2.3.1

2 files

2.3.0

2 files

2.2.2

2 files

2.2.1

2 files

2.2.0

2 files

2.1.2

2 files

2.1.1

2 files

2.1.0

2 files

2.0.2

2 files

2.0.1

2 files

2.0.0

2 files

1.89.0

2 files

1.88.1

2 files

1.88.0

2 files

1.87.0

2 files

1.86.0

2 files

1.85.0

2 files

1.84.0

2 files

1.83.0

2 files

1.82.0

2 files

1.80.0

2 files

1.79.0

2 files

1.78.0

2 files

1.77.0

2 files

1.76.0

2 files

1.75.0

2 files

1.74.0

2 files

1.73.0

2 files

1.72.0

2 files

1.71.0

2 files

1.70.0

2 files

1.69.0

2 files

1.68.0

2 files

1.67.0

2 files

1.66.0

2 files

1.65.1

2 files

1.65.0

2 files

1.64.0

2 files

1.63.0

2 files

1.62.0

2 files

1.61.0

2 files

1.60.0

2 files

1.59.0

2 files

1.58.0

2 files

1.57.0

2 files

1.56.0

2 files

1.55.0

2 files

1.54.0

2 files

1.53.0

2 files

1.52.0

2 files

1.51.0

2 files

1.50.0

2 files

1.49.0

2 files

1.48.0

2 files

1.47.0

2 files

1.46.0

2 files

1.45.0

2 files

1.44.0

2 files

1.43.0

2 files

1.42.0

2 files

1.41.0

2 files

1.40.0

2 files

1.39.0

2 files

1.38.0

2 files

1.37.0

2 files

1.36.0

2 files

1.35.0

2 files

1.34.0

2 files

1.33.0

2 files

1.32.0

2 files

1.31.0

2 files

1.30.0

2 files

1.29.1

2 files

1.29.0

2 files

1.28.1

2 files

1.28.0

2 files

1.27.2

2 files

1.27.1

2 files

1.27.0

2 files

1.26.1

2 files

1.26.0

2 files

1.25.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page