The off-topic pool had to be moved out of Python, because the code corpus was ingesting it

RE-call

Agent memory on PostgreSQL with pgvector where every hit carries one of eleven verdicts, strict mode refuses instead of answering, erasure writes a permanent tombstone the builder re-checks, and 162 dated preregistrations record what was going to be measured before it was.

Carries 7 of 7 rubric mechanisms. Most systems here carry none or one (41%), and a dash means the mechanism was not found at this commit — not that the system needed it. Each mark is one LLM reviewer's reading of the code at this commit rather than a run of it — known limits.

  • Tombstone
  • Trust state
  • Bi-temporal
  • Scope enforced
  • Mutation audit
  • Human review
  • Negative evals

1. Executive Summary

RE-call is "[m]emory that abstains instead of guessing" — Apache-2.0, version 0.13.0, 102,183 lines of Python across 253 modules with 494 test files, running on the caller's own PostgreSQL with pgvector. It stores source documents, indexes them, "and keeps validity and lineage attached to every hit."

The pitch is a distinction rather than a feature:

"Plain vector search returns nearby text. RE-call also asks whether that text is current, supported, and trustworthy enough for the query. A superseded claim comes back marked superseded; a result that does not clear the calibrated trust gate becomes ABSTAIN with a reason."

That is implemented as a closed vocabulary of eleven verdicts, of which exactly one — ok — becomes evidence. The rest stay readable and never become the answer. Under a strict policy the gate does not degrade quietly; it raises.

What makes this repository worth a careful read is not the retrieval stack but what surrounds it. docs/preregistrations/ holds 162 dated markdown documents, running from 15 August to 15 September 2026, each stating what was about to be measured before the measurement happened. Amendments are separate dated files (...-amendment-2.md through -amendment-5.md on one canary), and results are separate documents again (2026-08-27-checker-ground-truth.md beside 2026-08-27-checker-ground-truth-result.md). A hypothesis and its outcome cannot be reconciled after the fact when both are committed under their own dates.

The single best passage in the tree is about the project's own test data. The off-topic query pool — subjects a search must abstain on — was written as Python literals, and RE-call is a system people point at code corpora, including its own:

"These subjects are DATA, and as Python literals they were also CORPUS. offtopic_subjects_absent_from keeps a subject only when none of its content words appear anywhere in the corpus under test, so a code corpus that includes recall's own tree ingested this very list and then disqualified every one of its 25 subjects."

It was measured, not assumed: none of twenty-five survived against a repository-rooted corpus, eleven of twenty-five against a third-party corpus of the same size, "so the failure was recall dogfooding itself, not the pool being too small." The pool moved to JSON, which the wizard's **/*.py and **/*.md globs do not match. And the distinctive words are deliberately never written in prose, because naming one re-contaminates the pool — a rule the author reports breaking twice while fixing it, "once in a comment in recall/wizard/queryset.py, caught only because the measured survivor count moved the wrong way, and once in THIS paragraph, caught in seconds by the guard below."

Every one of the atlas's seven capabilities is present, and the reason is visible in the code rather than in the README: each was built as an argument about what a neighbouring design would have got wrong.

2. Mental Model

A hit carries a verdict, and only ok becomes evidence.

A verdict is a member of a closed set, each one defined against the one it is most likely to be confused with.

A tombstone is permanent, keyed on the source URI, and re-checked mid-build.

A tenant is a Postgres row-level-security policy, not a WHERE clause.

A preregistration is what you wrote down before you measured.

Diagram — retrieval is judged against two independent time axes and a calibrated threshold, only ok hits become evidence, strict mode refuses rather than degrading, and erasure writes a permanent tombstone the builder re-checks inside the ingest
Diagram source
%% caption: retrieval is judged against two independent time axes and a calibrated threshold, only ok hits become evidence, strict mode refuses rather than degrading, and erasure writes a permanent tombstone the builder re-checks inside the ingest
flowchart TB
    Q["a query"] --> LEGS["dense · sparse · graph legs, fused and reranked"]
    RLS[("recall_chunks_v1 —<br/>FORCE ROW LEVEL SECURITY<br/>tenant_id = current_setting(GUC)")] --> LEGS
    LEGS --> EV["trust.evaluate — pure: no DB access, no clock reads"]
    VT["now = VALID time:<br/>drives expired, not_yet_valid<br/>from declared valid_from/valid_until"] --> EV
    TT["known_as_of = TRANSACTION time:<br/>drives not_yet_known from<br/>first_indexed_at, not indexed_at"] --> EV
    TT -.->|"using the LAST write 'claimed a memo edited today<br/>had never existed before the edit, so every replay<br/>of an earlier instant reported an empty store'"| FIXED["a bug found, fixed, written down"]
    SUPE["supersession edges are rewound too:<br/>an edge dates from when its<br/>superseding document was written"] --> EV
    EV --> V{"one of eleven verdicts"}
    V -->|"ok"| USE["evidence: trusted = [h for h in hits if h.verdict == 'ok']"]
    V -->|"superseded · expired · not_yet_valid ·<br/>not_yet_known · low_confidence ·<br/>invalid_metadata · not_entailed ·<br/>dependency_invalidated"| SEEN["returned and readable —<br/>never the answer"]
    V -->|"ambiguous_supersession"| CLOSED["fails closed rather than<br/>guessing a successor"]
    V -->|"unverified"| DEG["the DEGRADED-mode verdict:<br/>'not a weaker ok — the trust gate never ran'"]
    GATE{"strict policy?"} --> EV
    GATE -->|"strict"| REFUSE["raise TrustRefusal —<br/>including a deliberate<br/>BROAD-CATCH: fail-closed"]
    GATE -->|"degraded"| DEG
    FORGET["forget(source_uri)"] --> TS[("recall_source_tombstones<br/>PK (tenant_id, source_uri)<br/>FK → the source_forgotten audit event")]
    TS --> BUILD["every future build calls _is_tombstoned"]
    BUILD -.->|"re-checked INSIDE the ingest, because an erasure<br/>arriving mid-build would otherwise land after<br/>the check that would have caught it"| PERM["permanent: the URI is barred from every future build"]
    AUD[("recall_audit_events — appended on the<br/>build's OWN connection: generation_created,<br/>built, failed, abandoned, validated,<br/>rolled_back, gc, source_forgotten")] --- BUILD
    PROP["a model-extracted proposal"] --> REV{"review_proposal: _require_review_fields"}
    REV -.->|"raises without reviewer identity,<br/>timestamp AND a non-blank audit note"| REFUSED["no promotion"]
    REV --> PF["PromotedFact — the only type<br/>downstream writers accept"]

3. Architecture

Area Role
recall/trust.py The gate: two time axes, the verdicts, and where it refuses
recall/types.py The verdict vocabulary, each value argued against its neighbour
recall/generations.py Builds, the audit events, and the tombstone re-checks
recall/migrations/sql/0008_generation_foundation.sql Forced RLS, the audit table, the tombstone table
recall/promotion.py The review gate a proposal cannot go around
recall/decision_ledger.py A witness of retrieval decisions, deliberately not an enforcer
recall/eval/, docs/preregistrations/ The negative sets, and what was written down first

4. Essential Implementation Paths

recall/trust.py:520-544 — the two axes, how they compose, and the first_indexed_at bug that made every point-in-time replay return an empty store.

recall/types.py:55-81 — eleven verdicts, and the argument for why unverified is not low_confidence.

recall/generations.py:566-570 with :810, :976, :1005 — one tombstone check, called three times, for a reason stated in generation_store.py:52-58.

recall/migrations/sql/0008_generation_foundation.sql:128-133FORCE ROW LEVEL SECURITY on the chunk table itself.

recall/promotion.py:200-210 — a gate that refuses three ways.

recall/eval/synthetic.py:75-100 — the negative pool that was contaminating itself, measured.

