Skip to content
yehiahmadPublic

About

Mem++: Non-Destructive Memory for Long-Term Organizational LLM Agents

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Mem++

Non-Destructive Memory for Long-Term Organizational LLM Agents

Paper · Quickstart · How it works · Usage · Configuration · Evaluation · Results · Citation

Python 3.13+ PostgreSQL 16 with pgvector Zero LLM calls at ingest Append-only memory Paper: ICLR 2027, under review


Introduction

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 a single narrator's facts at ingest and loses the originals; organizational memory keeps every record from many authors and lets the question do the choosing
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.

Key features

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.

How it works

Mem++ architecture in three stages: memory building and storing, memory activation via multi-index retrieval, and dated evidence presentation

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.

Quickstart

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 openai

Docker

docker compose up -d db
export MEMORY_DATABASE_URL=postgresql+psycopg2://memory:memory@localhost:5433/memory_research
uv run alembic upgrade head

The engine reads MEMORY_DATABASE_URL from the environment; it does not load .env on its own. .env.example documents every connection option.

Docker-free (macOS)

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.

Optional extras

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.

Basic usage

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.

Asking the record as of a past date

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

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).

Demo

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.

Configuration

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.

Per-benchmark settings

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=onnx
MEMORY_RRF_W_VECTOR=4
MEMORY_CANDIDATE_LIMIT=50
MEMORY_LEXICAL_OR=0
MEMORY_EMBEDDING_BACKEND=openai
defaults otherwise
MEMORY_EMBEDDING_BACKEND=openai
MEMORY_RRF_W_VECTOR=4
MEMORY_CANDIDATE_LIMIT=50
MEMORY_LEXICAL_OR=0

Evaluation

OrgMemBench: an organizational memory benchmark of 443 artifacts over 18 months, with 73 questions across six capabilities

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.

Results

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.

OrgMemBench

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

LoCoMo

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

MethodTemporal ReasoningOpen DomainMulti-HopSingle-HopAverage
LLMF1BLEULLMF1BLEULLMF1BLEULLMF1BLEULLMF1BLEU
Full Context74.247.540.056.628.422.277.244.233.786.961.453.480.653.345.0
RAG-204866.850.340.148.628.321.767.439.128.982.857.748.174.550.941.3
RAG-409627.422.319.128.817.913.931.720.112.835.925.822.032.923.519.2
Zep60.223.920.043.824.219.353.730.520.466.945.540.061.636.930.9
Mem056.939.233.247.923.717.768.240.130.371.448.642.066.343.536.5
A-Mem66.740.333.737.513.412.755.730.420.064.045.039.861.439.433.2
Nemori77.657.750.251.025.819.375.141.731.984.958.851.579.453.445.6
Mem++81.441.733.553.029.022.174.841.932.087.062.154.881.552.144.2
Memg++81.141.833.650.527.922.871.841.232.086.462.055.080.451.944.3

Answerer gpt-4o-mini

MethodTemporal ReasoningOpen DomainMulti-HopSingle-HopAverage
LLMF1BLEULLMF1BLEULLMF1BLEULLMF1BLEULLMF1BLEU
Full Context56.244.136.148.624.517.266.835.426.183.053.144.772.346.237.8
RAG-204862.647.538.245.223.418.163.035.724.678.553.643.070.347.237.1
RAG-409622.818.415.235.517.615.631.118.611.733.023.919.230.721.416.8
Zep58.944.838.139.622.915.750.527.519.363.239.733.758.537.530.9
Mem050.444.437.640.627.119.460.334.325.268.144.437.761.341.534.2
A-Mem54.238.133.822.99.08.643.624.018.858.235.629.252.532.427.0
Nemori71.056.746.644.820.815.165.336.525.682.154.443.274.449.538.5
Mem++76.956.946.547.324.217.868.539.027.584.059.747.977.453.142.0
Memg++75.655.344.546.224.618.865.837.225.384.158.947.376.752.141.0

LongMemEvalS

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

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)

Retrieval depth k on OrgMemBench

OrgMemBench score of Mem++ against the number of retrieved rows k, for gpt-4o-mini and gpt-4.1-mini answerers, with Full Context as a dashed baseline

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

Project structure

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

Testing

uv sync --extra test
uv run pytest

End-to-end tests expect a migrated PostgreSQL reachable through MEMORY_DATABASE_URL.

Citation

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}
}

License

Apache 2.0 — see the LICENSE file for details.

About

Mem++: Non-Destructive Memory for Long-Term Organizational LLM Agents

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages