# Changelog All notable changes to `pgmnemo` are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). --- ## [0.2.2] — 2026-05-10 (candidate) ### Added - **`pgmnemo.recall_hybrid()` — vector + BM25 weighted fusion** (QUICK-B) — new function combining dense cosine retrieval with BM25-class sparse retrieval (`ts_rank_cd` on `lesson_tsv`). Formula: `0.4×cosine + 0.4×ts_rank_cd(lesson_tsv, q, 32) + 0.05×(importance/5) + 0.05×recency_90d + 0.05×prov_strength + graph_weight×graph_proximity`. Union retrieval: candidates matched by **either** embedding cosine **or** BM25 text match. Returns `rrf_score` diagnostic column (`1/(rrf_k+vec_rank) + 1/(rrf_k+bm25_rank)`). Motivation: pure BM25 baseline achieves 0.982 recall@10 vs. 0.933 for vector-only; hybrid closes the gap per Maharana 2024 and Wu ICLR 2025. - **Migration script** `extension/pgmnemo--0.2.1--0.2.2-hybrid.sql` — idempotent `CREATE OR REPLACE FUNCTION`, backward-compatible (existing `recall_lessons()` unchanged). - **Benchmark script** `benchmarks/scripts/run_longmemeval_hybrid.py` — LongMemEval evaluation harness for `recall_hybrid()` with gap-analysis reporting vs. vector-only and BM25 baselines. ### Upgrade ```sql \i extension/pgmnemo--0.2.1--0.2.2-hybrid.sql ``` --- ## [0.2.1] — 2026-05-09 ### Added - **`traverse_causal_chain(direction)` parameter** (W2.2 / F5) — adds `direction TEXT DEFAULT 'forward'` parameter. `'forward'` follows source→target edges (existing behaviour, backward-compatible). `'backward'` follows target→source edges for reverse traversal. `'both'` traverses all edges. Input validation raises `EXCEPTION` on invalid values. Cycle guard via path array applies to all directions. - **`pgmnemo.ef_search` GUC** (F2) — `SET LOCAL pgvector.hnsw.ef_search` applied at `recall_lessons()` entry from `pgmnemo.ef_search` GUC (default 100, clamped 10–500). - **Graph-proximity mixin in standard upgrade path** (F3) — `pgmnemo--0.2.0-step4-recall-mixin.sql` content folded into the v0.2.0.1→0.2.1 upgrade script (was supplemental-only). - **Row-Level Security multi-tenant isolation** (W2.3 / Q5) — `pgmnemo.tenant_id` GUC gates `agent_lesson` by `project_id` and `mem_edge` by endpoint ownership. Empty/unset = service-account bypass. Policies are idempotent (DROP IF EXISTS before CREATE). ### Fixed - **`recall_lessons()` IN-param/RETURNS TABLE collision on `project_id`** (INS-029 v2) — IN-param `project_id INT` collided with the `RETURNS TABLE` column of the same name. Fix: IN-param renamed to `project_id_filter`; all internal `recall_lessons.project_id` references updated accordingly. Backport of the same pattern as the `role`→`role_filter` fix in v0.1.4.1/v0.2.0.1. ### Changed - **`pgmnemo.recency_weight` default lowered** (F1) — from `0.20` to `0.08` (pending REC-1 ablation confirmation). Operator can override via `ALTER SYSTEM SET pgmnemo.recency_weight = ''; SELECT pg_reload_conf();`. ### Upgrade ```sql ALTER EXTENSION pgmnemo UPDATE TO '0.2.1'; ``` --- ## [0.2.0.1] — 2026-05-04 ### Fixed - **`traverse_temporal_window()` numeric → double precision cast** (INS-030) — comparison between a `NUMERIC` intermediate value and `DOUBLE PRECISION` caused a type-mismatch error at runtime on PostgreSQL 14/15. Cast now explicit throughout the function body. - **`recall_lessons()` IN-param/RETURNS TABLE collision** (INS-029) — IN-param `role` renamed to `role_filter` (backport of v0.1.4.1 fix; see below). - **Idempotent upgrade DDL** (INS-031) — `ADD COLUMN IF NOT EXISTS` and `CREATE INDEX IF NOT EXISTS` guards added across all `0.1.4→0.2.0` upgrade scripts (backport of v0.1.4.1 fix). - **`recall_lessons_pooled()` post-collision smoke** (Action #7) — confirmed pooled wrapper correctly delegates to the renamed `role_filter` parameter after the collision fix. ### Upgrade ```sql ALTER EXTENSION pgmnemo UPDATE TO '0.2.0.1'; ``` --- ## [0.1.4.1] — 2026-05-04 (maintenance branch) ### Fixed - **`recall_lessons()` IN-param/RETURNS TABLE collision** (INS-029, P0) — PL/pgSQL raised `ERROR: parameter name "role" used more than once` because the IN-param `role TEXT` collided with the `RETURNS TABLE` column of the same name. The flagship function never compiled on a fresh install. Fix: IN-param renamed to `role_filter`; all callers updated. - **Idempotent upgrade DDL** (INS-031) — `ADD COLUMN IF NOT EXISTS` and `CREATE INDEX IF NOT EXISTS` guards applied across all upgrade scripts so re-running a patch on an already-upgraded database no longer raises duplicate-object errors. ### Upgrade ```sql ALTER EXTENSION pgmnemo UPDATE TO '0.1.4.1'; ``` --- ## [0.2.0] — 2026-05-04 ### Added - **`pgmnemo.mem_edge` DDL** (closes RFC §3) — directed typed edge table between `agent_lesson` rows. - Columns: `source_id`, `target_id`, `relation_type` (CAUSED_BY, SUPERSEDES, CO_OCCURRED, DERIVED_FROM, or user-defined), `weight REAL` [0.0–1.0], bitemporality (`valid_from`/`valid_until`), `commit_sha`, `metadata JSONB`. - Three covering indexes: forward/reverse traversal on `(source_id, relation_type)` and `(target_id, relation_type)` with `WHERE valid_until IS NULL`; temporal range index on `(valid_from, valid_until)`. - `CONSTRAINT ck_no_self_loop`: prevents `source_id = target_id`. - **`pgmnemo.traverse_causal_chain(start_id, max_depth, relation_types, only_active)`** (closes RFC §4) — recursive CTE walk of the causal edge graph. - Returns `(lesson_id, depth, path BIGINT[], path_weight, role, topic, lesson_text, importance, created_at, commit_sha, verified_at)`. - Cycle-safe via accumulated path array. Fail-safe: returns zero rows if `start_id` missing. - `max_depth` default 5; `relation_types` default `ARRAY['CAUSED_BY']`; `only_active` default `TRUE`. - **`pgmnemo.traverse_temporal_window(start_id, window_interval, include_unlinked, role_filter, project_id_filter, k)`** (closes RFC §5) — co-temporal episode discovery. - Returns lessons whose `created_at` falls within `±window_interval` of the anchor lesson. Window hard-capped at 30 days. - `linked=TRUE` when a `mem_edge` (either direction) exists between the row and `start_id`. - Ghost-lesson exclusion controlled by `pgmnemo.include_unverified` GUC (default off). - **Graph-proximity mixin for `recall_lessons()`** (PGMNEMO-V020-4) — integrates BFS graph traversal into scoring. - Updated scoring formula: `0.4×cosine + 0.2×importance + γ×recency + 0.1×prov_strength + δ×graph_proximity`. - `graph_proximity = MAX(1 - depth/max_depth)` via BFS through `CAUSED_BY`, `CO_OCCURRED`, `DERIVED_FROM` edges from top-5 cosine anchors (max_depth=5). - New GUC `pgmnemo.graph_proximity_weight` (default `0.2`, clamped to `[0.0, 0.5]`). ### Upgrade ```sql ALTER EXTENSION pgmnemo UPDATE TO '0.2.0'; ``` Or from a fresh install: ```sql CREATE EXTENSION pgmnemo CASCADE; -- installs 0.2.0 directly ``` --- ## [0.1.4] — 2026-05-04 ### Added - **State machine for `agent_lesson`** (closes [#3](https://github.com/pgmnemo/pgmnemo/issues/3)) - New `state TEXT` column (default `'draft'`), constrained to 9 lifecycle values: `draft`, `candidate`, `validated`, `canonical`, `deprecated`, `superseded`, `archived`, `rejected`, `conflicted`. - `state_changed_at TIMESTAMPTZ` — auto-set on every state change. - `pgmnemo.agent_lesson_state_transition` table — explicit allowed-transition pairs. - `pgmnemo.transition_lesson(lesson_id BIGINT, new_state TEXT)` — enforces the DAG; raises on invalid transition. - **Provenance FK columns** (closes [#4](https://github.com/pgmnemo/pgmnemo/issues/4)) - `source_run_id BIGINT NULL` — soft FK to the orchestrator `agent_run` row that produced this lesson. - `source_task_id BIGINT NULL` — soft FK to the orchestrator `tasks` row. - Partial indexes `ix_pgmnemo_lesson_source_run` and `ix_pgmnemo_lesson_source_task` (WHERE NOT NULL). - Columns are intentionally not hard `REFERENCES`-constrained so the extension remains portable across host schemas. - **TTL / `expires_at`** (closes [#5](https://github.com/pgmnemo/pgmnemo/issues/5)) - `expires_at TIMESTAMPTZ NULL` — optional hard expiry; `NULL` = never expires. - `pgmnemo.evict_expired_lessons()` — deletes rows where `expires_at < NOW()`; returns eviction count. Safe to call on a schedule. - Partial index `ix_pgmnemo_agent_lesson_expires` keeps eviction scans cheap. ### Fixed - **`pgmnemo.version()` dynamic lookup** (closes [#1](https://github.com/pgmnemo/pgmnemo/issues/1)) - `version()` previously returned a hard-coded string baked at build time. After `ALTER EXTENSION pgmnemo UPDATE` the reported version was stale. - Now reads `extversion` from `pg_catalog.pg_extension` at call time — always accurate. ### Upgrade ```sql ALTER EXTENSION pgmnemo UPDATE TO '0.1.4'; ``` Or from a fresh install: ```sql CREATE EXTENSION pgmnemo CASCADE; -- installs 0.1.4 directly ``` --- ## [0.1.3] — 2026-04-29 ### Added - `verifier_role TEXT` column on `agent_lesson` — records which agent role validated the lesson. --- ## [0.1.2] — 2026-04-28 ### Added - Tri-state `prov_strength` (`hard` / `soft` / `none`) on `agent_lesson`. - `recall_lessons_pooled()` wrapper — cross-project recall for shared-context queries. --- ## [0.1.1] — 2026-04-27 ### Added - `recency_weight` GUC — tune the time-decay component of the hybrid recall score without restarting the server. --- ## [0.1.0] — 2026-04-26 ### Added - HNSW vector index via `pgvector` — fast approximate nearest-neighbour recall. - `pgmnemo.ingest()` — provenance-gated write API; requires `commit_sha` or `artifact_hash`. - `pgmnemo.recall_lessons()` — hybrid scoring: cosine similarity + BM25 full-text + recency decay. - Role + `project_id` composite scoping. - `recall_lessons_pooled()` (cross-project variant). --- ## [0.0.1] — 2026-04-20 Initial schema: `pgmnemo.agent_lesson` table + basic HNSW index.