memtrust-cli 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. memtrust/__init__.py +11 -0
  2. memtrust/adapters/__init__.py +62 -0
  3. memtrust/adapters/base.py +2020 -0
  4. memtrust/adapters/mem0_adapter.py +456 -0
  5. memtrust/adapters/mem0_direct_adapter.py +1217 -0
  6. memtrust/adapters/mempalace_adapter.py +1166 -0
  7. memtrust/adapters/openviking_adapter.py +570 -0
  8. memtrust/adapters/zep_graphiti_adapter.py +181 -0
  9. memtrust/adapters/zep_graphiti_selfhosted_adapter.py +882 -0
  10. memtrust/cli.py +1201 -0
  11. memtrust/evals/__init__.py +5 -0
  12. memtrust/evals/compression.py +235 -0
  13. memtrust/evals/contradiction.py +451 -0
  14. memtrust/evals/crash_recovery.py +290 -0
  15. memtrust/evals/embedder_cost.py +163 -0
  16. memtrust/evals/embedding_drift.py +273 -0
  17. memtrust/evals/episode_temporal_leak.py +182 -0
  18. memtrust/evals/extraction_quality.py +373 -0
  19. memtrust/evals/filter_injection.py +371 -0
  20. memtrust/evals/language_degradation.py +189 -0
  21. memtrust/evals/lock_contention.py +263 -0
  22. memtrust/evals/locomo.py +392 -0
  23. memtrust/evals/longmemeval.py +249 -0
  24. memtrust/evals/mempalace_metadata_scale.py +494 -0
  25. memtrust/evals/migration_rollback.py +276 -0
  26. memtrust/evals/orphan_cleanup.py +235 -0
  27. memtrust/evals/ranking_quality.py +303 -0
  28. memtrust/evals/resource_sync_safety.py +336 -0
  29. memtrust/evals/result_consistency.py +239 -0
  30. memtrust/evals/scale_fixtures.py +189 -0
  31. memtrust/evals/scale_stress.py +502 -0
  32. memtrust/evals/stats_accuracy.py +240 -0
  33. memtrust/evals/temporal_kg_boundary.py +281 -0
  34. memtrust/receipt.py +318 -0
  35. memtrust/scoring/__init__.py +1 -0
  36. memtrust/scoring/cost_tracker.py +199 -0
  37. memtrust/scoring/llm_judge.py +161 -0
  38. memtrust_cli-0.3.0.dist-info/METADATA +624 -0
  39. memtrust_cli-0.3.0.dist-info/RECORD +42 -0
  40. memtrust_cli-0.3.0.dist-info/WHEEL +4 -0
  41. memtrust_cli-0.3.0.dist-info/entry_points.txt +2 -0
  42. memtrust_cli-0.3.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,624 @@
