# Temporal validity

How SurrealDB Agent Memory tracks when facts were true using valid_from and valid_until.

Facts change. SurrealDB Agent Memory records **when each fact was true**, not only what the current value is.

Every active attribute and relation carries a **validity interval**:

- **`valid_from`** - when the fact became true (often the extraction or correction time; document facts may use an effective date from the source).
- **`valid_until`** - when the fact ceased to be true. **`null`** means still valid.

Together these fields power current-state reads, point-in-time reconstruction, and full correction history. The broader picture - system time, known time, and valid time - is in [Tri-temporal model](/docs/agent-memory/architecture/tri-temporal-model.md).

## What temporal validity enables

**Current state** - default queries return attributes whose validity interval includes “now”.

**Point in time** - ask what was true on a past date (“what role did Alice hold in February 2025?”) using **`asOf`** or validity parameters on entity reads.

**History** - walk the supersession chain for a key to see every value it took, in order.

**Temporal language in chat** - when a turn says “I became CEO in January 2025”, “last week I moved to Berlin”, or “I used to live in Berlin”, extraction sets or adjusts validity bounds where it can. **Relative phrases** (“last week”, “three weeks ago”) resolve against the **turn’s spoken time**, not wall-clock ingest time - the same anchoring documents get from `authored_at` or caller-supplied **`observed_at`** / **`observedAt`** on ingest. Unresolved dates may become **`uncertainty`** records instead of guessed timestamps.

## How it differs from deletion

Closing a fact with **`valid_until`** is not the same as deleting it. The record stays in the store for audit and time-travel queries; it simply drops out of “what is true now”.

| Operation | Delete-and-replace | Temporal validity |
| --- | --- | --- |
| Apply correction | Old value gone | Old value closed; new value opened |
| Query current state | Latest record wins | Filter by open validity interval |
| Query past state | Often impossible | Filter by interval containing the instant |
| Undo a bad correction | Restore from backup | Reopen prior value in the chain |

## Interaction with supersession

**Supersession** tracks **why** a value changed (replaced by a newer belief). **Temporal validity** tracks **when** each value was active. A correction updates both: the old attribute gets **`valid_until`** and a link to its replacement; the new attribute gets **`valid_from`** and a link back. See [Reconciliation and supersession](/docs/agent-memory/reasoning/reconciliation-and-supersession.md).

## Correction path versus expiry path

Two situations set **`valid_until`**:

**Correction** - a conflicting new value supersedes the old one. The chain links old → new.

**Expiry** - a fact ends without a replacement (a promotion that lapses, a time-boxed assignment, an instruction that was revoked). **`valid_until`** is set directly - at extraction when temporal language supplies an end date, or via **`POST /forget`** / lifecycle operations when you expire memory explicitly.

## Narrative playback and spoiler safety

Agents that ingest an entire canon at once - every chapter of a novel, every episode of a series, every film in a franchise - **know things the user has not reached yet**. That is a problem for:

- **Serial fiction** - a reader on chapter 8 of *Dr Jekyll and Mr Hyde* must not learn that Hyde and Jekyll are the same person before the reveal page.
- **Episodic or franchise media** - a viewer on *The Empire Strikes Back* in release order must not be told Vader is Luke's father before that scene; someone watching in chronological order (I → II → III) needs a **different** spoiler boundary entirely, as the spoiler for the viewer in this case is that Anakin becomes a villain.
- **Long-running book series** - a *Wheel of Time* reader on book three should not get answers that depend on book twelve.

The fix is not to omit later material from ingest. Ingest everything, but stamp **when each fact entered the reader's timeline**, then query **`asOf`** the user's current position.

| User position | On ingest | On recall |
| --- | --- | --- |
| "I've read up to page *N*" | `observed_at` / `observedAt` per page or chapter (a synthetic timeline, not wall-clock) | `asOf` = that page's stamp |
| "I'm on episode 5 of my chosen order" | One stamp per episode in **viewing order** (release, chronological, or machete) | `asOf` = stamp for episode 5 |
| "Show me chapter 3 only" | `labels: ["chapter=3"]` on upload | `labels: ["chapter=3"]` (often with `include: ["passages"]`) |

**`asOf` gates the knowledge graph** - attributes **and relations**. A `same_as` edge between two characters only appears once the ingest stamp for the reveal page has passed. Earlier queries see separate entities with no link.

> [!NOTE]
> Raw **passages** keep wall-clock upload time. Spoiler guarantees on structured answers use the entity/attribute/relation view (`GET /entities/...?asOf=...`, or `/query` with `asOf`). For passage-level navigation ("what's on page 3?"), pair **`labels`** with `include: ["passages"]`. **`lens`** narrows by scope region at every tier (facts and passages).

See the [spoiler-safe narrative memory](/docs/agent-memory/cookbooks/patterns/spoiler-safe-narrative-memory.md) cookbook for franchises, non-chronological viewing orders, and page-by-page ingest recipes.

## Caller-supplied known time

When each unit of a serial (page, chapter, episode, policy revision) should appear on a **known-time axis** distinct from when you ran the import, pass **`observed_at`** on `POST /facts` (or **`observedAt`** in document upload metadata). SurrealDB Agent Memory stamps derived facts with that instant as their **known time** (`created_at`), so **`asOf`** queries reconstruct what the system would have believed at that point in the narrative - not at bulk-import time.

**Derived values ride the same axis.** A number you recompute per unit - mentions so far, a running score, sentiment to date - becomes one supersession per unit when you write it with that unit's **`observed_at`**. Reading it back with **`asOf`** returns the value as it stood at that position. See [Tracking prominence over the timeline](/docs/agent-memory/cookbooks/patterns/spoiler-safe-narrative-memory.md#tracking-prominence-over-the-timeline).

## Reading validity through the API

- **`GET /entities/{type}/{name}`** - active attributes for an entity; optional temporal query params on the request.
- **`GET /entities/{type}/{name}/history/{key}`** - ordered supersession history for one key.
- **`POST /query`** with **`asOf`** - belief-level recall at a past instant. **`asOf` now gates relations** as well as attributes: a relationship is visible only when its known time and validity interval include the requested instant.

Field-level schema detail lives in [Data model and schema](/docs/agent-memory/reference/data-model-and-schema.md) for operators who manage SurrealDB directly.
