Bi-Temporal Memory: Why AI Agents Must Know When a Fact Was True
An AI agent that has been running for six months will, at some point, confidently tell you something that used to be true. It will cite a pricing tier…
An AI agent that has been running for six months will, at some point, confidently tell you something that used to be true. It will cite a pricing tier that changed in Q2. It will reference a teammate's role after they've moved teams. It will pull a customer's contract terms from a version that was superseded eleven weeks ago. None of this is a hallucination in the technical sense. The model is retrieving a real fact from a real document. The failure is temporal: the agent has no concept of when that fact stopped being true, or when the system found out it stopped being true. It just knows the fact exists somewhere in its retrieval index, so it says it.
This is the core limitation of nearly every production RAG and agent memory system today. Vector stores index text. Knowledge graphs index entities and relationships. Almost none of them index time correctly, and the ones that try usually only track one dimension of it. That single gap is responsible for a disproportionate share of the "confidently wrong" outputs teams see from long-running agents, and it gets worse, not better, the longer an agent runs and the more documents accumulate in its context.
The fix is bi-temporal memorya modeling approach that tracks not just what was true, but when it was true in the world, and separately, when the system learned it was true. This article explains the mechanism in depth, why single-timestamp systems fail, and how a bi-temporal context graph like Sentra's resolves it at write time instead of leaving it as a runtime guessing game.
The two clocks problem: valid time versus transaction time
Bi-temporal modeling comes from database theory, originally used in financial systems and healthcare records where "what did we believe, and when" is a compliance requirement. It rests on tracking two independent timelines for every fact:
Valid time is the period during which a fact was actually true in the real world. A customer's contract had a 12-month term valid from March 1 to February 28. A teammate held the title "Senior Engineer" from January through September before being promoted.
Transaction time is the period during which the system recorded that fact as true. This is when the document was ingested, when the field was written to the database, or when the agent's memory graph absorbed the update.
These two clocks are almost never in sync, and the gap between them is exactly where agents get things wrong. Consider a real scenario: a pricing page changes on April 3rd, reflecting a decision that was made and took effect on April 1st. If your agent ingested the old page on March 15th and does not re-ingest until May 1st, there is a 47-day window (April 3 to May 1) where the world already changed but the system does not know it, and a separate 2-day window (April 1 to April 3) where the change was valid before it was even published anywhere the agent could see.
A single-timestamp system, meaning one that just stores "last updated" or "created at," collapses both of these clocks into one number. It cannot tell you which gap you are in. It cannot tell you whether a fact is stale because the world changed, or stale because ingestion lagged, or actually still current. Bi-temporal memory keeps both clocks explicit, which means an agent (or the humans debugging it) can always ask two separate questions: "was this true then?" and "did we know it at the time?"
Why single-timestamp memory breaks agents in production
Most vector databases and even many "knowledge graph" products attach a single timestamp, typically ingestion time, to each chunk or node. This works fine for static corpora. It fails predictably in any environment where facts change, which is every real business.
Here is the specific failure chain:
1. A fact changes in the source system (Salesforce, Notion, a contract repository, an HR tool). 2. The old fact and the new fact both exist as embeddings in the vector store, often with similar semantic content, because "contract term: 12 months" and "contract term: 24 months" are close in embedding space. 3. At query time, retrieval ranks by semantic similarity to the question, not by validity. Both chunks score similarly. 4. The agent's context window ends up with both the old and new fact, with no structural signal indicating which one superseded the other. 5. The LLM, faced with two contradictory chunks and no temporal ordering, either merges them into a confused answer or picks the one that happened to appear first in context.
This is not a rare edge case. In any org with normal document churn (contract renewals, org changes, pricing updates, policy revisions), the density of superseded-but-still-indexed facts grows continuously. A six-month-old agent memory is not six months of facts. It is six months of facts plus every prior version of every fact that changed, all competing for retrieval relevance with no temporal arbitration.
Some teams try to patch this at query time: adding a "please prioritize recent information" instruction to the prompt, or reranking by ingestion date. This helps marginally but does not solve the underlying problem, because ingestion date is transaction time only. It tells you when the system learned something, not when that something stopped being valid in the world. A document ingested last week can still describe a policy that expired two months ago.
Resolving meaning at write time instead of guessing at query time
This is the architectural decision that separates a bi-temporal context graph from a patched vector store. The question is not "how do we rank conflicting facts better at query time." It is "why are we letting conflicting facts reach query time unresolved at all."
Sentra's approach is to resolve temporal meaning once, at write time, when new information enters the graph. When a new fact arrives, whether from a document, an API sync, a Slack message, or a CRM update, the ingestion pipeline does not just embed and store it. It checks the fact against existing nodes in the org's context graph, identifies whether it contradicts, updates, or extends an existing fact, and writes the correct temporal relationship immediately: closing the valid-time range on the superseded fact, opening a new valid-time range on the current one, and stamping both with accurate transaction time.
The practical effect is that by the time an agent queries the graph, there is no ambiguity left to resolve. There is exactly one fact marked as currently valid for any given entity and attribute, plus a full, queryable history of what was valid before, with clean boundaries. The agent does not need a clever prompt to figure out which contract term is current. There is only one node marked current, and it is the correct one, because the resolution work already happened during ingestion.
This matters enormously for token economics as well as correctness. A query-time guessing system has to stuff multiple candidate versions of a fact into context and hope the model sorts it out, burning tokens on redundant, contradictory information. A write-time-resolved graph returns one clean, current answer (plus history only if the query asks for it), which is a meaningful part of why Sentra customers see roughly 70% lower token spend on comparable agent workloads. Sentra also does better on the actual reasoning, scoring approximately 88% on Terminal-Bench 2.1, a benchmark that specifically stresses multi-step agent tasks where stale or contradictory context causes cascading errors.
What a bi-temporal fact actually looks like
It helps to be concrete about the data model. A bi-temporal fact is not just a row with a timestamp. It is a record with at minimum four temporal boundaries plus the fact payload itself:
| Field | Example value | Meaning |
|---|---|---|
| Entity | contract:acme-corp-2024 | What the fact is about |
| Attribute | term_length | What property is being described |
| Value | 24 months | The fact itself |
| Valid from | 2024-03-01 | When this became true in the world |
| Valid to | 2026-03-01 (or null if current) | When this stopped (or will stop) being true |
| Recorded at | 2024-03-04T09:12:00Z | When the system learned this |
| Superseded by | null or a pointer to the next fact node | Explicit link to the fact that replaced it |
That "superseded by" pointer is what makes the graph genuinely bi-temporal rather than just timestamped. It turns a flat list of dated facts into a traversable history where every fact knows its own successor and predecessor. An agent asking "what is Acme's current contract term" traverses to the node with valid_to: null. An agent asking "what did we tell Acme's account manager the term was in April" traverses to whichever node had a valid range covering April, regardless of what the current node says.
Query patterns a bi-temporal graph enables
The practical value of this structure shows up in query types that are simply impossible to answer reliably with a single-timestamp system.
"What is true right now?" This is the default query, and it is trivial in a bi-temporal graph: filter for valid_to: null on the relevant entity. No ranking, no reranking, no ambiguity.
"What did we believe at time X, even if it was wrong?" This is critical for auditing agent behavior. If an agent gave a customer incorrect information on June 10th, you need to reconstruct exactly what the graph said was current on June 10th, not what is current now. Bi-temporal storage answers this by filtering on recorded_at <= June 10 and finding whichever fact had the widest valid range as of that transaction time. Systems with only one timestamp cannot answer this at all; they have already overwritten the old value.
"What was actually true during period Y, regardless of when we found out?" This is the valid-time-only query, useful for retroactive corrections. Legal and compliance teams ask this constantly: "what were the actual terms in effect during Q2, even accounting for the late invoice correction we processed in Q3."
"When did we find out X changed, and how long was the lag?" This compares valid time to transaction time directly and surfaces operational risk. A 60-day lag between a policy changing and the system recording it is a process problem worth flagging on its own, independent of any specific query an agent runs.
A single-timestamp system can approximate the first query type and cannot reliably answer the other three at all, because the information needed to answer them was never captured in the first place.
Comparing memory architectures directly
| Capability | Sentra (bi-temporal context graph) | Standard vector RAG | Single-timestamp knowledge graph |
|---|---|---|---|
| Tracks valid time (when fact was true in the world) | Yes, explicit valid_from/valid_to on every fact | No | Rarely, and usually inferred, not stored |
| Tracks transaction time (when system learned it) | Yes, explicit recorded_at with full history | Only as generic "ingested_at" metadata | Sometimes, as last-updated field |
| Resolves conflicts at write time | Yes, contradiction detection during ingestion | No, conflicts persist until query time | Partial, usually just overwrites old value |
| Can reconstruct "what we believed on date X" | Yes | No | No |
| Token cost for retrieving current fact | Low, single resolved node returned | High, multiple candidate chunks often retrieved | Medium |
| Org-wide shared context across agents | Yes, single graph shared by all agents and teams | No, typically per-application index | No, typically per-application |
| Compliance posture | SOC 2 and ISO 27001, self-hosting available | Varies by vendor | Varies by vendor |
| Benchmark performance (Terminal-Bench 2.1) | ~88% | Not designed for this class of task | Not designed for this class of task |
The verdict across every row that matters for long-running agent reliability favors resolving temporal state structurally rather than statistically. Vector similarity is a good tool for finding semantically related content. It is the wrong tool for determining which of two contradictory facts is currently authoritative, because similarity and correctness are unrelated axes.
The org-wide context graph as a shared source of truth
A subtlety that often gets missedtemporal consistency is not just a per-agent problem, it is an org-wide problem. If your sales agent, your support agent, and your internal ops agent each maintain separate memory stores, you can end up with three different agents holding three different beliefs about the same fact, each internally consistent but mutually contradictory. A customer asks support about a contract term and gets one answer, then asks sales and gets another, and both agents are technically citing "current" information from their own isolated index.
This is why Sentra is built as a single org-wide bi-temporal context graph, a shared company brain that every agent and every team draws from, rather than siloed memory per application. When the contract term updates, it updates once, in one place, and every agent querying that entity afterward sees the same current fact and the same history. This also means the write-time resolution work (contradiction detection, supersession linking) happens exactly once per fact change, rather than being duplicated across N separate ingestion pipelines for N separate agents, which is both a correctness win and a cost win.
For regulated environments, this shared graph runs under SOC 2 and ISO 27001 controls, and self-hosting is available for teams that need the context graph inside their own infrastructure perimeter rather than a third-party cloud, which matters when the facts in question include contract terms, HR records, or customer PII.
Implementation patterns for teams building this themselves
Teams evaluating whether to build bi-temporal handling in-house versus adopting a platform should know the specific engineering lift involved, because it is substantial and easy to underestimate:
Contradiction detection at ingestion requires an entity resolution layer that can recognize "24 month term" and "term_length: 24mo" as the same attribute before it can even ask whether they conflict. This is nontrivial NLP and schema-matching work, not a database feature.
Supersession linking requires deciding, for every entity type, what counts as "the same fact updating" versus "a new, separate fact." A contract term changing is clearly an update. A customer having two concurrent contracts for different products is not a conflict at all and should not be merged. Getting this distinction wrong in either direction either loses history or falsely merges facts that were never actually competing.
Query-time filtering on two temporal dimensions instead of one means every retrieval call needs to specify not just "what" but "as of when, on which clock," and application code calling the memory layer has to be written with that in mind from day one, since retrofitting it later means auditing every existing query.
Most teams that attempt this in-house get as far as single-timestamp overwriting (the vector RAG or basic knowledge graph rows in the table above) and stop there, because full bi-temporal modeling with write-time contradiction resolution is a multi-quarter engineering investment on top of whatever the actual product is supposed to do.
Closing takeaway
An agent that has been running for months is not just accumulating knowledge, it is accumulating versions of knowledge, and without a structural way to track which version was true when and which version the system knew about when, that accumulation guarantees eventual confident wrongness. Bi-temporal memory solves this by tracking valid time and transaction time as separate, explicit dimensions on every fact, and by resolving contradictions once, at write time, rather than leaving the LLM to guess at query time from a context window full of competing versions. Sentra implements this as a single org-wide context graph shared across agents and teams, cutting token spend by roughly 70% on comparable workloads, scoring around 88% on Terminal-Bench 2.1, and running under SOC 2 and ISO 27001 controls with self-hosting available for teams that need the graph inside their own perimeter. The question worth asking about any agent memory system is not "does it have timestamps." It is "does it know the difference between when something was true and when it found out."