Your agent forgets what changed — bi-temporal memory ships today

Two weeks ago we shipped a benchmark showing Mem0 fabricates dates. At the bottom of that post we promised bi-temporal versioning would land in two weeks.

Today it ships in v2.9.0, live on PyPI and npm.

The problem

Real memory has a temporal structure. When Alice says "I prefer dark mode" in April and "I prefer light mode" in May, both statements were true at the time they were made. A useful agent shouldn't pick one and forget the other. It should know that on April 15 the user preferred dark mode, on May 15 they preferred light mode, and right now they prefer light mode.

Most memory layers, including ours until today, store the latest fact and silently overwrite the old one. The agent loses the ability to answer "what did the user prefer two weeks ago" — and worse, it loses the ability to know that anything changed.

What ships today

Three new capabilities, every memory now carries:

{
  "decision": "Alice prefers dark mode",
  "valid_from": "2026-04-15T00:00:00Z",
  "valid_to":   "2026-05-02T20:18:33Z",
  "superseded_by": "uuid-of-the-replacement-memory"
}

valid_from is when the fact started being true. valid_to is when it stopped (or null if it's still true). superseded_by points to the memory that replaced it (or null for current truth).

Every existing memory in your account got valid_from = created_at backfilled automatically. No migration on your end.

The API

List memories at a specific point in time. Returns what was true on April 1, even if it's no longer true now:

client.memories.list(as_of="2026-04-01T00:00:00Z")

Mark a memory as replaced. Both fields are optional, but at least one must be set. Pass superseded_by when a new memory replaces the old one. Pass valid_to alone when a fact expired without a replacement (e.g., "user is on a 2-week trial"):

client.memories.supersede(
    old_memory_id,
    superseded_by=new_memory_id,
)

Walk the supersession chain. Returns the full timeline, oldest to newest:

timeline = client.memories.timeline(memory_id)
for entry in timeline:
    print(entry.decision, "current" if entry.is_current else "superseded")

JavaScript users get the same surface:

import { Aurra } from 'aurra';

const client = new Aurra({ apiKey: process.env.AURRA_API_KEY! });

await client.memories.supersede(oldId, { supersededBy: newId });
const timeline = await client.memories.timeline(newId);

What the read path looks like now

By default, list() and query() return only current truth — memories with superseded_by = null. Your existing code keeps working unchanged.

If you want to see the full history, pass include_superseded=true. The response includes a superseded_count so you know how much history exists without paying to fetch it:

{
  "count": 12,
  "superseded_count": 47,
  "memories": [...]
}

Twelve facts your agent currently believes. Forty-seven facts that used to be true. Both queryable.

Why we shipped Level 1 first

Bi-temporal can mean three different things at three different complexity levels:

LevelWhat it doesStatus
1Manual supersession — developer says "this old memory is replaced by this new one"Live today
2Auto-detection — LLM decides at write time whether new info contradicts old infoTuesday
3Full bi-temporal — separate "valid time" (when fact was true) and "transaction time" (when Aurra learned it). Lets you query what Aurra thought was true on a past date.Wednesday

Level 1 is the right place to start. It's the foundation Levels 2 and 3 build on. It ships without an LLM in the write path, so there's no new failure mode to debug. And manual supersession is what you actually want when you're integrating from existing systems — your code already knows when a fact got updated.

Auto-detection in Level 2 is genuinely hard. The LLM has to decide whether "Alice prefers tea in the morning" supersedes "Alice prefers tea" (it doesn't — second is more general) or whether "Alice's birthday is March 5" supersedes "Alice mentioned her birthday is in March" (it does, but barely). We're shipping it Tuesday with a hand-labeled test set and confidence thresholds, not as a guess.

What's next

Try it

Both SDKs are live now:

pip install --upgrade aurra
# or
npm install aurra@latest

Existing API keys work unchanged. Email support@aurra.us for early access.


The full v2.9.0 release notes are on the changelog.