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:
| Level | What it does | Status |
|---|---|---|
| 1 | Manual supersession — developer says "this old memory is replaced by this new one" | Live today |
| 2 | Auto-detection — LLM decides at write time whether new info contradicts old info | Tuesday |
| 3 | Full 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
- Tuesday — Level 2 with LLM auto-detection, behind a per-write opt-in flag
- Wednesday — Level 3 full bi-temporal with separate transaction time
- Thursday — public docs at docs.aurra.us
- Saturday — bi-temporal benchmark added to github.com/aurra-memory/benchmarks
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.