Bi-temporal memory
Separate when something happened from when the agent learned it — and supersede beliefs without deleting the history they came from.
Every entry in Mubit carries two independent times: ingestion time (when the store learned it) and occurrence time (when the event actually happened). Most memory systems conflate the two, which makes "what did we believe in April?" and "what happened in April?" the same — usually wrong — question. Use this pattern when your agents ingest events after the fact, when beliefs change over time, or when any agent needs to reconstruct a past state instead of just the current one.
How it works
Two timestamps per entry. Ingestion time is stamped automatically. Occurrence time you set explicitly on the write; if you don't, the runtime falls back to extracting it from metadata keys (occurrence_time, event_time, session_date) before finally treating ingestion time as the event time.
Time-bounded queries. min_timestamp / max_timestamp (inclusive, Unix seconds) restrict evidence to entries whose occurrence time — falling back to ingestion time — lands inside the window. Temporal bounds activate a dedicated temporal retrieval branch, so history is a first-class lane, not a post-filter.
Supersession instead of deletion. When a belief changes — a new fact contradicts a stored one — the old entry is not overwritten. It is marked is_stale: true with superseded_by pointing at its replacement (supersessions written by fact reconciliation also stamp invalid_at, closing the old belief's validity interval):
- Current-state answers exclude it. Synthesis works from the live belief only.
- Historical and time-bounded queries can still see it. The past stays queryable.
This is what makes the two-timestamp model useful: the store distinguishes "no longer true" from "never happened". How stale entries surface (ranking penalty, superseded_by chains, knowledge_confidence) is covered in Trust, confidence, and staleness.
Walkthrough
In March, Ari stored "Acme Corp is on the Starter plan". On May 3, Acme upgrades. Ari writes the new plan fact with the upgrade's occurrence time; the Starter fact is superseded, not erased.
From SDK v0.12.0 the typed helpers forward all three fields natively in every language — remember(..., occurrence_time=...) and recall(..., min_timestamp=..., max_timestamp=...) in Python and JS, matching what Rust's RememberOptions/RecallOptions already carried. On 0.11.0 and earlier the Python/JS helpers do not forward all of them, which is why the tabs above route through the occurrence_time metadata key and the advanced passthrough — both routes remain valid on every version (occurrence_time is a wire-level ingest-item field; the temporal bounds are wire-level query fields). See SDK methods → Temporal queries for the per-language matrix.
When not to use it
- Events ingested as they happen. If ingestion time ≈ occurrence time (a live agent writing what it just did), skip explicit
occurrence_time— the fallback is already correct. Reserve it for backfills, imported history, and user reports about the past. - You want the old value gone, not superseded. Supersession is deliberate retention. For actual removal (a compliance delete, a poisoned entry), use explicit deletion —
forget()— not a superseding write; see Utility-weighted forgetting for how deletion differs from archival. - The "change" is really a refinement. If the new write clarifies rather than contradicts ("Scale plan, billed annually"), write-time fact reconciliation resolves it as an
UPDATEinstead of a supersession — see Write-time reconciliation.
Related
- Temporal memory patterns — the recipe: occurrence-time writes, range queries, staleness handling, and budget control end to end
- Trust, confidence, and staleness — reading
is_stale,superseded_by, and confidence signals before acting - Write-time reconciliation — the mechanism that decides when a new fact supersedes an old one
- Retrieval semantics — how the temporal branch fuses with semantic and lexical retrieval
Configuration. Bi-temporal supersession is an instance feature, on by default on hosted instances (MUBIT_CL_BITEMPORAL=0 disables it — beliefs then coexist without staleness marking). Occurrence-time storage and time-bounded queries are always available and need no configuration.