Bitemporal model
Bitemporal columns aren’t limited to facts — they live on decisions, strategies, relations and hyperedges as well, so any of those can be superseded without losing history. Taking facts as the example, every fact carries two time axes:
valid_from/valid_to— business time, when the statement was true in the world.created_at/updated_at— system time, when Mnemosyne learned the statement.
A fact is current when valid_to IS NULL. A fact is historical when
it has a valid_to. Both stay in the database; the historical row is your
audit trail.
Why two axes
Suppose the agent learned at 10:00 that the Redis version was 6.2, and at 14:00 that it was upgraded to 7.0 at 11:00. With a single time axis you have to pick:
- Delete the 6.2 row → lose history.
- Update the row to 7.0 → corrupt the audit trail.
- Soft-delete with a timestamp → close, but you can no longer answer “what did the agent know at 12:00?”.
Mnemosyne writes:
fact_a: { content: "Redis 6.2", valid_from: <unknown>, valid_to: 11:00,
created_at: 10:00 }
fact_b: { content: "Redis 7.0", valid_from: 11:00, valid_to: NULL,
created_at: 14:00 }You can now answer four different questions:
| Question | Query |
|---|---|
| What is true now? | valid_to IS NULL |
| What did we believe at 12:00? | created_at <= '12:00' filtered by valid window |
| What was true in the world at 12:00? | valid_from <= '12:00' AND valid_to > '12:00' |
| When did we change our mind? | scan created_at history |
Time travel in the API
Every read endpoint accepts an asOf parameter:
curl -X POST http://localhost:3000/v1/recall \
-H "Authorization: Bearer mns_live_xxx" \
-d '{
"query": "redis version",
"asOf": "2026-06-07T12:00:00Z"
}'The hits returned are the facts the agent would have surfaced at noon on that day, regardless of what’s true now.
Implementation notes
- The PostgreSQL
tstzrangeis indexed with a GIST exclusion constraint — no two current rows can claim the same(workspace_id, topic_key)simultaneously. - Closing a fact (
DELETE /v1/facts/:id) setsvalid_to = now()and preserves the row. - Restoring is the inverse:
valid_toreturns toNULLand the old window is re-opened.
This model is borrowed from data warehousing’s Type-2 SCD and from XTDB. We picked it because the agent’s reasoning quality drops sharply when its memory rewrites history.