pgmnemo 0.16.0

This Release
pgmnemo 0.16.0
Date
Status
Stable
Other Releases
Abstract
Single-plan multimodal agent memory for PostgreSQL — token-budget navigation, provenance-gated writes, outcome-learning
Description
pgmnemo is a PostgreSQL extension that fuses HNSW vector search (pgvector), BM25 full-text, graph-edge proximity, and JSONB metadata filtering into one SQL query plan inside your existing database. No new service, no data egress, no LLM cost per write. Features: provenance-gated writes (enforce/warn/off), token-economy navigation (navigate_locate + navigate_expand), outcome-learning (reinforce/confidence), bitemporal point-in-time recall, ACID in-place embedding updates, and multi-tenant RLS. Apache-2.0.
Released By
GAIDABURA
License
Apache 2.0
Resources
Special Files
Tags

Extensions

pgmnemo 0.16.0
Single-plan multimodal agent memory for PostgreSQL — token-budget navigation, provenance-gated writes, outcome-learning

Documentation

CHANGELOG
Changelog

README

pgmnemo ### Persistent memory for coding agents — in the Postgres you already run [![Release](https://img.shields.io/github/v/release/pgmnemo/pgmnemo?label=release&color=brightgreen)](https://github.com/pgmnemo/pgmnemo/releases/latest) [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE) [![PyPI](https://img.shields.io/pypi/v/pgmnemo-mcp.svg)](https://pypi.org/project/pgmnemo-mcp/) [![PyPI Downloads](https://img.shields.io/pypi/dm/pgmnemo-mcp.svg)](https://pypi.org/project/pgmnemo-mcp/) [![PGXN](https://badge.pgxn.org/stable/pgmnemo.svg)](https://pgxn.org/dist/pgmnemo/) [![CI](https://github.com/pgmnemo/pgmnemo/actions/workflows/ci.yml/badge.svg)](https://github.com/pgmnemo/pgmnemo/actions/workflows/ci.yml) [![PostgreSQL](https://img.shields.io/badge/PostgreSQL-17-4169E1.svg)](https://www.postgresql.org/) [![Version](https://img.shields.io/badge/version-0.15.0-blue.svg)](https://github.com/pgmnemo/pgmnemo/releases/tag/v0.15.0) [![LoCoMo recall@10](https://img.shields.io/badge/LoCoMo_recall%4010-0.8409-success.svg)](docs/img/all_metrics_history.md) [![LongMemEval recall@10](https://img.shields.io/badge/LongMemEval_recall%4010-0.9604-brightgreen.svg)](docs/img/all_metrics_history.md) [Docs](docs/USAGE.md) · [Quickstart](#quickstart) · [Discussions](https://github.com/pgmnemo/pgmnemo/discussions) · [PyPI](https://pypi.org/project/pgmnemo-mcp/)

The problem

Your coding agent finishes a debugging session. It found the root cause, fixed it, understood something about the codebase. Next session: blank slate. Same mistake, same debugging cycle, same cost.

This happens because agent memory lives in the context window. When the session ends, the memory is gone. The agent that ran 500 sessions on your codebase knows exactly as much as a fresh install.

See it work

The following transcript was captured on a pgmnemo v0.15.1 instance holding 8,112 active lessons (July 2026). Real output, copied from the session.

Session 1 — an agent debugs a connection-pooling issue and writes what it learned:

SELECT pgmnemo.ingest(
  'developer', 99, 'connection-pooling',
  'When PgBouncer runs in transaction mode, prepared statements fail
   silently. Switch to session mode, or disable the statement cache
   (statement_cache_mode=none in asyncpg).',
  4, NULL, 'demo_readme_proof'
);

-- Result: 45087

One SQL call. The lesson is stored in your Postgres with a provenance link (commit_sha), indexed for hybrid retrieval. No LLM API call on the write path.

Session 2 — a different agent, fresh context, hits the same problem. It asks memory:

