
# inspeximus — a zero-dependency Python agent-memory library
**Ask any memory where it came from — and check the answer.** `provenance(key)` returns the declared
source, the lineage it inherited, the evidence grade, every value the fact has held and which policy
retired each one, and whether the record still matches what its write receipt committed to. Delete the
fact and `erasure_certificate()` is the same answer for the deletion — a receipt an auditor verifies
offline, without the live store and without trusting us. Because a delete that returns success is not a
delete: we measured one that left the data recoverable in **five of six** places the application had put it
(`python probes/forget_verification_bench.py` — soft delete scores 0.17 and names the five leaking stores; the wired hard delete scores 1.00).
*"We have inspected" — the medieval charter that recites an earlier one word for word and
attests it unaltered. The self-correcting memory layer for AI agents.*
*Correct a fact once and it stays corrected: the store serves the new value and refuses to let the old
one creep back — deterministically, with no LLM on the write path, from a single zero-dependency file.
`revert(key)` puts a correction back on command, which is the cheapest proof that what sits underneath is
a real state model and not a log. Extracted from an autonomous research OS that has run it daily over a private ~10,000-note vault (our own deployment — you cannot re-run that one; every number you CAN re-run
is listed in [docs/CLAIMS.md](CLAIMS.md) with its command).*
`pip install inspeximus` → `import inspeximus` · [PyPI](https://pypi.org/project/inspeximus/) · [Hugging Face](https://huggingface.co/Danchi17/inspeximus) · [DOI](https://doi.org/10.5281/zenodo.21708778) · [Homepage](https://dancenitra.github.io/inspeximus/) · MIT · v2.44.0
[](https://github.com/DanceNitra/inspeximus/actions/workflows/audit.yml)
[](https://github.com/DanceNitra/inspeximus)
*If inspeximus's saved you some time, a ⭐ would mean a lot — it's how other people find it. Thank you!*

Built by **[Rastislav Drahoš](https://github.com/DanceNitra)** — extracted from [Agora](https://github.com/DanceNitra/agora), an autonomous research OS that runs it daily.
---
## Install into Claude Code in one line
```
/plugin marketplace add DanceNitra/inspeximus
/plugin install inspeximus@inspeximus
```
That registers this repository as a plugin marketplace and installs the MCP server, which then starts
with `uvx --from "inspeximus[mcp]" inspeximus-mcp` and keeps its store in `.inspeximus/memory.json` inside the
project. Nothing to configure by hand, and nothing to install globally.
Prefer the manual route? `pip install "inspeximus[mcp]"` and point your client at `inspeximus-mcp` — the
extra matters, because the core library is deliberately zero-dependency and the MCP server is the one
piece that needs a dependency.
## What you install, at a glance
### Your next session starts knowing what this one decided — with no LLM
`python -m inspeximus.claude_code --install` also wires the cross-session loop. On `SessionEnd` inspeximus
writes a **ledger diff** of what the session established — which keys changed value, which decisions were
recorded, what was erased, what is still open — read straight off its own supersession ledger. On
`SessionStart` it injects that, size-bounded and ranked. No transcript is sent anywhere, so it is instant,
free, and *byte-reproducible*: two stores replaying the same event log render an identical digest.
Measured on an 8-session, 2,606-record fixture (`probes/session_digest_multisession.py`): **1.000** of a
session's conclusions reach the next session, **1.0000** of below-threshold items stay out — and with the
salience bar removed that rejection collapses to **0.2213**, which is how you know the bar is doing the
work and the digest is not just the log again. Shell commands and file states are capped below the bar no
matter how much value they accrue. `close_session` costs 7 ms at that size.
Two things a frozen summary cannot do: the injected block is **re-resolved against the live store**, so a
decision reversed in a later session is replaced by the current one and an erased record leaves the
context too. Off switch: `INSPEXIMUS_SESSION_DIGEST=0`. Details in [docs/API.md](API.md).
## What you install, and what it does that others don't
A **mem0 alternative** built the opposite way: deterministic, not an LLM extracting facts on every write — a
memory that can say where a fact came from, keep a correction corrected, and show with an offline-verifiable
receipt what it erased. The right-hand column is what we found when we read the other libraries' current
source and docs ([the scan](AI_ACT.md)); it is a scan, not a proof of a universal negative, and we
correct it when someone shows us better.
| | **inspeximus** | what we found in mem0 / cognee / Zep · Graphiti |
|---|---|---|
| **Provenance — where did this fact come from?** | `provenance(key)`: declared source, inherited lineage, evidence grade, every value held + which policy retired it, and whether the record still matches its write receipt — **one call, no model** | bitemporal/graph provenance (Zep·Graphiti), a per-record history log (mem0); we found no single call that also checks the record against a receipt |
| **Why did recall return *this*?** | `why_recalled(id, query)` — the ranking decomposed, deterministically | *not assessed — the scan did not look for this* |
| **Correction — a fact changes, and you can audit what replaced what** | keyed supersession serves the current value and `history()` / `supersession_report()` show which value replaced which and under which policy; a restated stale value can't creep back (`echo_guard`) | LLM re-extract / bitemporal invalidation |
| **Verifiable erasure** | signed, content-free tombstone + `erasure_certificate` an auditor verifies **offline** | `delete()` — we found no receipt to verify |
| **Did the bytes actually go?** | **`inspeximus residue` — checks ANY store, exits non-zero on residue** | not offered in what we read |
| **Tamper-evident record-keeping** | hash-linked receipts + a signed anchor + **content bound across time** | SOC 2 audit logs (Zep — organisational, not cryptographic) |
| **EU AI Act / GDPR evidence** | **`inspeximus compliance` overlay + audit bundle** | not framed |
| **Revert — the proof the state model is real** | `revert(key)` rolls a corrected fact back to its predecessor: deterministic, no model call, no similarity guess | varies — see the note below |
| **Write path** | deterministic — **no LLM** | LLM extraction on every `add()` |
| **Dependencies** | **zero required — one pure-Python file's worth of core** | server / DB / vector / graph stack |
| **MCP server** | yes (one-command install) | varies |
On a scan of nine products, the only agent-memory library that ships verifiable erasure **and** tamper-evident
record-keeping **with zero required dependencies** — every qualifier load-bearing, and the scan is what we read
on those dates, not a proof about the field ([details](AI_ACT.md); honest: Zep has a real SOC 2/HIPAA surface,
just not verifiable erasure or AI-Act framing). The cross-system integrity numbers behind that — run by the same
harness through every system's native config, published whichever way they fall — are in
[`probes/INTEGRITY_BENCHMARK.md`](../probes/INTEGRITY_BENCHMARK.md), including the cell where inspeximus does *not*
win. (This sentence used to link to a section of this README that had been moved out; the link went nowhere and
the number it advertised was no longer on the page.)
**On the revert row specifically.** We used to write that no other agent-memory library
exposed a revert; our own pre-publication gate falsified it. So the claim here is not that others lack a
capability — it is that inspeximus does these **deterministically, with no model on the write path, from a
zero-dependency package, and verifiably without trusting us**. Every one of those four is something you can
check in a minute, which is the only reason to believe any of it. Where we describe another system we say
what we *found* when we read it, on a dated scan, and we would rather be corrected than be right by default.
## Where did this memory come from — and can you check?
This is the question people actually bring to an agent-memory layer, so it is the first thing this page
shows you how to answer. You rarely reach for provenance going forwards. You reach for it backwards — when
someone says *"delete that"*, and the fact is back next session because a summary that three other episodes
still support had absorbed it. `provenance()` answers that in one call, for one fact, in the order an
auditor asks:
```python
m.provenance(key="billing-api::auth") # or provenance(id="…") for one record
```
```
fact billing-api::auth
now oauth2 [active]
source adr-014 (not attested)
lineage primary observation
trust claimed
history 2 value(s), 1 retired
api-keys -> retired by keyed_lww
oauth2 -> active
integrity content matches; attribution matches; chain ok; unsigned the write receipt
```
Same answer from the shell (`inspeximus provenance billing-api::auth`, `--json` for the full object) and over
MCP (`provenance`). It assembles what the store already carries, rather than adding a new claim layer:
| field | what it answers | built from |
|---|---|---|
| `origin` | the declared source; the taint it **inherited transitively** through summarization, so a derived note is never mistaken for a first-hand one; whether an **origin attestation** bound it to a verified key; the acting user/agent/session | `source` + `derived_from` + `attested_key` |
| `trust` | the evidence grade — `claimed` → `corroborated` → `verified` → `settled`, earned from corroboration and external ratifications and **never settable by the writer** | `grade()` |
| `timeline` | every value the fact has held, its validity interval, and **which policy retired each one** (`keyed_lww`, `echo_guard`, `state_toggle`, …) | `history()` |
| `integrity` | whether the record still matches the content **and sources** its write receipt committed to — so an out-of-band edit that rewrites a record's source **without also rewriting the receipts sidecar** is caught — plus the current anchor to pin the answer against | `verify_attribution()` + `anchor()` |
**What it does not prove**, returned in a `limits` field so a caller rendering this cannot quietly drop it:
this is tamper-**evident**, not **correct** — a source that was already wrong when it was written is
committed faithfully and nothing here can tell. And unsigned (the default), the receipt chain only catches
an editor who cannot *also* rewrite the `.receipts` sidecar — which sits next to the store, so an attacker
with that much file access simply recomputes it and passes. We ship that as a failing negative control, not
as prose (`test_provenance.py::test_an_attacker_who_rewrites_the_sidecar_too_is_NOT_caught`). Pass
`receipt_key=` with the key off the write path, or have `anchor()` witnessed externally, for the property to
mean anything against someone who owns the store. Note too that `attribution_matches_receipt` is a *change*
detector: a legitimate re-derivation upstream can flip it with no attacker present.
None of the machinery here is new. Binding the actor and attribution into a tamper-evident provenance chain
so retroactive relabeling is detectable is Hasan, Sion & Winslett, *The Case of the Fake Picasso: Preventing History
Forgery with Secure Provenance* (USENIX FAST 2009; journal version ACM TOS 5(4), 2009); answering provenance facets from one call is standard in provenance-aware
databases (Perm, ProvSQL, ProQL); signed, Merkle-logged lineage for LLM agent memory specifically is
MemLineage ([arXiv:2605.14421](https://arxiv.org/abs/2605.14421)), which inspeximus's lineage auto-stamping
already credits. What is ours is the packaging: all of it with zero third-party dependencies, on by default, with
the limits attached to the answer.
### The two neighbouring questions, same shape
**"Why did recall return *this* record?"** — `why_recalled(id, query)` decomposes the ranking that produced a
hit into its terms, deterministically, with no model in the loop. It is the retrieval-side twin of
`provenance`: one asks where the content came from, the other asks why it surfaced.
**"What replaced what, and under which rule?"** — this is the auditable half of correction, and it is a
separate question from whether the correction was *right*. `history(key)` lists every value the fact has
held, oldest to newest; `supersession_report()` does it across the store; each retirement names the policy
that caused it (`keyed_lww`, `echo_guard`, `state_toggle`, …). `verify_attribution()` then asks whether the
records still match what their write receipts committed to. None of the four calls a model.
`revert(key)` sits at the end of that chain, and it is deliberately not the headline: it is the cheapest
demonstration that the timeline above is a real state model — you can move along it in both directions —
rather than an append-only log with a nice renderer.
## Don't take our word for it — three commands that answer about YOUR stack
Every claim above is a claim. These are checks you run yourself, and two of the three work on stores we
did not write.
**1. Did the bytes actually go?** `delete()` returning success is not the same as the value being gone
from disk. Point this at any directory — a vector database, a sqlite history, a JSONL trace, another
library's data dir:
```
inspeximus residue --root ./deployment --value alice@example.com
# PLAIN trace.jsonl fp=337961f64779
# LIVE v.sqlite [t.x x1] fp=337961f64779
# RESULT: residue found → exit 1
```
It separates three verdicts, and the distinction is the whole point: **LIVE** (a table still holds it — the
system retained it), **UNRECLAIMED** (in the bytes but in no row — the storage engine has not reclaimed the
page; run `VACUUM`, and this is *not* a vendor defect), **PLAIN** (a log or backup still has it). It never
echoes the value you gave it — findings carry a fingerprint, because a tool that hunts a secret and then
prints it into your terminal is itself the leak. It exits non-zero, so it works as a CI or DSAR gate.
We ran it against **mem0 2.0.11** with a local qdrant while building it: after its documented `delete()`
and `reset()`, **no live row anywhere held the value** — only unreclaimed sqlite pages, which is a storage
property, not a defect. We went looking for a difference and found an honest null. That is why this ships
as a measuring instrument and not as an argument.
**2. Prove the erasure at the only moment you can.** After `forget()` the value is gone with the row, so it
can never be searched for afterwards — the check has to run *during* the erasure:
```python
res = store.forget(ids=[rid], request_id="DSAR-1", verify_residue_in="./deployment")
res["residue"]["ok"] # False if it survived anywhere under that root
```
**3. Bind an audit bundle to CONTENT, not just to a chain.** A hash chain proves nobody rewrote the past.
It does not prove the store is serving what it committed to — the bundle is content-free by design, so a
clean chain over substituted content verifies fine:
```python
from inspeximus.audit_bundle import build_bundle, bind_content
witnessed = build_bundle(store) # the auditor takes this away
bind_content(witnessed, list(store.items))["ok"] # False if the content no longer matches
store.explain_growth(prior_anchor, writes=2) # and did the chain grow by what you did?
```
`bind_content` compares against the **earliest** receipt for each record, not the latest — the latest is
precisely what an amendment would have rewritten. `explain_growth` supplies the denominator only your
application has. Prior art we build on rather than reinvent: [RFC 6962](https://www.rfc-editor.org/rfc/rfc6962)
(a log proves inclusion, never validity) and Schneier & Kelsey, *Secure Audit Logs to Support Computer
Forensics* (USENIX Security 1998) — post-compromise entries are attacker-chosen by construction. The
contribution isn't the principle; it's that the check is a function you can run.
## New — the EU AI Act compliance-evidence layer for agent memory
When the AI Act's high-risk obligations start applying (**2 Dec 2027** for standalone Annex III systems,
**2 Aug 2028** for Annex I product-embedded ones — deferred from 2 Aug 2026 by Regulation (EU) 2026/1744, the
[Digital Omnibus on AI](https://eur-lex.europa.eu/eli/reg/2026/1744/oj/eng), in force 27 Jul 2026), a provider
of one -- and, for log-keeping, its deployer under Art. 26(6) -- has to be able to *show* things about what its
agent **remembers**. The Act requires automatic event
logging over the system's lifetime (Art. 12) and log retention (Art. 19); accuracy, robustness and
cybersecurity including resilience to attempts to alter performance (Art. 15); and GDPR Art. 17 gives data
subjects a right to erasure. **None of those articles say memory, provenance or tamper-evidence** — mapping
them onto agent memory is our reading, not the text, and this is not legal advice.
Of the agent-memory libraries we read (mem0, Zep/Graphiti, Cognee, Letta, LangMem — source at `main`,
24 Jul 2026), we did not find verifiable erasure with a receipt or tamper-evident record-keeping in any of
them; inspeximus ships both, as a drop-in overlay rather than a rebuild. That is a statement about what our
scan found, not about what exists: we have not surveyed the whole field, smaller projects exist that we have
not read, and a library may well have added either since. Tell us if we missed one and we will correct this.
```bash
inspeximus compliance --out report.html # article-labelled evidence, live counts from your store
inspeximus audit-build --out bundle.json # hand an auditor the bundle; they verify it offline (audit-verify)
```
Evidence, not certification; the memory slice only, and the obligations bind the provider/deployer, not the library.
→ **[docs/AI_ACT.md](AI_ACT.md)**
A delete that returns success tells you the call ran, not that the data left. Three commands from a
deletion obligation to a receipt you can re-verify without trusting us — with the honest scope of what a
residue scan can and cannot see: → **[docs/ERASURE.md](ERASURE.md)**
## Every claim below is checked by a script you can run
```bash
python claims_audit.py # downloads the published wheel from PyPI and audits THAT
python claims_audit.py --numbers # audits every NUMBER on the reader-facing surface (offline)
```
It fetches the released artifact, prints its sha256, and runs each claim on it — never on the working
tree. The write-path claim is enforced rather than asserted: sockets are disabled for the duration, so a
write that reached for a model would fail the check instead of passing it quietly. Claims about *other*
systems are listed separately and marked untestable here; verifying those means running those systems,
so they are never counted as passing.
```
13 passed · 0 FAILED · 0 skipped · 5 not testable here
```
The second mode audits the *numbers*. Every figure printed on this page, in `MCP_LISTINGS.md` and on the
homepage is registered in **[docs/CLAIMS.md](CLAIMS.md)** against the exact command that reproduces it
and a status — REPRODUCIBLE, REPRODUCIBLE-WITH-DEPS, PENDING-HARNESS, EXTERNAL or WITHDRAWN. A number that
appears here without an entry fails the audit and fails CI (`tests/test_claims_coverage.py`), which is the
part that stops the drift from coming back.
This exists because the exercise pays for itself: the first time we ran a README sentence against the
published wheel, it failed. Erasure did delete the record and scrub the bytes, but plain `forget()` left
no receipt, so the store's own `verify_writes()` reported the deletion as out-of-band — flagging a
legitimate API call as tampering. Fixed in 1.24.0, with a regression probe, and the audit now covers it.
The tightened audit then caught a second one: `forget_subject()` was writing *two* receipts per record,
one of them with the wrong reason (fixed in 1.24.3).
**On certification.** There is no certification body for an agent-memory library, and anyone claiming
otherwise is selling a logo. SOC 2 and ISO 27001 certify organisations running services; no scheme
certifies that a Python file deletes what it says it deletes. So instead:
- **`governance_audit.py`** attacks the strongest claim here — *tell it to forget everything about a
subject and it can prove it* — across three scenarios and three repeats each: erasure through
`derived_from` lineage, absence from the records, from recall under several phrasings, and from the
**bytes of every file the store wrote** including sidecars; exactly one receipt carrying the caller's
stated basis; tamper detection; survival across a reload; unrelated records intact; and an identical
end state on every run.
- **The audit must be able to fail.** `GOV_FALSIFY=1` skips the erasure, and CI requires the run to
report CLAIM BROKEN. A green falsification control would mean the checks measure nothing, so it is
treated as a build failure.
- **It runs where we cannot touch it** — every push and daily, on Linux, Windows and macOS, against
both this source and the wheel published on PyPI. The badge above is the result. If it is red,
believe the badge and not this paragraph.
What that does **not** certify: this store, not your vector index, prompt logs or backups; the receipt
proves the *act* of deletion, never the content; and an operator holding the receipt key can forge
receipts, so anchor the chain head externally if your adversary is the operator. Those limits are in the
docstrings too, and they are the reason the word "certified" does not appear anywhere else on this page.
### Witness network — the operator-adversarial layer
"Anchor the chain head externally" has a concrete, runnable form. `anchor()` emits a signed tree head; on its
own it catches a rewrite on **one** timeline (`verify_consistency`), but a compromised host can still show a
**different** history to a different client (a split-view / fork). Independent **witnesses** that co-sign the
head close that — an honest witness refuses to co-sign a fork, so a client requiring **k-of-n** cannot be shown
a forked head that reaches threshold. This is the operator-adversarial guarantee that a single-party receipt
cannot give on its own, whoever ships it: the property needs a second party by construction. Here it needs no
LLM, no GPU and no graph database.
```bash
# each independent party runs one witness (stdlib http server, zero framework):
python -m inspeximus.witness_server --port 9700 --state witnessA.json # prints its pubkey
```
```python
from inspeximus import Inspeximus
from inspeximus.witness_pool import collect_cosignatures, http_witness, Witness
anchor = store.anchor() # signed tree head of the whole history
witnesses = [http_witness("http://hostA:9700"), http_witness("http://hostB:9701"), Witness()]
out = collect_cosignatures("my-store", anchor, witnesses)
allowlist = ["