Inspiration
A refund agent pays out on Thursday. The customer changed their bank details on Tuesday. The money lands in a closed account.
Nothing crashed. No exception was thrown. The agent recalled a fact, acted on it, and the fact was simply out of date. Nothing anywhere in its memory could have told it so.
I kept coming back to how ordinary that failure is. Every agent memory system I looked at answers the question, "What do you remember about this?" Almost none can answer, "And is that still true?" Vector similarity ranks by how close a query is to a stored fact. It has nowhere to put "This one died on Tuesday." The superseded fact still matches, often better than its replacement, because it has had longer to accumulate context.
Then I found the thing that reframed it. In 1983, James F. Allen proved that between any two intervals of time there are exactly thirteen possible relations. Not approximately thirteen. Exactly. A complete, closed vocabulary for how two facts can sit in time relative to each other.
That is what memory needs and does not have. Not better recall, but a way to say when.
What it does
Tredecim stores every fact with the interval over which it is true and never deletes anything. Revising a fact closes the open interval and opens a new one in a single serializable transaction, alongside the business write that justifies it.
That gives two independent clocks. Valid time is when something is true in the world. Transaction time is when the system found out. They are not the same, and the gap between them is where every question after an incident lives:
What is the IBAN? Any database answers this What was it at 14:02? Needs valid time What did the agent BELIEVE at 14:02? Needs transaction time It was wrong. Since when do we know? Needs both
The last one is the one no vector store can answer, and it is a regulatory requirement in finance rather than a curiosity.
The console demonstrates seven things going wrong and the database refusing each: a fact superseded without loss, eight agents racing one refund, a worker killed holding a payment, a fact true before anyone recorded it, a semantic search that cannot reach a stale fact, an untrusted source asserting a new bank account, and the window that deferred memory extraction leaves open.
How I built it
Facts are rows in CockroachDB with valid_from, valid_to, a version, provenance, and a 1024-dimensional embedding on the same row. Embeddings live with the facts rather than in a separate store, which is the whole argument: similarity ranking and validity are evaluated by one engine, in one query, so semantic recall cannot return a closed fact.
The central invariant is not enforced by application code:
CREATE UNIQUE INDEX facts_one_open ON facts (entity_id, key) WHERE valid_to IS NULL;
An agent that tries to assert a second truth without closing the first gets a constraint violation. So does a script, a migration, or a service written by someone who never read the repository.
The agent holds nothing in process memory. Every step of an episode is checkpointed before the next begins, so a worker killed mid-flight is resumed by another. That durability creates the problem the rest solves: deciding and acting are separated by an unbounded gap, so a decision is a proposal rather than an authorisation. Before money moves, it is re-validated against live memory under FOR UPDATE, in the same transaction as the payout.
CockroachDB: distributed vector indexing (partial, cosine), serializable transactions with 40001 retry, a partial unique index for the invariant, AS OF SYSTEM TIME for engine-side time travel, row-level TTL, the Managed MCP Server driving a schema drift detector, ccloud for provisioning, and the Agent Skills repo applied to the live cluster.
AWS: Amazon Bedrock (Titan Text Embeddings v2) and AWS Amplify Hosting.
Challenges I ran into
THE VECTOR INDEX WAS NEVER USED. It existed, SHOW INDEXES listed it, and every semantic query was a full scan. Two causes, both silent. The operator class defaults to vector_l2_ops, which serves <-> only, while the query ranks by cosine <=>. And even once that was fixed, the valid_to IS NULL predicate every live recall carries was on its own enough to send the planner back to a scan. The index has to be partial over that same predicate.
That restriction turned out to be the better design. Only facts in force are ever semantically recalled, so the index tracks the size of the present rather than the size of the history. Keeping every superseded interval forever costs storage and costs search nothing. 476 ms scanning became 177 ms indexed at ten thousand facts.
AN AGENT COULD PAY A STALE DESTINATION. A code review caught that the payout used the destination captured during recall, while an episode can sit parked between deciding and acting indefinitely. Precisely the failure this project claims to eliminate.
THE APPLICATION USER HELD ADMIN. A skill from the CockroachDB Agent Skills repo asked the question directly. The credential compiled into a public web app could drop every table. Split into a runtime role with DML on six tables and an admin role for migrations. Granting USAGE on public is not enough. CockroachDB grants CREATE there to the public role by default.
RECORDING A HOSTILE FACT VERSUS ACTING ON ONE. The instinct is to refuse the write. But a memory that declines to record what it was told has quietly decided what is true and destroyed the evidence. So the poison is recorded and refused at the moment it would move money.
Accomplishments I'm proud of
64 assertions run against a live cluster, and one of them reads the query plan. A regression that stops the planner using the index fails the test run rather than quietly costing a full scan per query. That check exists because I lost hours to an index that was built, listed, and never chosen, a failure invisible to any test that only checks results.
A browser test drives the deployed console and walks a first-time visitor through the whole path. It has caught things nothing else could: an overlay intercepting a click, a label giving the page a horizontal scrollbar.
Every number reported was measured, not estimated. p50 commit is 20 ms when co-located and 650 ms across an ocean, and both are stated, because a memory layer that hides its write latency is not telling you the thing you most need to know.
And the README has a section called "What this does not do", listing six limits I measured. A limit you found reads better than one a reviewer finds for you.
What I learned
An index that is built, listed, and never chosen looks exactly like one that works. So does a claim that has never met an EXPLAIN. Almost everything that improved this project came from testing the mechanism rather than the outcome.
The second lesson was about durability. Making execution durable does not just add safety. It creates the gap between deciding and acting that did not exist when both happened in one function call. The re-validation before payout is not an extra safety measure bolted on. It is the direct consequence of the checkpointing, and without it, durability would have made the system less correct rather than more.
What's next
Historical semantic recall still scans, since the partial index covers only facts in force. The provenance floor gates payouts rather than every read. There is no story yet for a deployment that must forget.
The larger direction: the thirteen relations are currently implicit in the schema. Making them explicit, asking the memory directly which of Allen's thirteen relations holds between two facts, turns a storage layer into something an agent can reason with.
Built With
- allens-interval-algebra
- aws-amplify
- aws-bedrock
- bitemporal
- claude
- cockroachdb
- nextjs
- postgresql
- react
- typescript
- vector-search

Log in or sign up for Devpost to join the conversation.