SELECT lesson_id, score, lesson_text
FROM pgmnemo.recall_lessons(
  query_embedding := NULL::vector, k := 3,
  role_filter := 'developer',
  query_text := 'prepared statements broken with pgbouncer'
);

  [1] id=45087  score=0.0500
      When PgBouncer runs in transaction mode, prepared statements fail
      silently. Switch to session mode, or disable the statement cache
      (statement_cache_mode=none in asyncpg).

The lesson from Session 1 surfaces as the top result. The second agent skips the debugging cycle entirely.

The score of 0.0500 is BM25-only (no embedding vector was passed with this lesson). With embeddings configured, hybrid vector+BM25 recall produces richer ranking — but even text-only search finds the right answer when the vocabulary overlaps. Run EXPLAIN ANALYZE on any recall query to see exactly what Postgres did.

Recent releases (v0.15.0, v0.14.2, v0.14.1) · full CHANGELOG

v0.15.0 (2026-07-27): Episodic recall — situation fingerprint index. extract_sit_fp(topic, text) normalises a lesson into a situation class fingerprint; recall_situation(sit_fp, project_id, role, k) returns prior lessons for that situation class via O(log n) expression index — no embedding search required. Two differently-phrased reports of the same failure class return the same fingerprint; two similarly-phrased reports of different classes return different fingerprints. See CHANGELOG.md.

v0.14.2 (2026-07-26): Curation-honesty: reclassify_corpus() must not overwrite curator-owned content types. Root cause (P1 data-destructive): reclassify_corpus() was overwriting 'event' and 'relation' labels set by typed write verbs. Fixed via classifier_owned_types() canonical source of truth; consolidate() now accumulates evidence_count correctly across passes. See CHANGELOG.md.

v0.14.1 (2026-07-25): P0 recall latency fix + undo_consolidate(). recall_hybrid() was taking 22–30 s on parts of a 7,440-lesson corpus (HNSW planner regression — LIMIT via plpgsql variable caused generic plan with Seq Scan). Fixed with EXECUTE format(... LIMIT %s). undo_consolidate() added — exact inverse of consolidate() apply; consolidation is now reversible. See CHANGELOG.md.

Quickstart

Option A: Wire your AI CLI (two commands)

pip install pgmnemo-mcp
pgmnemo init claude   # or: codex | gemini

Session-capture and recall hooks wire into Claude Code, Codex CLI, or Gemini CLI automatically. Memory accumulates across sessions without manual ingest() calls.

Option B: Install the extension directly

# Dev / laptop evaluation (NOT for production — state lost on container rebuild):
docker run --name pgmnemo-dev -e POSTGRES_PASSWORD=pass -p 5432:5432 -d pgvector/pgvector:pg17
curl -L https://github.com/pgmnemo/pgmnemo/releases/download/v0.9.5/pgmnemo-0.9.5.zip -o /tmp/pg.zip
docker cp /tmp/pg.zip pgmnemo-dev:/tmp/
docker exec pgmnemo-dev bash -c "cd /tmp && unzip -q pg.zip && cp -r pgmnemo-0.9.5/extension/* /usr/share/postgresql/17/extension/"
-- psql -h localhost -U postgres
CREATE EXTENSION pgmnemo CASCADE;

Option C: Docker production image — pgmnemo is pure SQL, no compilation:

FROM pgvector/pgvector:pg17
ADD https://github.com/pgmnemo/pgmnemo/releases/download/v0.9.5/pgmnemo-0.9.5.zip /tmp/
RUN apt-get update && apt-get install -y --no-install-recommends unzip \
    && unzip /tmp/pgmnemo-0.9.5.zip -d /tmp/ \
    && cp -r /tmp/pgmnemo-0.9.5/extension/* \
          /usr/share/postgresql/17/extension/ \
    && apt-get remove -y unzip && rm -rf /tmp/pgmnemo-0.9.5* /var/lib/apt/lists/*

PGXN: pgxn install pgmnemo==0.9.5

Full installation guide with gotcha table: INSTALL.md

Scenarios

You wire hooks and forget about them

You run pgmnemo init claude. From now on, every Claude Code session on this project captures lessons automatically and recalls relevant ones at the start of each new session.

You don’t call ingest() or recall() manually. The hooks handle it. After a week, your agent has accumulated 40 lessons — auth patterns, deployment quirks, test fixtures that break on Mondays. New sessions start with context from old ones.

Your corpus grows; duplicates pile up

After 200 sessions, you have 800 lessons. Many are near-duplicates — the same deployment failure reported 12 different ways.

SELECT * FROM pgmnemo.consolidate(dry_run := true);

-- cluster_id | canonical_id | member_count | similarity
-- 1          | 4023         | 12           | 0.94
-- 2          | 4089         | 8            | 0.93
-- 3          | 4112         | 5            | 0.92
-- ...

You review the clusters, then run consolidate(dry_run := false). 12 reports of the same deployment failure collapse into one canonical lesson. Changed your mind? undo_consolidate(4023) reverses it.

On a real 7,400-lesson corpus, consolidate() found 399 near-duplicate clusters — a quarter of the total. Largest cluster: 55 reports of the same failure.

You need to know where a lesson came from

A lesson says “never use LEFT JOIN on the events table — it causes a full scan.” Is that real? When was it learned? What commit produced it?

SELECT lesson_text, commit_sha, created_at, match_confidence
FROM pgmnemo.recall_hybrid(
  query_text := 'events table join performance',
  role_filter := 'developer'
);

Every lesson carries its provenance. With gate_strict = 'enforce' (the default), ingest() rejects writes that lack a commit_sha or artifact_hash — at the Postgres constraint layer, not application code. Hallucinated lessons from failed runs don’t graduate to long-term memory.

You run multiple agents on different projects

Each agent has a role and a project ID. Recall is scoped:

-- Agent A (backend, project 1) sees only backend lessons for project 1
SELECT * FROM pgmnemo.recall_lessons(
  query_text := 'database migration',
  role_filter := 'backend_dev',
  project_id_filter := 1
);

-- Agent B (frontend, project 2) sees only its own scope
SELECT * FROM pgmnemo.recall_lessons(
  query_text := 'component state management',
  role_filter := 'frontend_dev',
  project_id_filter := 2
);

Role+project isolation is built into the schema. Optional RLS enforcement via pgmnemo.tenant_id GUC.

Your agent hits the same class of failure again

Two differently-worded bug reports describe the same underlying failure — a race condition in the queue consumer. extract_sit_fp() normalises both into the same situation fingerprint:

SELECT pgmnemo.extract_sit_fp('queue', 'consumer hangs under load');
SELECT pgmnemo.extract_sit_fp('queue', 'messages stuck when traffic spikes');
-- Both return the same fingerprint

SELECT * FROM pgmnemo.recall_situation('queue:consumer-hang', 99, 'developer', 5);
-- Returns prior lessons for this situation class via O(log n) index lookup

No embedding search required. The fingerprint index is an expression index — EXPLAIN shows the index scan.

Honest limits

We publish where we lose. A benchmark result that shows only wins is indistinguishable from cherry-picking.

The −68% turns result has a caveat

Agents at one engineering team used 68% fewer turns on runs where memory fired a relevant hit. This is significant on that slice. Averaged across all runs — including runs where memory had nothing relevant to say — the effect washes out.

Memory only helps when it has something relevant to say. We do not claim “−68% fewer turns” without this qualification.

The graph layer does not improve recall

pgmnemo includes add_edge() and graph traversal primitives (graph_proximity in recall_hybrid, navigate_locate, navigate_expand). The graph_proximity_weight GUC defaults to 0.2.

We have no published benchmark showing graph-augmented recall outperforms hybrid (vector + BM25) alone. The graph layer ships because it is architecturally useful for dependency tracking and causal chains, but it is not a recall quality lever today. We will not claim otherwise until a controlled experiment demonstrates otherwise.

BM25 baseline still wins on LongMemEval-S

Benchmark pgmnemo recall@10 BM25 baseline recall@10 Gap
LongMemEval-S (ICLR 2025) 0.9604 0.982 −2.2pp
LoCoMo (ACL 2024) 0.8409 (session-level) 22× smaller search space than paper Table 3
LoCoMo turn-level recall@5 = 0.302 Paper DRAGON = 0.225 +7.7pp
Production corpus (N=1,060) 0.5745 Leave-one-out self-retrieval

On LongMemEval-S, a 50-line BM25 script still beats pgmnemo’s hybrid recall by 2.2 percentage points. The gap closed from −5pp (v0.5.x) to −2.2pp (v0.6.2 RRF Fix-A, p=0.017), but it has not closed fully.

Full benchmark methodology and caveats: docs/COMPETITIVE_REALITY.md · Reproducibility: docs/BENCHMARKS.md#reproducibility · Per-version history: benchmarks/METRICS_BY_VERSION.md

How it works

pgmnemo is a PostgreSQL extension. CREATE EXTENSION pgmnemo CASCADE creates the pgmnemo schema with tables, indexes, and functions. Everything runs inside your existing Postgres process — no sidecar, no external service.

Aspect pgmnemo Generic Vector DB Cloud Memory API
Hybrid recall (vector + BM25 + JSONB) in one SQL plan EXPLAIN-able Vector-only or opaque Opaque service
Data residency In your database External service Vendor cloud
LLM cost per write $0 (pure SQL) Varies ~$0.17–$0.36 / 1K writes
Provenance enforcement DB-layer constraint None None
Near-duplicate collapsing consolidate() built-in None None
Cross-model hooks Claude Code / Codex / Gemini None Varies
Install model CREATE EXTENSION External service SaaS API
Backup pg_dump Vendor-specific Vendor-specific

What ships in the box

  • Hybrid recall — HNSW vector + BM25 full-text + JSONB predicate pushdown in one SQL plan. EXPLAIN ANALYZE works on every query.
  • Provenance gateenforce / warn / off modes via pgmnemo.gate_strict GUC. enforce rejects unverified writes at the Postgres constraint layer.
  • Corpus housekeepingconsolidate() collapses near-duplicates (dry-run by default); undo_consolidate() reverses any merge; classify_content_type() labels lessons as incident / decision / entity / fact / procedure (deterministic, no LLM).
  • Episodic recallrecall_situation() matches by situation class fingerprint, O(log n) index lookup.
  • Outcome-learningreinforce(lesson_id, 'success' | 'failure') adjusts per-lesson confidence via Beta posterior. recall_hybrid() returns match_confidence [0,1].
  • Cross-model hookspgmnemo init claude|codex|gemini wires session-capture and recall into your AI CLI.
  • Bitemporal point-in-time recallrecall_lessons(..., as_of_ts) restricts to a validity window. Time-travel your agent’s memory.
  • Role scopingrole + project_id composite isolation with optional RLS enforcement.
  • In-place maintenancereembed() / reembed_batch() refresh embeddings; recompute_content() updates lesson text with automatic hash cascade.
  • Graph traversal (opt-in)traverse_causal_chain() and traverse_temporal_window() walk typed edges. See Honest limits for the current state of graph-augmented recall.
  • Diagnostic observabilitypgmnemo.stats() (19 columns); pgmnemo.recall_stats view for call-count tracking.
  • Corpus exportpgmnemo export produces human-readable Markdown.

Compatibility

PostgreSQL Status pgvector Platform
17 Fully tested ≥ 0.7.0 required amd64 (Docker + native)
14–16 Best-effort ≥ 0.7.0 required amd64 (Docker + native)
< 14 Not supported
arm64 Source-build only ≥ 0.7.0 required No pre-built images

CI status: - 17 blocking — every release runs installcheck + smoke-recall-hybrid + bench-gate. A failure blocks the tag. - 14/15/16 aspirationalcompat-matrix job fires with continue-on-error: true. Visibility, not enforcement. If you run pgmnemo on PG < 17 and hit a bug, file an issue — we’ll prioritise fixing or downgrading the support claim honestly.

MCP server

pgmnemo-mcp is an MCP server that exposes pgmnemo’s ingest and recall as tool calls for AI agents and LLM hosts.

Install

pip install pgmnemo-mcp          # from PyPI
# or from source:
pip install -e pgmnemo_mcp/

Configuration

Variable Default Description
DATABASE_URL postgresql://localhost/pgmnemo libpq connection string
MCP_PORT 8765 Port for HTTP/SSE transport
EMBEDDING_SERVER (unset) OpenAI-compatible embeddings endpoint (e.g. http://server:1234/v1/embeddings). When set, ingest/recall embed text for vector+BM25 hybrid recall. Unset → text-only (BM25) fallback.
EMBEDDING_MODEL (unset) Model name sent in the embeddings request.
EMBEDDING_DIM 1024 Expected embedding dimension. Must match vector(1024) (e.g. bge-m3).

Usage

# Start (stdio transport — works with Claude Desktop, Cursor, etc.)
pgmnemo-mcp

# Smoke test
DATABASE_URL=postgresql://user:pass@host/db python -m pgmnemo_mcp --smoke

Docker (dependency isolation)

docker pull gaidabura/pgmnemo-mcp:0.9.5
# or build locally:
docker build -t pgmnemo-mcp:0.9.5 pgmnemo_mcp/

Full Docker quickstart (fresh DB → MCP)

# 1. Postgres with the extension (pure SQL, no compiler):
docker run -d --name pgmem -e POSTGRES_PASSWORD=pass pgvector/pgvector:pg17
curl -L https://github.com/pgmnemo/pgmnemo/releases/download/v0.9.5/pgmnemo-0.9.5.zip -o /tmp/p.zip
unzip -q /tmp/p.zip -d /tmp
docker cp /tmp/pgmnemo-0.9.5/extension/. pgmem:/usr/share/postgresql/17/extension/
docker exec pgmem psql -U postgres -c "CREATE EXTENSION pgmnemo CASCADE;"

# 2. (optional) OpenAI-compatible embeddings endpoint (1024-dim, e.g. bge-m3)
#    — without it, recall is BM25-only.

# 3. Smoke-test:
docker run --rm --link pgmem -e DATABASE_URL=postgresql://postgres:pass@pgmem:5432/postgres \
  --entrypoint python gaidabura/pgmnemo-mcp:0.9.5 -m pgmnemo_mcp --smoke
  # → "pgmnemo-mcp smoke: OK (recall_lessons returned N rows)"

MCP client config (stdio via docker run -i):

{
  "mcpServers": {
    "pgmnemo": {
      "command": "docker",
      "args": ["run", "-i", "--rm",
               "-e", "DATABASE_URL", "-e", "EMBEDDING_SERVER", "-e", "EMBEDDING_MODEL",
               "gaidabura/pgmnemo-mcp:0.9.5"],
      "env": {
        "DATABASE_URL": "postgresql://user:pass@host:5432/db",
        "EMBEDDING_SERVER": "http://server:1234/v1/embeddings"
      }
    }
  }
}

Tools exposed

Tool Arguments Description
pgmnemo.ingest text (req), role, topic, importance, project_id, commit_sha, artifact_hash, metadata Store a lesson
pgmnemo.recall query (req), top_k Retrieve relevant lessons

ingest arguments are top-level — do not nest under metadata. Pass commit_sha or artifact_hash to satisfy the provenance gate; without one the lesson is a “ghost” (excluded from recall by default).

Documentation

License

Apache License 2.0 — see LICENSE.

Contributing

See CONTRIBUTING.md. Contributions accepted under the DCO sign-off model.

Citing

@misc{gaydabura2026pgmnemo,
  author = {Gaydabura, Alex and pgmnemo contributors},
  title  = {pgmnemo: A Provenance-Gated Multi-Agent Memory Substrate for PostgreSQL},
  year   = {2026},
  note   = {ICSE-SEIP submission in preparation}
}