Benchmark for LLM memory decay โ measure how AI memory architectures forget across long conversations
Project description
๐ญ MemoryLens
Measure how your AI's memory forgets
An open-source benchmark for LLM memory decay โ 8 memory architectures, 6 metrics, 4 domain scenarios, statistical rigor, zero API keys required.
Install ยท Quick Start ยท Results ยท How It Works ยท Contributing
The Problem
Every LLM application that runs multi-turn conversations has a memory strategy โ usually "keep everything in context and hope." Almost nobody measures what that strategy actually remembers after 50, 100, or 200 turns.
MemoryLens answers three questions:
- How much does an AI still recall after N conversation turns โ and at what token cost?
- Which memory architecture (full history, RAG, tiered compression, entity store, knowledge graphโฆ) retains facts most efficiently?
- When a user updates a fact ("I moved to Mumbai"), does the memory surface the new value, the stale one โ or contradict itself with both?
Every core metric is content-based and deterministic: no API key, fully reproducible. Add any LLM key and a two-stage answer+judge pipeline measures what the model actually answers.
Install
pip install memorylens-bench
| Extra | Installs | For |
|---|---|---|
memorylens-bench[dashboard] |
streamlit, plotly, pandas | Interactive dashboard |
memorylens-bench[server] |
fastapi, uvicorn | REST API |
memorylens-bench[faiss] |
faiss-cpu | FAISS vector backend |
memorylens-bench[groq] |
groq | LLM evaluation via Groq |
memorylens-bench[openai] |
openai | LLM evaluation via OpenAI |
memorylens-bench[anthropic] |
anthropic | LLM evaluation via Anthropic |
memorylens-bench[all] |
everything above | โ |
Python 3.10โ3.13 ยท Linux, macOS, Windows (all tested in CI).
Quick Start
CLI โ results in under a minute, no API key
memorylens # 100-turn benchmark, 3 backends
memorylens --seeds 5 # multi-seed: mean ยฑ std across 5 personas
memorylens --scenario medical # domain scenarios: default | edtech | support | medical
memorylens --backends naive rag_chunked cascading graph
memorylens --seeds 5 --fit-curves # fit Ebbinghaus + exponential forgetting curves
memorylens --llm --provider groq # real answer+judge LLM evaluation
Python API
from memorylens import run_benchmark, results_to_display_dict
raw = run_benchmark(total_turns=100, backends=["naive", "rag", "cascading"])
results = results_to_display_dict(raw)
print(results["cascading"]["recall"]) # recall at each checkpoint
Dashboard and REST API
pip install "memorylens-bench[dashboard]"
streamlit run dashboard.py # decay curves, run history, cost projection
pip install "memorylens-bench[server]"
uvicorn memorylens.api:app # POST /v1/benchmarks โ job id โ poll results
Benchmark Results
All numbers below are reproduced from this exact code (v0.4.0) with:
memorylens --seeds 5 --backends naive rag rag_chunked cascading summary entity graph faiss
Recall@T โ 100 turns (mean ยฑ std, n=5 personas)
| Backend | T=10 | T=50 | T=100 | Tokens/query @ T=100 |
|---|---|---|---|---|
| naive (1,200-token budget) | 100% | 100% | 35.0 ยฑ 5.6% | 1,193 |
| rag | 100% | 100% | 100% | 67 |
| rag_chunked | 100% | 100% | 100% | 56 |
| cascading | 100% | 100% | 100% | 326 |
| summary (extractive) | 100% | 100% | 100% | 356 |
| entity | 100% | 100% | 100% | 63 |
| graph | 100% | 100% | 100% | 62 |
| faiss | 100% | 100% | 100% | 67 |
Read this before quoting numbers. At 100 turns with 8 templated facts, every retrieval- and extraction-based backend saturates โ the differentiation is token cost at equal recall (rag_chunked delivers the same recall as naive at ~1/21 the tokens) and naive's collapse once its context budget evicts early turns. Two structural caveats:
- entity / graph exploit the fact templates. Facts are injected as
"My X is Y", which the regex extractors match by construction. Their 100% shows structured stores never lose what they capture โ it says nothing about free-form conversation. Harder paraphrased injections are on the roadmap. - summary is extractive in zero-key mode โ it keeps fact-bearing lines verbatim, so substring recall is guaranteed. With an LLM key, compression becomes abstractive and honest decay appears.
Stress test โ 200 turns
At 200 turns the bounded systems saturate and real decay separates the field
(memorylens --turns 200 --checkpoints 10 50 100 150 200 --seeds 5 --backends ...):
| Backend | T=100 | T=150 | T=200 | Tokens/query @ T=200 |
|---|---|---|---|---|
| naive | 35.0 ยฑ 5.6% | 7.5 ยฑ 6.9% | 7.5 ยฑ 6.9% | 1,189 |
| rag_chunked (bounded index) | 100% | 17.5 ยฑ 6.9% | 5.0 ยฑ 6.9% | 70 |
| cascading | 100% | 100% | 100% | 326 |
| rag ยท summary ยท entity ยท graph ยท faiss | 100% | 100% | 100% | 62โ382 |
The two production-realistic constraints fail in opposite ways: naive's fixed token budget evicts old turns wholesale, while rag_chunked's bounded FIFO vector index (200 chunks) silently drops the earliest facts even though each query costs only 70 tokens. Cascading survives because its cold tier compresses fact-bearing lines instead of discarding them.
Temporal drift and contradiction after fact updates
Two facts update mid-conversation (city at T=40, age at T=60). At T=100:
| Backend | Drift (stale-only retrieval) | Contradiction (old + new surfaced) |
|---|---|---|
| naive | 0.0 | 1.0 โ full history keeps both values |
| cascading | 0.0 | 0.0 โ cold summaries patched in place |
| entity / graph | 0.0 | 0.0 โ updates overwrite in place |
Drift is a retrieval-layer proxy (worst-case bound). For behavioral measurement โ does the model answer with the stale value โ run
memorylens --llm.
How It Works
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ LAYER 1 โ SIMULATOR โ
โ Injects facts at known turns, fires domain filler queries in between โ
โ Facts can be updated mid-conversation to test drift + contradiction โ
โ โ
โ T=0 "My name is Arjun Sharma." โ
โ T=1 "My city is Bangalore." โ
โ T=40 "My city has changed to Mumbai." โ update event โ
โ Scenarios: default (tech Q&A) ยท edtech ยท support ยท medical โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ LAYER 2 โ MEMORY BACKENDS (8 implementations) โ
โ โ
โ naive Full history, evict oldest at a token budget โ
โ rag Embed every message, retrieve top-K (upper bound) โ
โ rag_chunked Chunked + bounded FIFO index (production-realistic) โ
โ cascading Hot/Warm/Cold tiers with Ebbinghaus temporal decay โ
โ summary Rolling compression (LLM or extractive) โ
โ entity Structured key-value fact store โ
โ graph NetworkX knowledge graph, in-place fact updates โ
โ faiss FAISS vector index (optional dependency) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ LAYER 3 โ EVALUATOR (6 metrics, dual mode) โ
โ โ
โ Content mode (no API key): deterministic substring checks โ
โ LLM mode (any provider): answer+judge pipeline โ did the LLM โ
โ actually answer correctly? โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The 6 Metrics
| Metric | What It Measures |
|---|---|
| Recall@T | Is the correct current fact value in retrieved context at turn T? |
| Precision@K | Of K retrieved chunks, how many contain a real fact? |
| Temporal Drift | After an update, what fraction of retrieval still carries the stale value? (worst-case proxy) |
| Contradiction | Does the context surface both old and new values at once, forcing the LLM to arbitrate? |
| Memory Noise Ratio | What fraction of retrieved context is irrelevant to any known fact? |
| Cascade Efficiency | Recall-per-token vs the naive baseline |
The 4 Temporal Decay Functions
The cascading backend's warm-tier scoring uses a pluggable forgetting curve โ compare them with --decay:
| Name | Formula | Reference |
|---|---|---|
ebbinghaus (default) |
e^{-t / (Sยทโ(1+t))} |
Ebbinghaus (1885) |
exponential |
e^{-kยทt/window} |
Jost (1897) |
linear |
1 โ t/window |
Wickelgren (1972) |
default |
max(0.2, 1 โ 0.6ยทt/w) |
Original heuristic |
--fit-curves fits both Ebbinghaus and exponential models to your measured Recall@T series and reports half-life, stability, and Rยฒ.
LLM Provider Support
All content metrics work with no API key. Add any one key for the LLM answer+judge pass:
| Provider | Key | Default Model | Free Tier |
|---|---|---|---|
| Groq | GROQ_API_KEY |
llama-3.1-8b-instant | โ |
| OpenAI | OPENAI_API_KEY |
gpt-4o-mini | โ |
| Anthropic | ANTHROPIC_API_KEY |
claude-haiku-4-5 | โ |
| OpenRouter | OPENROUTER_API_KEY |
llama-3.1-8b-instruct:free | โ |
| Ollama | (none โ local) | llama3.2 | โ |
memorylens --list-providers
memorylens --llm # auto-detect
memorylens --llm --provider groq # force one
How MemoryLens Compares
Most evaluation frameworks measure quality at a single point in time. MemoryLens measures how memory quality changes over conversation turns โ a dimension the tools below don't cover (they solve different problems, and compose well with this one):
| Framework | Focus | Decay over turns | Multi-architecture | No-API mode |
|---|---|---|---|---|
| MemoryLens | Memory decay | โ | โ 8 backends | โ |
| RAGAS | RAG answer quality | โ | โ | โ |
| TruLens | LLM app monitoring | โ | โ | โ |
| DeepEval | LLM answer quality | โ | โ | Partial |
| MemGPT / Letta | A memory system, not a benchmark | โ | โ | โ |
Details: docs/comparison-with-existing-tools.md
Project Structure
memorylens/ installable package
โโโ memory/ 8 backends + decay functions (add yours here)
โโโ simulator/ facts, conversation generator, personas, scenario registry
โโโ evaluation/ metrics, benchmark runner, stats, experiment logger
โโโ utils/ local embeddings, LLM providers, SQLite run storage
โโโ api.py FastAPI REST server
โโโ cli.py `memorylens` command
tests/ 39 integration tests, no API key needed
dashboard.py Streamlit dashboard (decay curves, run history, export)
docs/ guides: adding a backend, comparisons, methodology
Contributing
A new memory backend is 3 methods and one registry line โ the full guide with a worked example is in docs/adding-a-new-backend.md:
from memorylens.memory.base import BaseMemory
class YourMemory(BaseMemory):
name = "your_backend"
def add_message(self, role, content, turn): ...
def get_context(self, query, current_turn): ...
def reset(self): ...
New scenarios are a data file plus one registry entry. New metrics are plain functions.
git clone https://github.com/Neal006/memorylens && cd memorylens
pip install -e ".[server,dev]"
pytest tests -q # all green before you start
Start here: good first issue ยท Guide: CONTRIBUTING.md ยท Plans: ROADMAP.md
Citation
@software{memorylens2026,
author = {Daftary, Neal},
title = {{MemoryLens}: A Temporal Decay Benchmark for {LLM} Memory Architectures},
year = {2026},
url = {https://github.com/Neal006/memorylens},
version = {0.4.0}
}
License
MIT โ free to use, modify, and distribute.
If MemoryLens is useful to you, a โญ helps other researchers and developers find it.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file memorylens_bench-0.4.0.tar.gz.
File metadata
- Download URL: memorylens_bench-0.4.0.tar.gz
- Upload date:
- Size: 62.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
08051ba753a322415f8b3504eca440396267f6669b3c2c8077fd44e0055c6671
|
|
| MD5 |
9ffe25b626675baaabe96bbe041a3274
|
|
| BLAKE2b-256 |
b670a81cd05a74f6417c6d178df2a4ec5591a2439c960b3708cf6a3323246405
|
Provenance
The following attestation bundles were made for memorylens_bench-0.4.0.tar.gz:
Publisher:
release.yml on Neal006/memorylens
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
memorylens_bench-0.4.0.tar.gz -
Subject digest:
08051ba753a322415f8b3504eca440396267f6669b3c2c8077fd44e0055c6671 - Sigstore transparency entry: 2067125936
- Sigstore integration time:
-
Permalink:
Neal006/memorylens@2e60a442b0266d3d6ee60675036a3d6af2c5f8c0 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/Neal006
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2e60a442b0266d3d6ee60675036a3d6af2c5f8c0 -
Trigger Event:
push
-
Statement type:
File details
Details for the file memorylens_bench-0.4.0-py3-none-any.whl.
File metadata
- Download URL: memorylens_bench-0.4.0-py3-none-any.whl
- Upload date:
- Size: 61.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d7b26bf36afbe911babe401d43dab28ae50ff0d398080654b4d183a2a9d89c34
|
|
| MD5 |
45fd092bc6bbd3144fd3f51b163ad0f9
|
|
| BLAKE2b-256 |
85bf8df798b3e024a1a00d9010193aa0f5ba5bf6fde944b3f8a4fccc5f684aaf
|
Provenance
The following attestation bundles were made for memorylens_bench-0.4.0-py3-none-any.whl:
Publisher:
release.yml on Neal006/memorylens
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
memorylens_bench-0.4.0-py3-none-any.whl -
Subject digest:
d7b26bf36afbe911babe401d43dab28ae50ff0d398080654b4d183a2a9d89c34 - Sigstore transparency entry: 2067126101
- Sigstore integration time:
-
Permalink:
Neal006/memorylens@2e60a442b0266d3d6ee60675036a3d6af2c5f8c0 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/Neal006
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2e60a442b0266d3d6ee60675036a3d6af2c5f8c0 -
Trigger Event:
push
-
Statement type: