When a lot is recalled, Rescind pulls back every conclusion the agent built on it — in one transaction, with proof of what the agent knew before and after.
The 15:04 problem
At 15:00, Northvale Dairy Co-op recalls lot LOT-2026-0619-NV — Cronobacter detected in a retained sample.
At 15:04, the AI agent tells Meridian Foods DC-7 that 4,800 tins of infant formula are cleared for release.
The agent is not malfunctioning. It is remembering a Certificate of Analysis that no longer exists, and three conclusions it derived from that certificate, none of which know where they came from.
You cannot deploy an agent into a recall workflow today, because you cannot prove what it knew and you cannot take back what it learned.
That is the whole product. Not "AI for compliance" — one narrow, unglamorous, load-bearing capability that nothing on the market has.
Why nothing on the market has it
The field reads "agentic memory" as memory that accumulates: remember more, remember longer, recall better. Mem0, Zep, Letta and Pinecone are all excellent at that, and not one of them can take a fact back.
Retracting a memory safely requires three things at once:
| You need | Because | Vector stores have it? |
|---|---|---|
| Transactions | A half-finished retraction is worse than none | No |
| Foreign keys | Derived conclusions need real lineage to walk | No |
| Time travel | "What did it know at 15:04?" must be exact, not reconstructed | No |
CockroachDB has all three. That is why Rescind is a CockroachDB project rather than a project that happens to store vectors in CockroachDB.
Rescind inverts the premise: memory that can be un-learned.
The architectural move, in one sentence
retracted is a prefix column of the vector index, so retrieval is forced to constrain it — retracted memory becomes unreachable rather than filtered.
CREATE VECTOR INDEX facts_live_by_lot ON facts (lot_id, retracted, embedding);
facts.retracted is a STORED computed column (retracted_at IS NOT NULL). CockroachDB will not use a vector index unless every prefix column is equality-constrained, so every semantic retrieval must declare retracted = false.
You cannot write the fast query and forget the filter, because the filter is what makes the query fast.
That is the difference between a safety property and a coding convention. A convention survives until the first engineer in a hurry. This one is enforced by the query planner. ci/probe.json records the EXPLAIN output on a live cluster: plan uses facts_live_by_lot.
The other two load-bearing properties
Retraction is one serializable transaction. A WITH RECURSIVE walk over a real foreign-key lineage graph retracts the full transitive descendant set, flags every standing decision that rested on any of it, and writes an audit row recording the blast radius — all committing together or not at all. A partial retraction is worse than none: it leaves conclusions standing on facts the system has already disowned. The cascade runs entirely server-side, driven directly from the recursive CTE, so the transaction never ships its working set to the client and back.
Decisions are flagged, never silently reversed. Reversing a shipment release is a human's call, and a system that made it automatically would be its own kind of untrustworthy.
Replay is exact, not reconstructed. decisions.decided_hlc stores cluster_logical_timestamp() — a hybrid logical clock reading, not a wall clock — so AS OF SYSTEM TIME replay reads the identical MVCC snapshot the agent read. Wall-clock replay is an approximation that can straddle concurrent writes; this cannot. In the recorded run the same query text returns 4 memories at the decision's HLC and 1 now: knew 4, knows 1, withdrawn 3.
The refusal
Below 2 live supporting facts within L2 distance 0.55, the agent declines to answer — and the threshold is checked before the model is called, so the refusal is deterministic and identical on every run. A test asserts the model is never invoked below threshold.
Two more failure behaviours are designed, not incidental:
- Absence of evidence is never treated as safety. The Bedrock system prompt states explicitly that an empty or silent record set does not clear a lot. An agent reading "no recall notice found" as an all-clear is the exact failure this project exists to prevent.
- A malformed model response fails closed. It holds the lot and says so. It never invents a verdict.
Twelve pipelines, each separable and individually tested
| # | Pipeline | CockroachDB / AWS surface |
|---|---|---|
| 1 | Provenance-gated write — rejects any memory without a source, in Python and in a DB CHECK |
Constraints |
| 2 | Lineage write — records every parent; refuses parentless conclusions | Foreign keys |
| 3 | Vector retrieval over live memory only | Distributed vector indexing |
| 4 | Deterministic refusal below threshold, before the model is called | — |
| 5 | Bedrock reasoning — Titan embeddings + Claude release/hold | Amazon Bedrock |
| 6 | Fail-closed parsing — unparseable response holds the lot | — |
| 7 | Retraction cascade — WITH RECURSIVE transitive closure, server-side |
Serializable transactions |
| 8 | Decision flagging — marks dependents needs_review, never reverses |
Partial index |
| 9 | Audit — blast radius written in the same transaction as the damage | Arrays, HLC |
| 10 | Time-travel replay at the decision's HLC | MVCC / AS OF SYSTEM TIME |
| 11 | MCP service — retrieve / replay / recall / review queue | MCP |
| 12 | CI verification — probes assumptions, runs the suite, writes a receipt | GitHub Actions + real cluster |
Each row is a distinct code path, not a rename of the one beside it.
Verified, not asserted
Every number below was produced by a script against a real CockroachDB v25.3.0 cluster and committed to the repository as a machine-readable receipt. None is estimated.
Correctness — ci/latest.json. 19 of 19 tests pass, with no database mocks anywhere. The behaviour under test only exists in CockroachDB, so mocking it would certify nothing. One test rolls back mid-retraction and asserts that no fact, no flag and no audit row survives.
Assumptions — ci/probe.json. 5 of 6 probes hold. A STORED computed column is accepted as a vector-index prefix; single-statement WITH RECURSIVE … UPDATE parses; an HLC pins AS OF SYSTEM TIME to an exact snapshot; and the retrieval plan uses facts_live_by_lot. The sixth reports its true status rather than being quietly dropped.
Scale — ci/scale.json. The judging criterion asks for memory doing real work at real scale, so this was measured rather than claimed: 8,000 facts across 200 lots at lineage depth 12, loaded at ~220 facts/second. Retrieval at that corpus size stays in single-digit milliseconds — p50 3.46 ms, p95 3.76 ms in the run recorded at ci/scale.json — with the vector index confirmed still in use at 8,000 rows, so the index does not quietly stop being used as the corpus grows. That receipt is regenerated on every push, so the exact latency figures move a little run to run; the index-still-used result and the cascade ceiling do not. The same benchmark sweeps the retraction cascade until it breaks and records where it broke; that ceiling is stated in docs/LIMITS.md rather than omitted, because a system whose limits are unknown is not production-ready and one whose limits are published is.
Access control — ci/privileges.json. 12 of 12 privilege checks pass, executed as the least-privilege rescind_app role. It holds no DELETE privilege anywhere and no UPDATE on retractions — so the audit trail is insert-only at the privilege level, not by convention. It cannot DROP, ALTER, or CREATE. (CockroachDB grants CREATE on public by default; the check caught it and it was revoked.)
What was not verified is stated just as plainly in the README and in full in docs/LIMITS.md. That file is meant to be complete rather than flattering, and it is the most useful thing a judge can read.
Competitors become customers
Mem0, Zep, Letta and Pinecone are not obstacles to Rescind; they are its distribution. Each stores memories that their own users will eventually need to withdraw, and none can offer that, because retraction needs transactional lineage they do not have and would have to rebuild their storage layer to get.
So Rescind speaks MCP. Any of them can ask:
rescind_retrieve— is this memory still live?rescind_replay— what did the agent know when it decided that?rescind_recall_lot— pull this back, and everything built on it.rescind_open_reviews— what decisions are now standing on nothing?
The incumbent keeps the recall-and-ranking business it is good at. Rescind becomes the system of record for what may still be believed.
Who buys this, and out of which budget
Buyer: Director of Quality Assurance, or the designated recall coordinator, at a food or pharmaceutical distributor.
Budget line: FSMA 204 — the FDA Food Traceability Rule. Compliance date 20 July 2028, extended by a Federal Register notice of 7 August 2025.
The wedge, in one sentence: every one of those buyers is currently being sold an AI agent that reads certificates of analysis and clears lots for shipment, and not one of them can deploy it, because when a lot is recalled they cannot prove what the agent knew or take back what it learned.
The barrier is regulatory and evidentiary, not technical. It is not something a competitor routes around with more engineers.
How it was built
Python 3.12 and psycopg 3 against CockroachDB v25.3.0. Seven tables, two vector indexes, a STORED computed column, foreign-key lineage, partial indexes, UUID arrays and CHECK constraints. AWS Bedrock supplies both halves of the reasoning path: amazon.titan-embed-text-v2:0 for 1024-dimension normalised embeddings and Claude for the release/hold recommendation. An MCP server exposes the memory layer to any MCP client. GitHub Actions starts a real single-node CockroachDB cluster on every push, applies the schema, probes the assumptions, seeds the scenario, runs the suite, checks privileges and benchmarks scale — then commits the receipts to ci/.
Challenges
Proving the safety property instead of asserting it. The claim that retracted memory is unreachable rather than filtered rests on whether CockroachDB accepts a STORED computed column as a vector-index prefix. That was documented nowhere we could find, so it became a probe script that answers it empirically against a live cluster. It is accepted — and EXPLAIN confirms the planner uses the index.
Finding our own ceiling. Benchmarking the cascade against a deep lineage graph showed a single retraction transaction failing well before we expected. The cause was transaction lifetime: the original shape pulled every descendant id into Python and sent it back in id = ANY(...). Driving the UPDATE straight from the recursive CTE keeps the whole closure inside the database. The ceiling is real, it is measured, and it is published rather than hidden.
Keeping honesty and confidence in the same document. The headline claim is stated once, flat, with no hedge, on every surface. Every caveat lives in docs/LIMITS.md. Both are load-bearing.
What was learned
The prefix-column rule on vector indexes reads like a limitation in the documentation. It is actually the strongest safety primitive we found: making a boolean part of the index key makes a class of rows unreachable by fast retrieval, rather than trusting every future query author to remember a WHERE clause. Turning an application invariant into an index-key constraint is a pattern that generalises well beyond this project.
The CockroachDB Agent Skills repo found a real defect we had not: the retraction transaction had no client-side 40001 retry, which under SERIALIZABLE meant a concurrent writer could make a recall silently fail. Silent failure is the worst kind, and it is the exact condition this project exists to prevent.
What's next
Splitting a cascade that exceeds the single-transaction ceiling into a resumable sequence; an authenticated principal in retractions.actor instead of free text; multi-node and partition testing; and closing the review queue — today open_reviews() shows it and nothing closes it.
Built With
- agentic-memory
- amazon-bedrock
- amazon-titan
- amazon-web-services
- claude
- cockroachdb
- github-actions
- mcp
- psycopg
- python
- sql
- vector-search
Log in or sign up for Devpost to join the conversation.