---
title: "Bitemporal data, explained for AI systems"
description: "Bitemporal data tracks valid time and transaction time. What each axis records, the four query types they answer, and why agent memory needs both."
canonical: https://past.dev/guides/bitemporal-data
last-updated: 2026-09-02
---
# Bitemporal data, explained for AI systems

Source: https://past.dev/guides/bitemporal-data

Bitemporal data records two independent time axes for every fact: valid time, the period when the fact was true in the world, and transaction time, the moment the system learned it. Keeping both lets a system answer what was true at any date and also what it believed at any date. Agent memory is naturally bitemporal because sources arrive late, corrections rewrite the past, and backfills load old records long after the events.

## The two time axes

Every recorded fact has two histories. One is the history of the world: the salary rose on May 1. The other is the history of the record: the system learned about the raise on August 20. Bitemporal storage keeps both, as two independent axes on every row.

- **Valid time** is the period during which a fact was true in the world. It comes from the fact itself: the effective date of a raise, the start date of a contract, the date a meeting happened. Valid time can lie in the past or the future, and it moves only when the record of the world is corrected.
- **Transaction time** is the moment the system recorded the fact. It comes from the system clock at write time. It never lies in the future and never changes after the write, because it describes the write itself.

Streaming and agent memory systems call the same two axes [event time and ingestion time](/guides/event-time-vs-ingestion-time). SQL:2011 calls them application time and system time. The names differ by community; the two-axis distinction is the same everywhere.

## A worked example: a correction discovered late

Dana's salary was 70,000 USD from January 1. A raise to 78,000 USD was entered on June 1 with June 1 as its effective date. On August 20, payroll discovers the raise had actually taken effect on May 1, and enters a correction. The bitemporal store now holds three records.

| Record | Valid from | Recorded at |
| --- | --- | --- |
| Salary 70,000 USD | January 1 | January 1 |
| Salary 78,000 USD, first entry | June 1 | June 1 |
| Salary 78,000 USD, corrected start | May 1 | August 20 |

Both axes are needed to explain the payroll error. Asked on July 1 what Dana earned on May 15, the system answered 70,000 USD, which reflected everything it had learned by July 1. Asked the same question on September 1, it answers 78,000 USD. The May payslip was computed from a record that transaction time still preserves, so the underpayment can be shown, explained, and fixed. A store with valid time alone would have replaced the June record in place and lost the proof that the payslip was correct given what was known.

## The four question types

Combining the two axes yields four query shapes. Stores with one axis answer the first one or two. The last two need both.

| Query | Valid time | Transaction time | Example |
| --- | --- | --- | --- |
| Current state | now | now | What is Dana's salary? |
| Historical state | a past date | now | What was the salary on May 15, given everything known today? |
| Past belief | a past date | a past date | What did the system believe on July 1 about May 15? |
| Record history | any | a range | When did the salary record change, and to what? |

Past belief queries make bitemporal storage auditable: they reproduce any answer the system ever gave, on the information it had then. Audits, incident reviews, and [point-in-time recall](/glossary/point-in-time-recall) in agent memory all use this query shape.

## Bitemporal features in SQL

SQL:2011 standardized both axes. System-versioned tables maintain transaction time automatically: every `UPDATE` closes the old row version and inserts a new one, and `FOR SYSTEM_TIME` queries read past states. [MariaDB documents system versioning](https://mariadb.com/kb/en/system-versioned-tables/), including how to combine it with application-time periods on one table.

Application-time periods are the valid-time half: the table carries period columns the application sets, and the database enforces period semantics on update and delete. A table with both mechanisms is bitemporal in the SQL:2011 sense. A survey of temporal storage options, and what agent memory needs beyond them, is in the [temporal database guide](/guides/temporal-database).

A bitemporal query then names both axes. Asking for Dana's salary on May 15 as known on July 1 reads the row whose valid period covers May 15, from the table state as of July 1. One clause per axis, and either clause can default to now.

> **Terminology**
>
> Vendors name the axes differently: system time, application time, effective date, event time. When reading a feature list, determine which of the two axes it records. A feature that records write time only cannot answer what was true in the world.

## Why agent memory is naturally bitemporal

Agent memory ingests sources that describe the past, so the two axes separate on nearly every write.

- **Sources arrive late.** An email sent Tuesday is ingested Friday. Its facts were true from Tuesday (valid time) and known to the agent from Friday (transaction time). Treating Friday as the fact date misorders every change detection that follows.
- **Corrections rewrite the past.** A corrected transcript changes what was true about last month's meeting. Valid time moves; the correction's transaction time records when the system learned of it.
- **Backfills load years in a day.** Importing two years of tickets gives every record the same transaction date and preserves two years of spread in valid time. A system that conflates the axes sees that history collapse into one day.

[Bitemporal memory](/glossary/bitemporal-memory) is the memory-system name for this design. How systems are tested on late arrivals and corrections is described in the [benchmark methodology](/benchmarks/methodology).

## How past.dev maps onto bitemporal data

past.dev stores both axes for every fact. Event time comes from the timestamp sent with each ingested record, and validity windows with supersession links express change on the valid-time axis, while ingestion is tracked separately so late arrivals and corrections order correctly. The [quickstart](/docs/memory-api/quickstart) shows ingest with an original timestamp and recall returning dated evidence.

## Frequently asked questions

### What is the difference between valid time and transaction time?

Valid time is when a fact was true in the world, such as the effective date of a raise. Transaction time is when the system recorded the fact. A correction entered on August 20 for a raise effective May 1 has valid time May 1 and transaction time August 20.

### Are event time and ingestion time the same as valid time and transaction time?

The pairs correspond. Event time plays the role of valid time, and ingestion time plays the role of transaction time. Streaming and memory systems use the first vocabulary while SQL standards use the second.

### Do I need bitemporal storage for an AI agent?

You need it as soon as sources can arrive late or be corrected, which covers email, transcripts, and any backfill. With one axis, a late correction either falsifies history or gets the wrong date. With both, corrections coexist with the record of what was believed before them.

### Can a regular SQL database store bitemporal data?

Yes. Store valid-from and valid-to columns set by the application, plus a recorded-at column set at write time, and never update rows in place. Several databases automate the transaction-time half with system-versioned tables.

## Related

- [Temporal databases](https://past.dev/guides/temporal-database)
- [Facts that change over time](https://past.dev/guides/facts-that-change-over-time)
- [Event time vs ingestion time](https://past.dev/guides/event-time-vs-ingestion-time)
- [Bitemporal memory](https://past.dev/glossary/bitemporal-memory)
- [Point-in-time recall](https://past.dev/glossary/point-in-time-recall)