1
+ Metadata-Version: 2.4
2
+ Name: memtrust-cli
3
+ Version: 0.3.0
4
+ Summary: Independent, reproducible benchmark harness for agent-memory backends
5
+ Project-URL: Homepage, https://github.com/RudrenduPaul/memtrust
6
+ Project-URL: Issues, https://github.com/RudrenduPaul/memtrust/issues
7
+ Project-URL: Author - Rudrendu Paul, https://github.com/RudrenduPaul
8
+ Project-URL: Author - Sourav Nandy, https://github.com/Sourav-nandy-ai
9
+ Author: Rudrendu Paul, Sourav Nandy
10
+ License: Apache-2.0
11
+ License-File: LICENSE
12
+ Keywords: agent,benchmark,eval,evaluation,llm,memory
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: Apache Software License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Testing
21
+ Requires-Python: >=3.11
22
+ Requires-Dist: click<9.0,>=8.1.7
23
+ Requires-Dist: cryptography<49.0,>=48.0.1
24
+ Requires-Dist: httpx<1.0,>=0.27.0
25
+ Requires-Dist: rich<14.0,>=13.7.1
26
+ Provides-Extra: dev
27
+ Requires-Dist: mypy<2.0,>=1.10.0; extra == 'dev'
28
+ Requires-Dist: pip-audit<3.0,>=2.7.3; extra == 'dev'
29
+ Requires-Dist: pytest-cov<6.0,>=5.0.0; extra == 'dev'
30
+ Requires-Dist: pytest-httpx<1.0,>=0.36.2; extra == 'dev'
31
+ Requires-Dist: pytest<10.0,>=9.0.3; extra == 'dev'
32
+ Requires-Dist: ruff>=0.5.0; extra == 'dev'
33
+ Provides-Extra: mem0-direct
34
+ Requires-Dist: boto3<2.0,>=1.34.0; extra == 'mem0-direct'
35
+ Requires-Dist: elasticsearch<9.0,>=8.0.0; extra == 'mem0-direct'
36
+ Requires-Dist: fastembed<1.0,>=0.3.1; extra == 'mem0-direct'
37
+ Requires-Dist: google-genai<2.0,>=1.0.0; extra == 'mem0-direct'
38
+ Requires-Dist: mem0ai<3.0,>=2.0.12; extra == 'mem0-direct'
39
+ Requires-Dist: redis<6.0,>=5.0.0; extra == 'mem0-direct'
40
+ Requires-Dist: redisvl<1.0,>=0.3.0; extra == 'mem0-direct'
41
+ Requires-Dist: valkey<7.0,>=6.0.0; extra == 'mem0-direct'
42
+ Provides-Extra: mempalace-direct
43
+ Requires-Dist: mempalace<4.0,>=3.0.0; extra == 'mempalace-direct'
44
+ Description-Content-Type: text/markdown
45
+
46
+ # memtrust
47
+
48
+ Standardized, reproducible benchmarks for agent-memory backends, run against the vendors, not
49
+ published by them.
50
+
51
+ [![CI](https://github.com/RudrenduPaul/memtrust/actions/workflows/ci.yml/badge.svg)](https://github.com/RudrenduPaul/memtrust/actions/workflows/ci.yml)
52
+ [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/RudrenduPaul/memtrust/blob/main/LICENSE)
53
+ [![Version](https://img.shields.io/badge/version-0.2.0-blue.svg)](https://github.com/RudrenduPaul/memtrust/blob/main/pyproject.toml)
54
+ [![PyPI](https://img.shields.io/badge/pypi-memtrust-blue.svg)](https://pypi.org/project/memtrust/)
55
+
56
+ ```bash
57
+ pip install memtrust
58
+ memtrust run --backends mempalace,mem0,zep,openviking --eval all
59
+ ```
60
+
61
+ (For contributing to this repo instead of just running it, see [Development](#development) --
62
+ `pip install -e ".[dev]"` from a clone.)
63
+
64
+ **Contents:** [Why this exists](#why-this-exists) · [What it does](#what-it-does) ·
65
+ [Commands](#commands) · [How this differs](#how-this-differs-from-trusting-a-vendors-own-numbers) ·
66
+ [Contradiction detection](#the-eval-that-actually-matters-contradiction-detection) ·
67
+ [Compression fidelity](#the-eval-built-for-the-other-headline-overclaim-compression-fidelity) ·
68
+ [Temporal-KG boundary](#the-eval-built-from-mempalaces-own-bug-temporal-kg-boundary-detection) ·
69
+ [The landscape](#the-landscape-verified-not-benchmarked) · [Benchmarks](#benchmarks) ·
70
+ [GitHub Actions usage](#github-actions-usage) · [Self-host](#self-host) · [Install](#install) ·
71
+ [Hosted layer](#what-a-hosted-trust-layer-would-add) · [Backend coverage](#backend-coverage) ·
72
+ [Development](#development) · [License](#license) · [Success stories](#success-stories)
73
+
74
+ ## Why this exists
75
+
76
+ If you've compared agent-memory backends recently, you've probably noticed each one leads with a
77
+ different accuracy number, on a different benchmark, measured a different way. MemPalace's own
78
+ community already flagged the problem in public. Issue [#27](https://github.com/mempalace/mempalace/issues/27)
79
+ on the MemPalace repository, opened April 7, 2026 and still open, documents a widely-cited 100%
80
+ LongMemEval score that came from hand-tuning on the failed test questions themselves. The held-out
81
+ score is 98.4%, not 100%. A separate 96.6% figure people cite everywhere turns out to be mostly
82
+ ChromaDB's default embeddings doing the work, not MemPalace's own architecture. A "lossless"
83
+ compression claim drops accuracy by 12.4 percentage points in practice. Two internal pull requests
84
+ attempting to fix the reporting problem (#433 and #729) were both closed without merging on April
85
+ 12, 2026. As of this writing, the issue has 233 thumbs-up reactions and 39 comments.
86
+
87
+ None of that means MemPalace, or any other backend, doesn't work. It means nobody outside the
88
+ vendor had run the same test, the same way, against every option, and published the raw logs.
89
+
90
+ memtrust does that. It runs LongMemEval, LoCoMo, and a growing set of evals built specifically for
91
+ this project -- 14 of them as of this writing, registered in the CLI's `--eval` flag, plus a
92
+ fifteenth (temporal-KG boundary detection) that exists as a tested module but isn't wired into the
93
+ CLI yet. The two that matter most for understanding what this project is actually for:
94
+ contradiction detection, because neither LongMemEval nor LoCoMo tests the question that actually
95
+ matters once a memory system sits underneath a production agent -- what happens when a new fact
96
+ contradicts an old one? Does the backend flag the conflict? Silently overwrite the old fact with no
97
+ audit trail? Serve whichever version it happens to retrieve first? None of the four backends this
98
+ project tracks publish a number for that. And compression/round-trip fidelity, built to directly
99
+ test claims like the "lossless" one above: it stores content, retrieves it, and scores literal
100
+ reconstruction fidelity rather than semantic accuracy, per operating mode a backend exposes (see
101
+ `MemoryBackendAdapter.supported_modes`) -- the mechanism that would let a contributor with live
102
+ MemPalace credentials actually reproduce the 12.4-point compressed-mode accuracy drop
103
+ mempalace/mempalace#27 documents, instead of just citing it. **Neither has been run against any
104
+ live backend as of this writing** -- see `docs/methodology.md`. The other evals -- ranking quality,
105
+ crash recovery, extraction quality, embedding drift, scale/volume stress, lock contention, stats
106
+ accuracy, orphan cleanup, result consistency, migration rollback, filter injection, resource-sync
107
+ safety, and temporal-KG boundary detection -- each grew out of a specific real bug report against
108
+ one of the four tracked backends; see "Success stories" below for the full list.
109
+
110
+ ## What it does
111
+
112
+ Every command below was actually run against this repo, with zero vendor API keys configured, to
113
+ produce the output shown. Nothing here is simulated.
114
+
115
+ ```
116
+ $ memtrust run --backends mempalace,mem0,zep,openviking --eval all
117
+ memtrust 0.1.0 -- run_id=mt_2026-07-17T155402Z
118
+ Backends: mempalace, mem0, zep, openviking Evals: longmemeval, locomo, contradiction,
119
+ resource_sync_safety, compression, ranking_quality, scale_stress, embedding_drift, crash_recovery,
120
+ extraction_quality, migration_rollback, filter_injection, lock_contention, stats_accuracy,
121
+ orphan_cleanup, result_consistency
122
+
123
+ mempalace: SKIPPED (not configured) -- mempalace is not configured: environment variable
124
+ MEMPALACE_STORAGE_PATH is not set. Skipping this backend. See docs/methodology.md for setup
125
+ instructions.
126
+ mem0: SKIPPED (not configured) -- mem0 is not configured: environment variable MEM0_API_KEY is not
127
+ set. Skipping this backend. See docs/methodology.md for setup instructions.
128
+ zep: SKIPPED (not configured) -- zep is not configured: environment variable ZEP_API_KEY is not set.
129
+ Skipping this backend. See docs/methodology.md for setup instructions.
130
+ openviking: SKIPPED (not configured) -- openviking is not configured: environment variable
131
+ OPENVIKING_API_KEY is not set. Skipping this backend. See docs/methodology.md for setup
132
+ instructions.
133
+
134
+ Cost: $0.00 (no LLM-judged evals ran -- structural evals only, or judge not configured)
135
+
136
+ Full report: memtrust-report-2026-07-17.json
137
+ ```
138
+
139
+ That's the real, reproducible behavior of a fresh clone with no credentials: every backend reports
140
+ SKIPPED, the command exits cleanly, and a valid JSON report is still written. `memtrust --version`
141
+ currently prints `0.1.0` even though the package installs as `0.2.0` on PyPI -- a stale hardcoded
142
+ string in `src/memtrust/__init__.py` that hasn't caught up with `pyproject.toml`, shown here exactly
143
+ as it actually runs rather than smoothed over. Set the relevant environment variable for any backend
144
+ you want to actually test (`MEM0_API_KEY`, `ZEP_API_KEY`, `OPENVIKING_API_KEY`,
145
+ `MEMPALACE_STORAGE_PATH`) and that backend runs for real against its live API instead of being
146
+ skipped.
147
+
148
+ The eval logic itself is proven offline, against the bundled synthetic fixtures and, for several
149
+ adapters, the real installed vendor packages with only the network boundary mocked, by the test
150
+ suite:
151
+
152
+ ```
153
+ $ pytest --cov=memtrust --cov-report=term-missing
154
+ ...
155
+ Name Stmts Miss Cover
156
+ -------------------------------------------------------------------------------------
157
+ src/memtrust/adapters/base.py 290 1 99%
158
+ src/memtrust/adapters/mempalace_adapter.py 265 15 94%
159
+ src/memtrust/adapters/mem0_adapter.py 140 12 91%
160
+ src/memtrust/adapters/mem0_direct_adapter.py 281 34 88%
161
+ src/memtrust/adapters/openviking_adapter.py 178 18 90%
162
+ src/memtrust/adapters/zep_graphiti_adapter.py 63 3 95%
163
+ src/memtrust/adapters/zep_graphiti_selfhosted_adapter.py 165 24 85%
164
+ src/memtrust/evals/contradiction.py 127 2 98%
165
+ src/memtrust/evals/compression.py 86 1 99%
166
+ src/memtrust/evals/temporal_kg_boundary.py 90 4 96%
167
+ src/memtrust/receipt.py 118 10 92%
168
+ -------------------------------------------------------------------------------------
169
+ TOTAL 4130 256 94%
170
+
171
+ 580 passed, 8 skipped in 3.31s
172
+ ```
173
+
174
+ 580 passing tests across 33 source modules, 94% overall statement coverage, 98% on the
175
+ contradiction-detection eval, 99% on compression/round-trip fidelity, 96% on the new temporal-KG
176
+ boundary eval, 94-99% across the adapter layer. The 8 skips are live-`mempalace`-package tests that
177
+ only run with the optional `mempalace-direct` extra installed (`pip install -e
178
+ '.[dev,mempalace-direct]'`). Every test mocks its HTTP or wire
179
+ layer, or uses an in-memory fake backend -- none of them touch a real network, though a meaningful
180
+ share of the adapter tests now import and exercise *real installed vendor classes* directly
181
+ (`mem0ai==2.0.12`'s embedder and vector-store modules, and -- gated behind the optional
182
+ `mempalace-direct` extra -- the real `mempalace.mcp_server` functions), mocking only the outermost
183
+ network or wire-client boundary rather than the whole library. `graphiti-core` is not installed in
184
+ this environment, so its self-hosted adapter's tests still run against a hand-written Protocol
185
+ double built to match the real package's confirmed method signatures, not the real classes -- see
186
+ `docs/methodology.md`'s adapter confidence table for exactly which claim rests on which kind of
187
+ verification.
188
+
189
+ ## Commands
190
+
191
+ ```
192
+ $ memtrust --help
193
+ Usage: memtrust [OPTIONS] COMMAND [ARGS]...
194
+
195
+ memtrust: an independent, reproducible benchmark harness for agent-memory
196
+ backends.
197
+
198
+ Options:
199
+ --version Show the version and exit.
200
+ --help Show this message and exit.
201
+
202
+ Commands:
203
+ keygen Generate a new Ed25519 keypair for signing `memtrust run`...
204
+ report Read a prior `memtrust run` JSON report and print a formatted...
205
+ run Run the eval suite against the requested backends.
206
+ verify Verify a signed receipt produced by `memtrust run --sign`.
207
+ ```
208
+
209
+ | Command | Flags | What it does |
210
+ |---|---|---|
211
+ | `memtrust run` | `--backends TEXT` comma-separated list or `all` (default `all`) · `--eval TEXT` comma-separated from `longmemeval,locomo,contradiction,resource_sync_safety,compression,ranking_quality,scale_stress,embedding_drift,crash_recovery,extraction_quality,migration_rollback,filter_injection,lock_contention,stats_accuracy,orphan_cleanup,result_consistency`, or `all` (default `all`) · `--output FILE` (defaults to `./memtrust-report-<date>.json`) · `--locomo-dataset-path FILE` points the LoCoMo eval at a real, downloaded `locomo10.json` instead of the bundled synthetic fixture (memtrust does not bundle or auto-fetch the real dataset) · `--locomo-exclude-question-ids-file FILE` excludes known-bad-ground-truth LoCoMo question IDs from scoring · `--scale-stress-n-records INTEGER` (default `500`) sets how many synthetic records the scale-stress eval stores and re-queries · `--sign FILE` writes a signed `<output>.receipt.json` alongside the report, proving it was produced by the holder of the given Ed25519 private key | Runs the eval suite against the requested backends. A backend without its credential env var set prints `SKIPPED` and the run continues -- this command never crashes on missing credentials. |
212
+ | `memtrust report REPORT_PATH` | positional path to a prior JSON report | Reads a report written by `memtrust run` and prints a formatted summary. |
213
+ | `memtrust keygen` | -- | Generates a new Ed25519 keypair for signing reports with `run --sign`. |
214
+ | `memtrust verify RECEIPT_PATH` | -- | Verifies a signed receipt produced by `memtrust run --sign`; a tampered or mismatched receipt fails verification. |
215
+ | `memtrust --version` | -- | Prints the installed version. Currently prints `0.1.0`, one release behind the `0.2.0` this package actually installs as -- see the note above. |
216
+
217
+ The temporal-KG boundary eval (`src/memtrust/evals/temporal_kg_boundary.py`,
218
+ `run_temporal_kg_boundary_eval()`) is not yet wired into `--eval`'s known-eval list -- it exists as
219
+ a tested module and a MemPalace-specific capability, callable directly, not yet a `memtrust run
220
+ --eval temporal_kg_boundary` option. See its own section below.
221
+
222
+ Every line above came straight from running `memtrust --help`, `memtrust run --help`, and
223
+ `memtrust report --help` against this repo. Nothing here is invented.
224
+
225
+ ## How this differs from trusting a vendor's own numbers
226
+
227
+ Every backend memtrust tracks publishes its own benchmark numbers. None of them publish the same
228
+ benchmark, scored the same way, with the same held-out discipline. memtrust doesn't ask you to
229
+ trust it instead: it asks you to read the raw logs. Every run's methodology, prompt templates,
230
+ dataset versions, and scoring rubric are published in `docs/methodology.md`, versioned alongside
231
+ the code that produced them. If the methodology has a flaw, it's a flaw you can point to in a
232
+ specific file and line, not something buried in a vendor's internal eval pipeline.
233
+
234
+ General-purpose LLM eval frameworks (promptfoo, DeepEval, RAGAS, and similar tools) are mature and
235
+ widely used, but none of them ship a memory-backend adapter abstraction or a contradiction-
236
+ detection eval out of the box -- they're built for RAG quality, red-teaming, and general prompt
237
+ evaluation, not for comparing how different memory systems handle a fact that changes over time.
238
+ memtrust is narrower and more specific on purpose.
239
+
240
+ ## The landscape (verified, not benchmarked)
241
+
242
+ Real, publicly checkable numbers as of this writing (`gh api repos/<org>/<repo>`), not
243
+ memtrust-run scores -- accuracy and contradiction-handling comparisons stay in the "Benchmarks"
244
+ section below until a live run actually produces them:
245
+
246
+ | Backend | GitHub stars | Self-reported description |
247
+ |---|---|---|
248
+ | [MemPalace](https://github.com/MemPalace/mempalace) | 57,268 | "The best-benchmarked open-source AI memory system. And it's free." |
249
+ | [Mem0](https://github.com/mem0ai/mem0) | 60,688 | "Universal memory layer for AI Agents" |
250
+ | [Zep / Graphiti](https://github.com/getzep/graphiti) | 28,648 | "Build Real-Time Knowledge Graphs for AI Agents" |
251
+ | [OpenViking](https://github.com/volcengine/OpenViking) | 26,639 | "Self-evolving Context Database for AI Agents. Unify Agent Memory, Knowledge RAG and Skills." |
252
+
253
+ None of these numbers say anything about which backend handles a contradicted fact correctly --
254
+ that's the whole reason the harness exists. Star count measures adoption, not correctness.
255
+
256
+ ## The eval that actually matters: contradiction detection
257
+
258
+ LongMemEval and LoCoMo both measure recall: can the backend remember a fact you told it earlier.
259
+ That's necessary but not sufficient. The harder question is what a backend does when two facts
260
+ conflict: you tell it your meeting is at 2pm, then later say it moved to 3pm. Does it flag the
261
+ change? Overwrite silently? Serve whichever one it retrieves first? `memtrust`'s classifier stores
262
+ a fact, stores a contradicting fact, queries for it, then checks the actual retrieved content for
263
+ both values, rather than trusting whatever conflict signal the adapter itself reports. See
264
+ `src/memtrust/evals/contradiction.py` and the scoring-logic section of `docs/methodology.md` for
265
+ exactly how that classification works.
266
+
267
+ ## The eval built for the other headline overclaim: compression fidelity
268
+
269
+ mempalace/mempalace#27 documents two separate overclaims, not one: the LongMemEval score gap
270
+ described above, and a "lossless" compression claim that measured 12.4 percentage points lower in
271
+ practice under a compressed operating mode. memtrust could not previously reproduce that second
272
+ number at all -- there was no way to tell an adapter "run this under mode X vs mode Y" through the
273
+ shared interface. `MemoryBackendAdapter.store()`/`query()` now accept an optional `mode: str |
274
+ None` parameter, and `MemoryBackendAdapter.supported_modes` lets an adapter declare which mode
275
+ strings it actually understands (`MemPalaceAdapter.supported_modes` is `("raw", "AAAK")`, the two
276
+ names mempalace/mempalace#27 itself uses -- see `src/memtrust/adapters/mempalace_adapter.py` for
277
+ the exact provenance and confidence caveat on those names). Adapters with no mode variants accept
278
+ and ignore the parameter, so this is a purely additive, backward-compatible interface change.
279
+
280
+ `src/memtrust/evals/compression.py` runs the same store-then-retrieve round trip once per mode a
281
+ backend reports, and scores each round trip with a direct, deterministic character-level
282
+ similarity ratio (`fidelity_ratio()`, via `difflib.SequenceMatcher` -- not an LLM judge, since a
283
+ "lossless" claim is a literal-reconstruction claim, not a semantic one). This is what would let a
284
+ contributor with live MemPalace credentials point `memtrust run --eval compression` at it and
285
+ reproduce a "raw vs AAAK" fidelity gap directly. **As of this writing this eval has not been run
286
+ against any live backend** -- see `docs/methodology.md` for the same live-credentials caveat that
287
+ applies to every other eval in this table.
288
+
289
+ ## The eval built from MemPalace's own bug: temporal-KG boundary detection
290
+
291
+ MemPalace/mempalace#1913 (fixed by merged PR#1914, contributor ggettert) described a real,
292
+ concrete bug: `_temporal_filter_sql`'s `as_of` point-in-time query used a closed interval on both
293
+ ends, so a fact whose `valid_to` equaled the query's exact `as_of` instant still matched. Hand-roll
294
+ a fact change as `kg_invalidate(ended=T)` immediately followed by `kg_add(valid_from=T)` at the
295
+ identical boundary instant -- the exact pattern MemPalace's own pre-fix agent guidance told every
296
+ caller to do -- and an `as_of=T` query returns both the just-ended fact and its just-started
297
+ successor at once, so a single-valued fact reports two contradictory answers with no error.
298
+ `src/memtrust/evals/temporal_kg_boundary.py` reproduces that exact hand-rolled sequence against
299
+ `MemPalaceAdapter`'s `kg_add()`/`kg_invalidate()`/`kg_query()` and classifies the result with a new
300
+ `TemporalBoundarySignal` taxonomy, distinct from `ConflictSignal` and `RankingSignal` because it
301
+ concerns one narrow, structurally different failure: two facts sharing one instant, not a
302
+ contradiction across time or a ranking-order question.
303
+
304
+ Honest scope, stated the same way this project states it for every other eval: the real
305
+ `mempalace` PyPI package is not installed in this build environment, and PR#1914's fix had not
306
+ shipped in a released `mempalace` version as of this adapter's live-verified 3.5.0 build -- it
307
+ lands under the package's `[Unreleased]` changelog section. `tests/test_temporal_kg_boundary.py`
308
+ proves the *classification logic* is correct against two hand-written fake implementations that
309
+ reproduce the confirmed pre-#1914 (closed-interval) and post-#1914 (half-open-interval) SQL
310
+ comparison exactly. **This has not been run against a live MemPalace instance.** It also isn't
311
+ wired into `memtrust run --eval` yet -- call `run_temporal_kg_boundary_eval()` directly, or see the
312
+ test suite, until that CLI surface exists.
313
+
314
+ ## Benchmarks
315
+
316
+ **Not yet measured against live backends.** This repo was built without API credentials for
317
+ MemPalace, Mem0, Zep, or OpenViking. Publishing a number here without actually running the harness
318
+ against a live backend would be exactly the kind of unverifiable claim this project exists to
319
+ push back on, so there isn't one.
320
+
321
+ To produce real numbers:
322
+
323
+ ```bash
324
+ export MEM0_API_KEY=... # and/or
325
+ export ZEP_API_KEY=...
326
+ export OPENVIKING_API_KEY=...
327
+ export MEMPALACE_STORAGE_PATH=...
328
+ export MEMTRUST_JUDGE_API_KEY=... # needed for LongMemEval/LoCoMo grading; contradiction-detection doesn't need it
329
+
330
+ memtrust run --backends mempalace,mem0,zep,openviking --eval all
331
+ memtrust report memtrust-report-<date>.json
332
+ ```
333
+
334
+ The command prints per-backend accuracy and contradiction-handling rates, writes a full JSON
335
+ report, and prints an estimated cost for any LLM-judged evals that ran. `MemPalaceAdapter`'s
336
+ drawer and knowledge-graph calls are now live-verified against a real installed instance (see
337
+ "Backend coverage" below), but OpenViking's memory-write/query paths, and parts of the
338
+ self-hosted Mem0 and Zep/Graphiti adapters, are still built against best-effort interpretations of
339
+ documented or source-read product concepts rather than a live-confirmed API -- see the confidence
340
+ table in `docs/methodology.md` before treating any adapter's output as authoritative, and consider
341
+ that table's gaps a standing invitation to contribute a fix.
342
+
343
+ **Labeling requirement for any future `accuracy` figure published here.** LongMemEval and LoCoMo
344
+ `accuracy` grades the LLM judge's verdict on raw retrieved-record content directly -- there is no
345
+ answer-generation step in either eval runner. This is not the same measurement as the official
346
+ LongMemEval/LoCoMo leaderboards' generate-then-judge QA-accuracy scores. Any `accuracy` number
347
+ this project publishes for those two evals must be labeled "retrieval-graded accuracy," not bare
348
+ "accuracy," and must not be directly compared to leaderboard figures without that caveat. See
349
+ `docs/methodology.md`'s "Retrieval-graded accuracy vs. generated-answer accuracy" section.
350
+
351
+ ## GitHub Actions usage
352
+
353
+ Run the suite on a schedule and publish results to the leaderboard:
354
+
355
+ ```yaml
356
+ name: memtrust-leaderboard
357
+ on:
358
+ schedule:
359
+ - cron: "0 9 * * 1" # weekly
360
+ workflow_dispatch: {}
361
+
362
+ jobs:
363
+ benchmark:
364
+ runs-on: ubuntu-latest
365
+ steps:
366
+ - uses: actions/checkout@v4
367
+ - uses: actions/setup-python@v5
368
+ with:
369
+ python-version: "3.12"
370
+ - run: pip install memtrust
371
+ - run: memtrust run --backends mempalace,mem0,zep,openviking --eval all --output leaderboard/data.json
372
+ env:
373
+ MEM0_API_KEY: ${{ secrets.MEM0_API_KEY }}
374
+ ZEP_API_KEY: ${{ secrets.ZEP_API_KEY }}
375
+ OPENVIKING_API_KEY: ${{ secrets.OPENVIKING_API_KEY }}
376
+ MEMTRUST_JUDGE_API_KEY: ${{ secrets.MEMTRUST_JUDGE_API_KEY }}
377
+ - run: git add leaderboard/data.json && git commit -m "Update leaderboard" && git push
378
+ ```
379
+
380
+ This repo's own CI (`.github/workflows/ci.yml`) runs lint, type-check, test, and a dependency
381
+ security audit on every push and pull request -- no vendor credentials required, since every test
382
+ runs fully offline.
383
+
384
+ ## Self-host
385
+
386
+ ```bash
387
+ git clone https://github.com/RudrenduPaul/memtrust
388
+ cd memtrust
389
+ pip install -e ".[dev]"
390
+ export MEM0_API_KEY=...
391
+ memtrust run --backends mem0 --eval all
392
+ ```
393
+
394
+ Point an adapter at your own backend, or run the suite against your own conversation data instead
395
+ of the bundled synthetic fixtures (see `docs/methodology.md`'s note on swapping in the real
396
+ LongMemEval/LoCoMo datasets). Nothing leaves your machine unless you choose to publish it.
397
+
398
+ ## Install
399
+
400
+ ### npx (agent-native)
401
+
402
+ > **PyPI is live; the npm wrapper is publish-ready but not yet published.** `pip install memtrust`
403
+ > works today -- version 0.2.0 is on PyPI. The `memtrust-cli` npm package in `npm/memtrust-cli/` in
404
+ > this repo is built, tested, and passes both `npm pack --dry-run` and `npm publish --dry-run`
405
+ > cleanly (re-verified this session), but has not actually been pushed to the npm registry yet --
406
+ > `npm install memtrust-cli` still 404s as of this writing. The command below is the real,
407
+ > working distribution path once that publish happens; nothing about it is speculative except the
408
+ > "it's live on npm" part.
409
+
410
+ For CI and agent runners that have Node.js available but not necessarily a Python toolchain:
411
+
412
+ ```bash
413
+ npx memtrust-cli run --backends mempalace,mem0,zep,openviking --eval all
414
+ ```
415
+
416
+ The npm package is named `memtrust-cli` so it is unambiguous as a CLI tool at a glance (and so it
417
+ doesn't collide with any future `memtrust` JS library package). `npx` always resolves the package
418
+ name to its matching `bin` entry automatically, so `npx memtrust-cli ...` is what reliably works
419
+ for a zero-install first run. Once installed, the package also exposes the shorter `memtrust`
420
+ command as a second `bin` alias -- matching the underlying Python CLI's own command name -- so you
421
+ are not stuck typing `memtrust-cli` for every subsequent invocation; `memtrust run ...` works too.
422
+
423
+ This is not a zero-dependency install: `npx memtrust-cli` still fetches `memtrust` from PyPI on
424
+ first use. What it removes is no Python toolchain to provision by hand -- `npx memtrust-cli`
425
+ handles the interpreter and package fetch for you via a bundled, verified copy of Astral's
426
+ [`uv`](https://github.com/astral-sh/uv). Each platform package bundles a genuine, SHA-256-verified
427
+ copy of `uv`'s own GitHub release binary (fetched at npm package-publish time, never at end-user
428
+ install time), and its `bin` shim runs `uv tool run --from memtrust memtrust <args>`, which
429
+ transparently bootstraps a Python interpreter and installs `memtrust` from PyPI, caching it after
430
+ the first run.
431
+
432
+ ## What a hosted trust layer would add
433
+
434
+ The harness, adapters, and leaderboard in this repo are the entire OSS surface, and they're
435
+ sufficient on their own to compare backends. A hosted layer on top of this -- described here, not
436
+ built -- would add continuous regression monitoring that re-runs the suite automatically whenever
437
+ a tracked backend ships a new release, private scorecards that run the same methodology against a
438
+ team's own data shape instead of the public sample fixtures, and a compliance-report export for
439
+ teams whose security or legal review needs a documented third-party artifact rather than a
440
+ free-text summary. None of that exists yet. If it's ever built, it stays additive to the free
441
+ harness, never a requirement for using it.
442
+
443
+ ## Backend coverage
444
+
445
+ The MemPalace row below used to say "needs verification against a live instance" -- it needed more
446
+ than that. Every prior version of `MemPalaceAdapter` called a `mempalace.Palace` class
447
+ (`Palace(storage_path=...)` exposing `.remember()`/`.recall()`/`.invalidate()`) that never existed
448
+ in the real, installed package. `python3 -c "import mempalace; hasattr(mempalace, 'Palace')"`
449
+ returns `False`; grepping every `class` definition across the installed package turns up nothing
450
+ named `Palace` anywhere. Every test that appeared to pass before this rewrite was exercising a
451
+ hand-written fake standing in for that guess, never the real thing -- `store()`/`query()`/
452
+ `update()` had never actually worked against a live MemPalace install, in this project's entire
453
+ history, until this rewrite. `src/memtrust/adapters/mempalace_adapter.py` was rewritten from
454
+ scratch against the real, plain module-level functions in `mempalace.mcp_server`
455
+ (`tool_add_drawer`, `tool_search`, `tool_update_drawer`, `tool_delete_drawer`,
456
+ `tool_kg_add`/`tool_kg_invalidate`/`tool_kg_query`) -- every return shape documented in the
457
+ adapter's module docstring was captured by calling those functions live against a real, local
458
+ chromadb-backed palace, not read off a docstring and trusted. It's the kind of mistake this whole
459
+ project exists to catch in other people's benchmarks; finding it in memtrust's own adapter and
460
+ shipping the fix in the open, rather than quietly patching it, is the more useful story.
461
+
462
+ | Backend | Adapter status | Confidence (see docs/methodology.md) |
463
+ |---|---|---|
464
+ | MemPalace | Implemented -- drawer API + knowledge-graph API | High on the real `mempalace.mcp_server` functions this adapter now calls, live-verified against an installed `mempalace` 3.5.0 instance (see above). Still best-effort on compression-mode names (`"raw"`/`"AAAK"`) and on whether `degraded_retrieval` warnings are ever populated by the installed version -- see the adapter's module docstring for both caveats stated plainly. |
465
+ | Mem0 | Implemented -- hosted Platform API, self-hosted OSS server, and a direct in-process library adapter | High on the hosted Platform API and on what the installed `mem0ai==2.0.12` library's embedder/vector-store code actually does (confirmed by reading its real source, exercised directly in tests); medium-high on the self-hosted OSS server's route shape (confirmed from source, not run against a live server). |
466
+ | Zep / Graphiti | Implemented -- hosted Zep Platform API and a self-hosted `graphiti-core` adapter | Medium-high on the hosted API's documented contradiction-handling behavior; medium on the self-hosted adapter's wire-level shape (every method signature confirmed by reading `graphiti-core`'s real source, not by running it against a live Neo4j/FalkorDB instance -- the package isn't installed in this environment). |
467
+ | OpenViking | Implemented | Medium on architecture, low on exact memory-write/query paths -- still the adapter most likely to need correction against a live instance. |
468
+
469
+ Adding a backend adapter is the primary contribution path -- see `CONTRIBUTING.md`.
470
+
471
+ ## Development
472
+
473
+ ```bash
474
+ pip install -e ".[dev]"
475
+ ruff check . && ruff format --check .
476
+ mypy --strict src/memtrust
477
+ pytest --cov=memtrust --cov-report=term-missing --cov-fail-under=80
478
+ pip-audit
479
+ ```
480
+
481
+ `.pre-commit-config.yaml` wires ruff and mypy into `pre-commit` if you'd rather run these on every
482
+ commit than remember to run them by hand.
483
+
484
+ ## License
485
+
486
+ Apache 2.0. See `LICENSE`.
487
+
488
+ ## Success stories
489
+
490
+ 197 real issues/PRs filed by real contributors against MemPalace, mem0, Zep/Graphiti, and
491
+ OpenViking have been independently root-caused against this codebase: does the solution, as it
492
+ actually exists today, let you diagnose or resolve what was reported? 55 (28%) verify as a clean
493
+ PASS, 16 (8%) as PARTIAL (evidence captured, needs a human to
494
+ interpret further, or only part of the issue is covered), 42 (21%) as a genuine capability gap this
495
+ harness doesn't close yet, and 84 (43%) as not actually applicable (feature requests,
496
+ already-fixed-upstream, or genuinely out of scope). Every verdict below has been re-verified live
497
+ against the current codebase by a reviewer independent of whoever built the fix, not just cited
498
+ from a changelog. Full write-ups and validation evidence are tracked internally; the summary here
499
+ is for anyone deciding whether this harness would have caught their own bug.
500
+
501
+ **The headline story is about memtrust's own bug, not a vendor's.** Every version of
502
+ `MemPalaceAdapter` before this rewrite called a `mempalace.Palace` class -- `Palace(storage_path=
503
+ ...)` exposing `.remember()`/`.recall()`/`.invalidate()` -- that never existed in the real,
504
+ installed package. `python3 -c "import mempalace; hasattr(mempalace, 'Palace')"` returns `False`;
505
+ nothing named `Palace` appears anywhere in the installed package's source. Every test that appeared
506
+ to pass was exercising a hand-written fake standing in for that guess -- `store()`/`query()`/
507
+ `update()` had never once worked against a live MemPalace install. `src/memtrust/adapters/
508
+ mempalace_adapter.py` was rewritten against the real `mempalace.mcp_server` functions, with every
509
+ documented return shape captured by calling them live against a real local instance. A project
510
+ built to catch other vendors overclaiming found the same failure mode in its own code, and the fix
511
+ shipped in the open rather than quietly. See "Backend coverage" above for the full account.
512
+
513
+ **MemPalace**
514
+ - [#1754](https://github.com/MemPalace/mempalace/pull/1754) (@rodboev): a checkpoint recovery fix
515
+ for silently quarantined dim-None pickles. memtrust's contradiction eval couldn't previously tell
516
+ "silently quarantined" apart from "no update primitive at all"; it now can
517
+ (`ConflictSignal.EMPTY_OR_LOST`).
518
+ - [#1929](https://github.com/MemPalace/mempalace/pull/1929) (@jrzmurray): a fix for NUL bytes
519
+ silently corrupting a ChromaDB index. memtrust's `store()` used to trust "no exception" as proof
520
+ of a durable write; an opt-in read-after-write verification step now catches this.
521
+ - [#1450](https://github.com/MemPalace/mempalace/pull/1450) (@lealbrunocalhau): a fix for an empty
522
+ embedding response getting scored as a wrong answer instead of flagged as infra failure. Same
523
+ fix as #1754 above.
524
+ - [#1823](https://github.com/MemPalace/mempalace/pull/1823) / [#1543](https://github.com/MemPalace/mempalace/pull/1543)
525
+ (@fatkobra): lock and write-integrity fixes that pointed at the same read-after-write gap #1929
526
+ closed.
527
+ - [#1913](https://github.com/MemPalace/mempalace/issues/1913) / [PR#1914](https://github.com/MemPalace/mempalace/pull/1914)
528
+ (@ggettert): a temporal-KG `as_of` boundary bug where a fact ending at exactly the query instant
529
+ still matched alongside its successor. memtrust's new temporal-KG boundary eval reproduces the
530
+ exact hand-rolled `kg_invalidate()`-then-`kg_add()` sequence that triggers it -- see "The eval
531
+ built from MemPalace's own bug" above for the honest not-yet-live-verified caveat.
532
+ - [PR#1890](https://github.com/MemPalace/mempalace/pull/1890) / [#1889](https://github.com/MemPalace/mempalace/issues/1889)
533
+ (@JosefAschauer): an `authored_at` chronology tie-break fix for `_hybrid_rank`. memtrust's ranking
534
+ classifier now credits a top-level `authored_at` field, not just one nested under `metadata`, as
535
+ a genuine ranking-driving signal.
536
+ - [#524](https://github.com/MemPalace/mempalace/issues/524) (@gaby): a buried-comment report that
537
+ "no API key required" doesn't mean "no network required" -- ChromaDB's default embedder still
538
+ needs to download a model on first use, which silently breaks airgapped setups. The adapter's
539
+ module docstring now says so explicitly.
540
+
541
+ **mem0**
542
+ - [#5973](https://github.com/mem0ai/mem0/pull/5973) (@abhay-codes07, superseded by
543
+ [#5992](https://github.com/mem0ai/mem0/pull/5992)): an empty-string entity-id filter scoping bug.
544
+ memtrust's mem0 adapter only reached the hosted Platform API and had no delete operation at all,
545
+ so it couldn't have caught this. A self-hosted adapter with tested delete/delete_many primitives
546
+ now can.
547
+ - [#4297](https://github.com/mem0ai/mem0/pull/4297) (@utkarsh240799): a dimension auto-detection
548
+ fix. The self-hosted adapter now routes to the right deployment, though no test yet reproduces
549
+ this specific bug end to end, so this one is partial, not fully caught.
550
+ - [#4573](https://github.com/mem0ai/mem0/issues/4573) (@jamebobob): a 32-day audit of 10,134 real
551
+ mem0 entries finding 97.8% junk. memtrust's new extraction-quality eval and
552
+ `ExtractionQualitySignal` taxonomy cover the audit's own junk categories, including its
553
+ 808-duplicate feedback-loop case.
554
+ - [PR#5980](https://github.com/mem0ai/mem0/pull/5980) (@HrushiYadav): a filter-injection fix for
555
+ the Elasticsearch vector store. A new filter-injection eval exercises the real, installed
556
+ `mem0.vector_stores.elasticsearch.ElasticsearchDB._validate_filter()` directly and confirms it
557
+ rejects the exact malicious filter shape (`{"user_id": {"$ne": ""}}`) this PR fixed.
558
+ - [#4956](https://github.com/mem0ai/mem0/issues/4956) (@NDNM1408): an open proposal that mem0's
559
+ add-only pipeline surfaces stale, contradictory facts with no recency signal. memtrust's
560
+ contradiction eval now runs the same literal add-only scenario (two `store()` calls, no explicit
561
+ update) against this taxonomy.
562
+ - [#4884](https://github.com/mem0ai/mem0/issues/4884) (@wangjiawei-vegetable): a hardcoded
563
+ English-only tokenizer silently degrading non-Latin-script retrieval. A new
564
+ `LanguageDegradationSignal` and non-Latin-script fixtures now catch this shape.
565
+
566
+ **Zep / Graphiti**
567
+ - [#1489](https://github.com/getzep/graphiti/issues/1489) (@brentkearney): a bi-temporal
568
+ `invalid_at` correctness gap. memtrust's contradiction classifier used to discard Graphiti's own
569
+ `invalid_at` metadata and infer everything from a fixed top-5 text match, misreading a correctly
570
+ flagged case as a silent overwrite. It now checks the metadata first.
571
+ - [#1275](https://github.com/getzep/graphiti/issues/1275) (@rafaelreis-r, still open): O(n)
572
+ entity-resolution context growth silently dropping episodes past roughly 300 ingested. A new
573
+ self-hosted `graphiti-core` adapter plus a scale/volume-stress eval now tracks a fixed "anchor"
574
+ record's recall across ascending checkpoints against real `add_episode()` ingestion -- the same
575
+ shape this issue describes.
576
+ - [#836](https://github.com/getzep/graphiti/issues/836) (@matthiaslau) / [#920](https://github.com/getzep/graphiti/issues/920)
577
+ (@markwkiehl): two separate crashes in `update_communities()`/`resolve_edge_contradictions()` --
578
+ a too-many-values-to-unpack error and a tz-naive/aware datetime comparison error. A new
579
+ `CrashSignal` classification recognizes both exact shapes instead of surfacing an opaque generic
580
+ exception.
581
+ - [PR#1222](https://github.com/getzep/graphiti/pull/1222) (@david-morales) / [PR#1183](https://github.com/getzep/graphiti/pull/1183)
582
+ (@Milofax): FalkorDB RediSearch syntax errors from empty or unescaped fulltext queries. A new
583
+ `CrashSignal.QUERY_SANITIZATION_ERROR` recognizes both issues' verbatim filed error text.
584
+ - [#1467](https://github.com/getzep/graphiti/issues/1467) (@elimydlarz, open, zero engagement):
585
+ `GeminiEmbedder` silently returning the wrong vector count. A new
586
+ `CrashSignal.EMBEDDING_BATCH_COUNT_MISMATCH` catches this once Gemini embedder support is wired
587
+ into the self-hosted adapter.
588
+
589
+ **OpenViking**
590
+ - [#3029](https://github.com/volcengine/OpenViking/issues/3029) (@dfwgj, still open): Feishu resync
591
+ silently deleting user-managed files. memtrust had no way to observe this failure mode at all; a
592
+ dedicated resource-sync-safety eval now seeds generated and user files, triggers a resync, and
593
+ checks what survives.
594
+ - [#2850](https://github.com/volcengine/OpenViking/issues/2850) (@lg320531124, still open): BM25
595
+ search silently returning empty results at scale. A dedicated scale/volume-stress eval
596
+ (`memtrust run --eval scale_stress`) now stores a large synthetic corpus and re-queries it at
597
+ ascending checkpoints to reproduce the *shape* of this condition -- recall collapsing past a
598
+ volume threshold with no exception raised.
599
+ - [#1581](https://github.com/volcengine/OpenViking/issues/1581) (@0xble, fix rejected, still live
600
+ upstream): `v2_lock_max_retries=0` silently means unlimited retries, not zero. A new
601
+ lock-contention eval asserts a bounded response-time budget under concurrent-write contention.
602
+ - [#1255](https://github.com/volcengine/OpenViking/issues/1255) (@SeeYangZhi): a stats endpoint
603
+ silently returning zero despite persisted memories. A new `get_stats()`/`StatsResult` primitive
604
+ and dedicated stats-accuracy eval now catch this.
605
+ - [#2966](https://github.com/volcengine/OpenViking/issues/2966) (@lRoccoon, unaddressed upstream):
606
+ legacy uint16-truncated records that are permanently undeletable. A new
607
+ `CrashSignal.LEGACY_CORRUPT_RECORD_UNDELETABLE` now surfaces this instead of a silent no-op.
608
+ - [#204](https://github.com/volcengine/OpenViking/issues/204) (@ponsde, closed): non-deterministic
609
+ search results (Jaccard similarity 0.11 across identical queries) from a self-diagnosed dimension
610
+ mismatch. A new result-consistency eval computes pairwise Jaccard similarity over repeated
611
+ identical queries to catch this class directly.
612
+
613
+ **Cross-project**
614
+ - [OneNomad-LLC/przm-bench](https://github.com/OneNomad-LLC/przm-bench) (@mattstvartak): a peer
615
+ benchmarking project shipped cryptographic receipt signing; memtrust had none. `memtrust` now has
616
+ real Ed25519 signing/verification (the `cryptography` library, not a hand-rolled scheme) via
617
+ `memtrust keygen` / `run --sign` / `verify` -- a tampered receipt correctly fails verification,
618
+ a genuine one correctly passes.
619
+
620
+ Several PARTIAL and FAIL -- capability gap rows above and elsewhere in the full 197-row set remain
621
+ open, deliberately not counted as fixed: some point at real gaps this harness genuinely can't close
622
+ yet without a live vendor credential, and inflating a near-miss to PASS defeats the entire point of
623
+ an independently-verified benchmark. See the confidence caveats throughout this README and in
624
+ `docs/methodology.md` for exactly which claims rest on which kind of evidence.