5. Memory Data Model

Source documents are chunked into recall_chunks_v1 with declared validity, lineage and supersession attached, all under a tenant policy. Beside them, AtomicFact is a structured claim — namespace, subject, predicate, object, context, valid_from, valid_until — validated on construction and promoted into an append-only fact ledger with a current-state projection. Generations version the corpus: a build produces a new generation, promotion makes it active, and a garbage collector removes superseded ones.

6. Retrieval Mechanics

Dense, sparse and graph legs are fused and reranked, then every surviving hit is judged by trust.evaluate — a pure function with no database access and no clock reads, which is what makes the two time axes testable without a database. Calibration supplies the threshold, and carries its own certification status, so the system can tell a threshold it fitted from one it never had.

7. Write Mechanics

A build reads a manifest, skips tombstoned URIs, writes chunks, and appends its lifecycle events on the same connection. The corpus fingerprint is computed from the manifest minus the tombstoned set, so a build whose only change is an erasure is still a distinct generation rather than a no-op. Extracted proposals take the other path, through review.

8. Agent Integration

A CLI, an MCP server registered as io.github.GiulioDER/re-call, a Codex plugin, Claude Code hooks, desktop packaging and Docker compose files. Several preregistrations measure the integration itself — hook ordering, in-process against MCP transport, tool-definition context cost, and Claude Code with and without RE-call, calibrated and not.

9. Reliability, Safety, and Trust

Fail-closed is the default posture and is written as such, including a broad exception catch annotated with its intent. The gaps are the configured ones: a development mode that serves unverified results, a generation_promoted_unsafe_development path that exists, and a decision ledger that is off unless enabled and best-effort when it is. The mutation audit is the part that is not optional — it commits with the build.

10. Tests, Evals, and Benchmarks

494 test files, evaluation suites over LoCoMo, LongMemEval, BEIR and synthetic corpora, a labelled gap study, and abstention scored as its own axis. The preregistration directory is the artifact that distinguishes it: hypotheses, amendments and results filed separately and dated, including negative results and a holdout-validation document.

11. For Your Own Build

Write down what you are about to measure, in a dated file, before you measure it. Everything else here follows from that habit.

Check whether your evaluation data is inside your corpus. If your system indexes code and your negative examples are Python literals, they are corpus, and the failure is silent.

Re-check your tombstones inside the ingest, not only at the start of it.

Define each status against the one it will be confused with, and say so in the type. unverified and low_confidence look interchangeable until somebody has to explain a result.

12. Open Questions

Whether the degraded path should be reachable in a packaged install at all. Both escape hatches are honestly named and audited, which is the right second-best; the first-best would be that a deployment cannot serve an unjudged result.

Whether reviewer_id should be bound to an authenticated identity. The gate is structural and the field is a string, so the mechanism is stronger than the attribution it records.

Appendix: File Index

Path What to read it for
recall/types.py:55-81 A status vocabulary where each value argues against its neighbour
recall/trust.py:520-544 Two time axes that compose, and the replay bug that shaped them
recall/eval/synthetic.py:75-100 Your own repository contaminating your own negative set
recall/generations.py:566-570 A tombstone check placed where the race actually is
recall/promotion.py:200-210 A review gate that refuses without a reviewer, a time and a note
docs/preregistrations/ 162 dated statements of what was going to be measured

History

2026-09-161994fd25… — first reading, at a commit dated 16 September 2026. Screened before opening, from a shallow clone: fifteen files scanned, three auto-run surfaces, three build-time execution points (recall/setup.py, which executes at install time, tests/conftest.py, which runs on pytest collection, and the Makefile default target), one unpinned dependency surface and three dependency files inside the seven-day cooldown. uv.lock is present. A hooks/pre-commit payload sits in the tree uninstalled and inert. AGENTS.md and CLAUDE.md are addressed to a reading agent and were recorded as data. Nothing was installed, built or run, so every claim here is read from source rather than observed.