Non-Destructive Memory for Long-Term Organizational LLM Agents
Paper · Quickstart · How it works · Usage · Configuration · Evaluation · Results · Citation
Mem++ is a memory engine for LLM agents that work over an organization's record: the Slack threads, emails, meeting notes, tickets and documents that many people write over months and years, and that revise, supersede and contradict one another.
Most agent-memory systems are designed for conversational memory, where one user narrates their own facts to an assistant. They distil each exchange into a handful of facts at write time, and whatever the extractor did not keep is gone for good. That trade works when a single narrator updates their own story. It fails when many authors write over each other, because the questions an organization actually asks need the original records rather than a summary of them: what did we believe on 12 March, what did this decision replace, and which document authorized it?
Mem++ takes the opposite trade. It keeps every artifact verbatim and dated, spends no LLM calls when writing, and does its understanding at read time. A question is answered through a date filter, three retrieval indexes fused by weighted rank, a few slots reserved for the most recent evidence, and a dated, attributed evidence block handed to the answering model. Nothing is overwritten, so every version stays retrievable.
Conversational memory distils one narrator's facts at write time. Organizational memory has to keep every author's record, because the question decides what matters.
Core capabilities
- Non-destructive by design. The store is append-only. Superseded and conflicting records are marked, never deleted, so any earlier state of the record can be replayed.
- Zero LLM calls at ingest. The verbatim write path embeds and indexes each record without calling a language model, so ingest cost does not grow with model pricing.
- As-of queries. An event-time predicate,
occurred_at ≤ θ, sits in the base query, so a question can be asked of the record exactly as it stood on any past date. - Multi-index retrieval. Lexical full-text search, dense vectors and entity tags each rank the candidates independently, and weighted Reciprocal Rank Fusion combines them.
- Recency slots. A few of the top-k slots are reserved for the latest-dated records in scope, so the newest version of a fact is always in view next to the most relevant ones.
- Dated, attributed evidence. Every retrieved record reaches the answering model with its date and its contributor.
- Optional consolidation. Near-duplicate records can be grouped and labelled as a conflict, a restatement or distinct. Originals are archived, not removed.
- Pluggable embeddings. A local, CPU-only ONNX MiniLM-384 embedder by default, or OpenAI
text-embedding-3-small.
Where it fits
- Organizational assistants that answer from Slack, email, meetings, tickets and documents.
- Audit and compliance, where the question is what was on record at a given date.
- Decision provenance: who decided, when, which alternatives were weighed, and what the decision replaced.
- Incident review: how the understanding of a root cause changed over time.
Mem++ runs in three stages.
1. Memory building and storing. Exact repeats within a scope are skipped. Every other
artifact becomes one memory record holding its full text, contributor, event date and
embedding. Records land in an append-only store and are indexed three ways: lexically
(PostgreSQL full-text search ranked by ts_rank_cd), by vector (pgvector) and by entity tag.
2. Memory activation via multi-index retrieval. A query arrives with an optional as-of
date θ. The date filter restricts the pool to active records with t ≤ θ. The lexical, vector
and tag indexes each return their own ranking, and weighted Reciprocal Rank Fusion combines
the three. The final shortlist of k records takes k − 3 by fused rank and reserves 3 slots
for the latest-dated records.
3. Dated evidence presentation. The shortlist is rendered as a dated, attributed evidence block, one entry per record, and passed to the answering model.
The boxes in the figure follow one convention:
| Box | Meaning |
|---|---|
| Solid | Core Mem++. No language-model call anywhere on the write path. |
| Dotted | Optional consolidation. Groups near-duplicates and labels what changed; originals are archived. |
| Dashed | The Memg++ variant, which adds an entity graph built at write time and appends up to five graph triples after the evidence rows. |
| LLM | A language-model call. |
Note
This repository contains the core Mem++ engine and the optional consolidation pass. The Memg++ entity-graph variant is a research extension and is not packaged in this release.
| Docker | Docker-free (macOS) | |
|---|---|---|
| Best for | Any OS, running the engine and the evaluation harness | A self-contained local demo |
| Database | PostgreSQL 16 + pgvector container on port 5433 | Project-local PostgreSQL 17 cluster over a unix socket |
| Start | docker compose up -d db |
./run_demo.sh |
Both paths use uv and Python 3.13 or newer. No GPU is needed.
git clone https://github.com/yehiahmad/mempp.git
cd mempp
uv sync --extra llm --extra openaidocker compose up -d db
export MEMORY_DATABASE_URL=postgresql+psycopg2://memory:memory@localhost:5433/memory_research
uv run alembic upgrade headThe engine reads MEMORY_DATABASE_URL from the environment; it does not load .env on its
own. .env.example documents every connection option.
brew install postgresql@17 pgvector
uv sync --extra chat
./run_demo.sh # create the local cluster, migrate, and launch the demo on http://127.0.0.1:7860
./run_demo.sh stop # stop the cluster; data is kept
./run_demo.sh reset # wipe the local database (asks first)Install postgresql@17 specifically, since that Homebrew formula is the one that ships
pgvector. Keep the checkout at a short path: the cluster listens on a unix socket inside the
project, and operating systems cap socket path length.
| Extra | Adds | Needed for |
|---|---|---|
llm |
Anthropic SDK | LLM fact extraction (store_facts), consolidation, Claude answer and judge models |
openai |
OpenAI SDK | The text-embedding-3-small backend, GPT answer and judge models |
chat |
Gradio | The local demo |
test |
pytest | The test suite |
Set ANTHROPIC_API_KEY and OPENAI_API_KEY in your environment for the extras you use. The
core write and read paths need neither.
Write records verbatim, then recall them. The first call downloads the ONNX MiniLM model.
from memory import store_facts_verbatim, recall_facts
# Write: each record is stored as-is with its event date. No LLM call is made.
store_facts_verbatim("ops-agent", [
{"summary": "VPN runs on Vendor A.", "tags": ["vpn", "vendor-a"], "occurred_at": "2023-12-28"},
{"summary": "We keep Vendor A this year.", "tags": ["vpn", "vendor-a"], "occurred_at": "2024-01-09"},
{"summary": "Board approved Vendor B.", "tags": ["vpn", "vendor-b"], "occurred_at": "2025-03-27"},
{"summary": "Vendor B is now live.", "tags": ["vpn", "vendor-b"], "occurred_at": "2025-09-09"},
])
# Read: date filter, three indexes, weighted RRF and recency slots.
for row in recall_facts("ops-agent", "Which VPN vendor do we use?", k=10):
print(row["occurred_at"], row["summary"], row.get("by"))Each fact may be a dict with summary, tags and occurred_at, a
(summary, tags, occurred_at) tuple, or a bare string. recall_facts returns plain dicts with
summary, occurred_at, hit_count and, when known, by, the record's contributors.
The store-level API exposes the as-of predicate directly.
import uuid
from datetime import datetime, timezone
from memory.db import get_session
from memory.store import PostgresMemoryStore
user, project = uuid.uuid4(), uuid.uuid4()
store = PostgresMemoryStore(get_session())
store.write(user_id=user, agent_type="ops-agent", project_id=project,
content="Board approved Vendor B.", tags=["vpn"],
occurred_at=datetime(2025, 3, 27, tzinfo=timezone.utc))
# Only records dated on or before 1 June 2024 are eligible, so Vendor B is not yet on record.
as_of = store.recall(user_id=user, agent_type="ops-agent", project_id=project,
query="Which VPN vendor do we use?", k=10,
occurred_before=datetime(2024, 6, 1, tzinfo=timezone.utc))Consolidation is manual and on demand; nothing runs in the background. It needs the llm
extra and ANTHROPIC_API_KEY.
from memory.consolidation import consolidate
report = consolidate("ops-agent", threshold=0.85)
# {"run_id", "pools_merged", "memories_before", "memories_after", "superseded"}Records whose embeddings reach a cosine similarity of at least threshold are grouped. A
language model labels each group as a conflict (the older version is marked replaced), a
restatement (merged, with the originals archived) or distinct (left untouched).
The Gradio demo runs the engine end to end on your machine.
uv sync --extra chat
./run_demo.sh # Docker-free, or with MEMORY_DATABASE_URL already set:
uv run python -m chat.app # http://127.0.0.1:7860| Tab | What it does |
|---|---|
| Chat | Talk to an assistant that writes to and recalls from Mem++ on every turn. |
| Memory | Browse and search stored records, read-only, by keyword or meaning, with filters. |
| Evaluation | Run a benchmark against the real engine from the browser. |
| System | The architecture diagram, a system summary and the consolidation lifecycle. |
Every setting is an environment variable read by memory/config.py.
| Variable | Default | Purpose |
|---|---|---|
MEMORY_DATABASE_URL |
(required) | SQLAlchemy DSN for PostgreSQL with pgvector. |
MEMORY_EMBEDDING_BACKEND |
onnx |
onnx for the local MiniLM-384 embedder, openai for the OpenAI API. |
MEMORY_EMBEDDING_MODEL |
text-embedding-3-small |
OpenAI embedding model, used when the backend is openai. |
MEMORY_EMBEDDING_DIM |
1536 |
Dimension of the OpenAI embedding. |
MEMORY_EMBEDDINGS_ENABLED |
1 |
Turn the vector index and write-time embedding on or off. |
MEMORY_RRF_W_FUZZY |
1.0 |
Fusion weight of the lexical index. |
MEMORY_RRF_W_TAG |
1.0 |
Fusion weight of the entity-tag index. |
MEMORY_RRF_W_VECTOR |
2.0 |
Fusion weight of the vector index. |
MEMORY_CANDIDATE_LIMIT |
200 |
Candidates each index may contribute before fusion. |
MEMORY_LEXICAL_OR |
1 |
1 matches any query term (disjunctive); 0 requires all terms (conjunctive). |
MEMORY_USER |
OS login | Overrides the login that scopes a user's memories. |
Fixed constants: Reciprocal Rank Fusion smoothing of 60, 3 recency slots, a 7-day recency
half-life, and a default k of 24 for recall_facts.
The paper fixes k = 50 everywhere and varies only these settings across benchmarks (Table 6 of the paper). The last row gives the matching environment variables.
| Setting | OrgMemBench | LoCoMo | LongMemEvalS |
|---|---|---|---|
| Embedding model | all-MiniLM-L6-v2 | text-embedding-3-small | text-embedding-3-small |
| Embedding size | 384 | 1536 | 1536 |
| Weights (wlex, wtag, wvec) | (1, 1, 4) | (1, 1, 2) | (1, 1, 4) |
| Candidates per index C | 50 | 200 | 50 |
| Lexical matching | conjunctive | disjunctive | conjunctive |
| Environment | MEMORY_EMBEDDING_BACKEND=onnxMEMORY_RRF_W_VECTOR=4MEMORY_CANDIDATE_LIMIT=50MEMORY_LEXICAL_OR=0 |
MEMORY_EMBEDDING_BACKEND=openaidefaults otherwise |
MEMORY_EMBEDDING_BACKEND=openaiMEMORY_RRF_W_VECTOR=4MEMORY_CANDIDATE_LIMIT=50MEMORY_LEXICAL_OR=0 |
Mem++ is evaluated on OrgMemBench, a benchmark for organizational memory: 443 artifacts in 157 threads spanning 18 months, and 73 questions across six capabilities.
| Code | Capability | What it tests |
|---|---|---|
| C1 | Supersession | The current value of a fact, what it replaced, when and why. |
| C2 | Decision provenance | Who decided, when, which alternatives were weighed, and the deciding rationale. |
| C3 | Bi-temporal (as-of) | What the organization believed as of a past date, as distinct from now. |
| C4 | Audit replay | Reconstruct the knowledge state at a past date and flag what has since changed. |
| C5 | Justification chain | The evidence behind a conclusion, each item classed as direct testimony or inference. |
| C6 | Contradiction | Detect that two artifacts conflict, report both sides, and whether it was resolved. |
OrgMemBench is distributed separately from this repository.
Question counts per category for the three benchmarks used in the paper (Table 5 of the paper):
| OrgMemBench (medium) | Count | LoCoMo | Count | LongMemEvalS | Count |
|---|---|---|---|---|---|
| C1 Supersession | 15 | Single-hop | 841 | Knowledge update | 72 |
| C2 Provenance | 15 | Multi-hop | 282 | Multi-session | 121 |
| C3 Bi-temporal | 7 | Temporal | 321 | Temporal reasoning | 127 |
| C4 Audit replay | 15 | Open domain | 96 | Single-session user | 64 |
| C5 Justification | 15 | – | – | Single-session assistant | 56 |
| C6 Contradiction | 6 | – | – | Single-session preference | 30 |
| Total | 73 | Total | 1,540 | Total | 470 |
The harness in eval/ also runs the conversational memory benchmarks LoCoMo,
LongMemEval, LongBench and MemBench. Benchmark data is never committed; fetch it
first.
uv run python datasets/fetch.py locomo # or: longmemeval | longbench | membench | all
# Smoke test with no database and no API key
uv run python -m eval.run_locomo --stub --limit 1
# Full run: Mem++ at k=50, GPT answerer, GPT judge
mkdir -p runs
uv run python -m eval.run_locomo --k 50 \
--answer-model gpt-4o-mini --judge-model gpt-4o-mini \
--workers 4 --out runs/locomo_k50.json| Flag | Effect |
|---|---|
--k |
Records recalled per question. |
--answer-model, --judge-model |
haiku, sonnet, opus, or any raw model id; ids starting with gpt route to OpenAI. |
--recall-only |
Retrieval metrics only, with no answer or judge calls. |
--fraction, --limit |
Evaluate a deterministic fraction of questions, or cap the number of conversations. |
--reuse-ingest |
Skip ingest for scopes that already hold memories. |
--stub |
In-memory backend with echo answer and judge, for wiring checks. |
eval.run_longmemeval, eval.run_longbench and eval.run_membench take the same flags. The
paper's results are summarized in the next section.
All numbers in this section are as reported in the paper (paper/mempp_iclr2027_submission.pdf, Tables 1 to 4 and 7, Figure 4). Scores are LLM-judge scores from a gpt-4o-mini judge on a 0 to 100 scale. Within each answerer block, bold marks the best result and underline the second best, as in the paper.
Performance by question type. Overall is weighted by the number of questions in each category and reported with its standard deviation across runs.
Answerer gpt-4.1-mini
| Method | Supersession | Decision Provenance | Bi-temporal | Audit Replay | Justification Chain | Contradiction | Overall |
|---|---|---|---|---|---|---|---|
| Full Context | 0.0 | 27.5 | 0.0 | 33.0 | 22.7 | 8.3 | 17.8 ± 0.1 |
| RAG | 66.1 | 79.7 | 66.7 | 40.9 | 19.6 | 75.4 | 55.0 ± 0.3 |
| Zep | 31.4 | 42.5 | 23.0 | 76.8 | 20.7 | 43.3 | 41.0 ± 0.2 |
| Mem0 | 40.8 | 55.0 | 50.0 | 25.1 | 14.4 | 52.8 | 36.9 ± 0.0 |
| A-Mem | 58.3 | 63.3 | 43.6 | 36.5 | 20.7 | 43.5 | 44.5 ± 0.3 |
| gbrain | 54.8 | 60.7 | 30.3 | 48.5 | 22.7 | 22.9 | 43.2 ± 0.1 |
| Mem++ | 67.7 | 65.9 | 50.0 | 83.0 | 21.5 | 47.2 | 57.6 ± 0.1 |
| Memg++ | 67.2 | 63.9 | 50.6 | 79.7 | 19.8 | 44.4 | 55.9 ± 0.1 |
Answerer gpt-4o-mini
| Method | Supersession | Decision Provenance | Bi-temporal | Audit Replay | Justification Chain | Contradiction | Overall |
|---|---|---|---|---|---|---|---|
| Full Context | 2.5 | 21.1 | 0.0 | 35.7 | 17.0 | 13.9 | 16.8 ± 0.1 |
| RAG | 56.7 | 47.8 | 30.2 | 55.1 | 10.4 | 67.2 | 43.4 ± 0.2 |
| Zep | 27.5 | 38.3 | 34.9 | 74.9 | 7.4 | 29.6 | 36.2 ± 0.3 |
| Mem0 | 32.3 | 47.2 | 33.3 | 32.4 | 11.8 | 62.8 | 33.8 ± 0.1 |
| A-Mem | 42.8 | 60.0 | 28.6 | 21.3 | 4.4 | 41.7 | 32.6 ± 0.3 |
| gbrain | 47.8 | 46.1 | 25.9 | 46.6 | 7.7 | 21.9 | 34.7 ± 0.2 |
| Mem++ | 57.0 | 49.2 | 33.3 | 70.7 | 8.9 | 39.8 | 44.2 ± 0.1 |
| Memg++ | 56.7 | 48.6 | 32.1 | 71.9 | 8.8 | 37.7 | 44.4 ± 0.1 |
Performance by question type, with LLM-judge score, F1 and BLEU-1. The baseline numbers come from Nan et al. (2025).
Answerer gpt-4.1-mini
| Method | Temporal Reasoning | Open Domain | Multi-Hop | Single-Hop | Average | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| LLM | F1 | BLEU | LLM | F1 | BLEU | LLM | F1 | BLEU | LLM | F1 | BLEU | LLM | F1 | BLEU | |
| Full Context | 74.2 | 47.5 | 40.0 | 56.6 | 28.4 | 22.2 | 77.2 | 44.2 | 33.7 | 86.9 | 61.4 | 53.4 | 80.6 | 53.3 | 45.0 |
| RAG-2048 | 66.8 | 50.3 | 40.1 | 48.6 | 28.3 | 21.7 | 67.4 | 39.1 | 28.9 | 82.8 | 57.7 | 48.1 | 74.5 | 50.9 | 41.3 |
| RAG-4096 | 27.4 | 22.3 | 19.1 | 28.8 | 17.9 | 13.9 | 31.7 | 20.1 | 12.8 | 35.9 | 25.8 | 22.0 | 32.9 | 23.5 | 19.2 |
| Zep | 60.2 | 23.9 | 20.0 | 43.8 | 24.2 | 19.3 | 53.7 | 30.5 | 20.4 | 66.9 | 45.5 | 40.0 | 61.6 | 36.9 | 30.9 |
| Mem0 | 56.9 | 39.2 | 33.2 | 47.9 | 23.7 | 17.7 | 68.2 | 40.1 | 30.3 | 71.4 | 48.6 | 42.0 | 66.3 | 43.5 | 36.5 |
| A-Mem | 66.7 | 40.3 | 33.7 | 37.5 | 13.4 | 12.7 | 55.7 | 30.4 | 20.0 | 64.0 | 45.0 | 39.8 | 61.4 | 39.4 | 33.2 |
| Nemori | 77.6 | 57.7 | 50.2 | 51.0 | 25.8 | 19.3 | 75.1 | 41.7 | 31.9 | 84.9 | 58.8 | 51.5 | 79.4 | 53.4 | 45.6 |
| Mem++ | 81.4 | 41.7 | 33.5 | 53.0 | 29.0 | 22.1 | 74.8 | 41.9 | 32.0 | 87.0 | 62.1 | 54.8 | 81.5 | 52.1 | 44.2 |
| Memg++ | 81.1 | 41.8 | 33.6 | 50.5 | 27.9 | 22.8 | 71.8 | 41.2 | 32.0 | 86.4 | 62.0 | 55.0 | 80.4 | 51.9 | 44.3 |
Answerer gpt-4o-mini
| Method | Temporal Reasoning | Open Domain | Multi-Hop | Single-Hop | Average | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| LLM | F1 | BLEU | LLM | F1 | BLEU | LLM | F1 | BLEU | LLM | F1 | BLEU | LLM | F1 | BLEU | |
| Full Context | 56.2 | 44.1 | 36.1 | 48.6 | 24.5 | 17.2 | 66.8 | 35.4 | 26.1 | 83.0 | 53.1 | 44.7 | 72.3 | 46.2 | 37.8 |
| RAG-2048 | 62.6 | 47.5 | 38.2 | 45.2 | 23.4 | 18.1 | 63.0 | 35.7 | 24.6 | 78.5 | 53.6 | 43.0 | 70.3 | 47.2 | 37.1 |
| RAG-4096 | 22.8 | 18.4 | 15.2 | 35.5 | 17.6 | 15.6 | 31.1 | 18.6 | 11.7 | 33.0 | 23.9 | 19.2 | 30.7 | 21.4 | 16.8 |
| Zep | 58.9 | 44.8 | 38.1 | 39.6 | 22.9 | 15.7 | 50.5 | 27.5 | 19.3 | 63.2 | 39.7 | 33.7 | 58.5 | 37.5 | 30.9 |
| Mem0 | 50.4 | 44.4 | 37.6 | 40.6 | 27.1 | 19.4 | 60.3 | 34.3 | 25.2 | 68.1 | 44.4 | 37.7 | 61.3 | 41.5 | 34.2 |
| A-Mem | 54.2 | 38.1 | 33.8 | 22.9 | 9.0 | 8.6 | 43.6 | 24.0 | 18.8 | 58.2 | 35.6 | 29.2 | 52.5 | 32.4 | 27.0 |
| Nemori | 71.0 | 56.7 | 46.6 | 44.8 | 20.8 | 15.1 | 65.3 | 36.5 | 25.6 | 82.1 | 54.4 | 43.2 | 74.4 | 49.5 | 38.5 |
| Mem++ | 76.9 | 56.9 | 46.5 | 47.3 | 24.2 | 17.8 | 68.5 | 39.0 | 27.5 | 84.0 | 59.7 | 47.9 | 77.4 | 53.1 | 42.0 |
| Memg++ | 75.6 | 55.3 | 44.5 | 46.2 | 24.6 | 18.8 | 65.8 | 37.2 | 25.3 | 84.1 | 58.9 | 47.3 | 76.7 | 52.1 | 41.0 |
LLM-judge accuracy by question type.
Answerer gpt-4o-mini
| Question type | Full Context | Zep | Nemori | Mem++ | Memg++ |
|---|---|---|---|---|---|
| Single-session preference | 6.7 | 20.0 | 46.7 | 46.7 | 50.0 |
| Single-session assistant | 89.3 | 80.4 | 83.9 | 94.6 | 96.4 |
| Temporal reasoning | 42.1 | 62.4 | 61.7 | 56.7 | 56.7 |
| Multi-session | 38.3 | 57.9 | 51.1 | 63.4 | 62.0 |
| Knowledge update | 78.2 | 83.3 | 61.5 | 84.3 | 85.2 |
| Single-session user | 78.6 | 92.9 | 88.6 | 98.4 | 98.4 |
| Average | 55.0 | 68.0 | 64.2 | 72.2 | 72.4 |
Answerer gpt-4.1-mini
| Question type | Full Context | Zep | Nemori | Mem++ | Memg++ |
|---|---|---|---|---|---|
| Single-session preference | 16.7 | 22.5 | 86.7 | 47.8 | 53.3 |
| Single-session assistant | 98.2 | 83.1 | 92.9 | 94.6 | 94.6 |
| Temporal reasoning | 60.2 | 64.5 | 72.2 | 68.5 | 69.0 |
| Multi-session | 51.1 | 57.6 | 55.6 | 56.1 | 56.8 |
| Knowledge update | 76.9 | 83.1 | 79.5 | 89.9 | 89.9 |
| Single-session user | 85.7 | 96.3 | 90.0 | 100.0 | 100.0 |
| Average | 65.6 | 69.4 | 74.6 | 74.7 | 75.3 |
Ablation with claude-sonnet-4-6 as the answerer and the same gpt-4o-mini judge. Parentheses give the change from Mem++.
| Variant | OrgMemBench | LoCoMo | LongMemEvalS |
|---|---|---|---|
| Mem++ | 57.0 | 85.7 | 87.9 |
| w/o vector leg | 21.3 (−35.7) | 76.7 (−9.1) | 11.9 (−76.0) |
| w/o lexical leg | 57.2 (+0.2) | 85.2 (−0.5) | 88.7 (+0.7) |
| w/o tag leg | 55.0 (−2.0) | – | – |
| w/ consolidation | 55.6 (−1.3) | 85.4 (−0.3) | 86.9 (−1.1) |
| w/ fact index | 55.2 (−1.7) | 83.1 (−2.6) | – |
| Memg++ | 55.3 (−1.7) | 84.8 (−0.9) | 86.0 (−2.0) |
OrgMemBench score of Mem++ as the number of retrieved rows k varies. The dashed line is Full Context, and the annotated point is k = 50, the setting used in the main experiments. Retrieved tokens are summed over all questions and counted before the 40,000-character cut.
| k | gpt-4o-mini | gpt-4.1-mini | Retrieved tokens |
|---|---|---|---|
| 5 | 36.57 | 43.99 | 0.20M |
| 10 | 45.10 | 50.56 | 0.41M |
| 15 | 47.16 | 54.44 | 0.61M |
| 30 | 45.94 | 56.49 | 1.22M |
| 50 | 44.23 | 57.00 | 2.01M |
| 70 | 48.67 | 56.70 | 2.77M |
| 100 | 44.89 | 56.54 | 3.63M |
| Full Context | 16.85 | 21.52 | 18.68M |
mempp/
├── memory/ the engine
│ ├── store.py write path, multi-index recall, weighted RRF, recency slots
│ ├── recall.py public API: store_facts, store_facts_verbatim, recall_facts
│ ├── consolidation.py optional near-duplicate grouping and conflict labelling
│ ├── extraction.py optional LLM fact extraction
│ ├── embeddings/ ONNX MiniLM-384 and OpenAI backends
│ ├── models.py SQLAlchemy schema
│ ├── mutate.py supersede and merge operations
│ ├── browse.py read-only inspection
│ └── config.py every environment setting
├── eval/ evaluation harness, benchmark adapters, answer and judge models
├── chat/ Gradio demo
├── alembic/ database migrations
├── datasets/fetch.py benchmark downloader
├── tests/, tests_engine/ test suites
├── docs/figures/ figures used in this README
├── paper/ the paper (ICLR 2027 submission, under review)
├── docker-compose.yml PostgreSQL 16 + pgvector
└── run_demo.sh Docker-free local cluster and demo
uv sync --extra test
uv run pytestEnd-to-end tests expect a migrated PostgreSQL reachable through MEMORY_DATABASE_URL.
If you use Mem++ or OrgMemBench, please cite:
@misc{mempp2026,
title = {Mem++: Non-Destructive Memory for Long-Term Organizational LLM Agents},
author = {Anonymous},
year = {2026},
note = {Under review at ICLR 2027}
}Apache 2.0 — see the LICENSE file for details.


