Most agent memory is asserted.
Ours was measured.

A Postgres extension that gives coding agents memory across sessions — and the only one that has been run against a control arm with the memory switched off.

>CREATE EXTENSION pgmnemo CASCADE;

Apache 2.0 · in the Postgres you already run · no sidecar, no data leaving your network, no model call on the write path.

0.20.0 — graph expansion for recall_hybrid(): causal-edge traversal and entity-key lookup can now add candidates the vector and text pools never saw, and every row says which pool it came from. Both are off by default: with the weights unset the output is byte-identical to 0.19.1. Whether switching them on retrieves better is being measured under a protocol registered before the data was collected — no number here until it finishes.

Task success rate by arm 30 days · n=435 runs
0% 25 50 75 memory off n=150 55.3% memory on n=148 66.9% selective n=137 55.5%

Read this before you quote the number. The on-versus-off comparison was not pre-registered — the pre-registered primary metric was cost, and on cost there is no effect at all (p=0.36). The sample is 298 runs against a pre-registered target of 400 per arm. χ²=4.19, p=0.041; under Bonferroni correction for three pairwise tests it rises to 0.12 and significance disappears. Treat this as a first signal, not a proven result.

Everyone publishes retrieval scores. Nobody turns the memory off.

The category measures itself with recall@k on LoCoMo and LongMemEval. Those benchmarks answer whether a retriever finds the right row. They do not answer the question a team actually has: does the agent do better work?

Answering that requires a control arm, and a control arm requires a fleet of agents doing real work that you are willing to deliberately handicap. We run one, so we ran the experiment: every agent run over 30 days was randomly assigned to one of three conditions and otherwise left alone. No task selection, no cherry-picked slice, no retrospective filtering.

The arms were balanced week over week, and the direction held in both high-volume weeks independently. The full protocol, the de-identified per-run dataset, and the script that regenerates every number below are published alongside the extension.

ArmRunsSuccessTurns (mean)Cost (mean)
Memory off 15055.3%34.39$1.4082
Memory always on 14866.9%27.81$1.2321
Selective recall 13755.5%34.49$1.1683

Success is task completion as judged by the same pipeline in all three arms. Turns is agent iterations to completion. Cost is USD of model spend per run. Differences in cost are not statistically significant in any pairing.

The part that failed is the useful part

We spent a quarter building selective recall on the premise that a memory which knows when to stay quiet beats one that always speaks. Our own experiment says it does not.

Selective recall performed as if there were no memory at all

The selective arm filtered retrieved context by relevance and type before injecting it — the behaviour that half this category sells as intelligence. It landed at 55.5% success against 55.3% for memory switched off entirely. Statistically the two are the same result (χ²=0.001, p=0.98), while plain always-on recall beat it (p=0.048).

The obvious explanation is the wrong one. Arm C was not starved of context — it injected more lessons per run than the winning arm (4.05 against 3.29), retrieved nothing at the same rate, at the same match quality. What separates them is the retrieval mechanism: the winning arm used hybrid vector plus BM25 with no gates; the failing one used vector-only recall behind a similarity threshold and a type filter. The lever is how memory is retrieved, not how much of it you hand over.

arm C ≈ arm A · p = 0.98 · more context retrieved, no benefit at all

We publish this for the same reason we publish the benchmarks we lose: a project that only reports its wins is asking to be taken on faith, and this is a category where nearly everything is currently taken on faith. Score-gated filtering stays off by default, is documented as measured-harmful, and leads our evidence document instead of hiding at the end of it.

It is a Postgres extension. That is the whole architecture.

Memory lives in the database you already run and back up. There is no service to deploy, no vendor to route your context through, and no model call on the write path.

01 — install

One statement

Postgres 15+ with pgvector. The extension creates its own schema and indexes; pg_dump is your backup story.

02 — wire

One command per agent

pgmnemo init claude|codex|gemini writes the session-capture and recall hooks. One store, shared across models.

03 — read

It is just SQL

Recall is a query with a plan you can EXPLAIN. When ranking surprises you, you can find out why without opening a support ticket.

-- session 1: an agent records what it learned
SELECT pgmnemo.ingest(
  'developer', 99, 'connection-pooling',
  'PgBouncer in transaction mode breaks prepared statements
   silently. Use session mode, or disable the statement cache.',
  4, NULL, 'a1b9f02');

-- session 2: a different agent, empty context, same wall
SELECT lesson_id, score, lesson_text
  FROM pgmnemo.recall_lessons(
    query_text := 'prepared statements broken with pgbouncer',
    role_filter := 'developer', k := 3);

Writes are checked by a Postgres constraint, not by a model — so a write costs nothing and cannot be talked out of its provenance requirement. Set the gate to enforce and an agent cannot record a lesson without a commit, ticket, or document hash behind it.

Four claims, and what would prove each one wrong

A claim you cannot falsify is marketing. Each of these has a test that anyone can run against the source.

Nothing leaves your network

Memory is stored and retrieved inside your own Postgres. There is no telemetry in the product — which also means we cannot count our own users, and we accept that trade.

Falsified if: the extension makes any outbound request after CREATE EXTENSION.

Zero model cost per write

Extraction and gating happen in SQL. Competitors that run an LLM on every write pay roughly $0.17–0.36 per thousand writes; the same volume here costs nothing beyond the disk.

Falsified if: a standard ingest() triggers any model inference.

Ranking you can audit

Hybrid vector, full-text, and JSONB filtering resolve in one query plan. No reranking service, no opaque score.

Falsified if: recall_hybrid() computes any part of its result outside Postgres.

We name our own defects

We shipped a feature that quietly matched our own schema names and would have returned nothing for anyone else. A confidentiality scan caught it, not a test. The fix — a required CI gate that installs into a deliberately foreign database — shipped in 0.17.0. Since then: a graph walk that ran on every recall and was then multiplied by a weight of zero (0.18.1), an upgrade path whose function bodies diverged from a fresh install (0.19.1, now gated by a body-hash check), and a latency claim we withdrew after paired benchmarks failed to reproduce it.

Falsified if: a defect of this class is found in our code and not disclosed here.

Where we lose

Published because the alternative is asking you to trust a vendor's own scorecard.

Standard benchmarks, our protocol published in full alongside the numbers.
BenchmarkpgmnemoComparisonVerdict
LongMemEval-S recall@100.9604BM25 = 0.982We lose by 2.2pp
LoCoMo turn-level recall@50.302DRAGON = 0.225We win by 7.7pp
Graph-augmented recallno gainhybrid aloneShips for lineage, not ranking
Selective recall (live fleet)55.5%no memory = 55.3%Failed; now off by default

A plain BM25 index still beats us on raw retrieval on one of the two standard benchmarks. We keep saying so because the number is real and because a memory layer is not chosen on recall@k alone — it is chosen on where the data lives, what a write costs, and whether you can explain the ranking to an auditor.

If raw retrieval on a static corpus is your whole problem, use BM25. It is free and it is very good.

Try it against your own agents

If you already run Postgres and already run coding agents that keep relearning the same thing, the cost of finding out is one statement and one command.

>CREATE EXTENSION pgmnemo CASCADE;
>pip install pgmnemo-mcp && pgmnemo init claude