Documentation

Bi-temporal validity

Bi-temporal Validity

Bi-temporal validity is the time model underneath evidence_refs and locked_facts. It's the foundation of Mnemix's audit guarantee: facts are never deleted or silently overwritten — they are bounded in time. This lets Mnemix answer two different questions precisely.

Forgetting is failure. A contradiction bounds the old fact; it never erases it.

Two timelines, not one

Every governed fact tracks two independent timelines, stored as range columns (tstzrange, GiST-indexed for fast as-of queries):

| Timeline | Column | Answers | | :--- | :--- | :--- | | Valid time | valid_range | When was this true in the real world? | | Transaction time | tx_range | When did Mnemix believe / record it? |

On evidence_refs, the ranges are generated from two scalar timestamps: event_time (when the event happened — caller-supplied) and ingestion_time (when Mnemix processed it). Keeping the timelines separate is what makes the model bi-temporal: a fact can be valid in the real world before the system ever recorded it (you backfill an opening balance dated August 1 on August 15), and a fact can be recorded but already bounded (you log that a value was true last quarter).

Scope (locked decision D3): bi-temporality lives on evidence_refs and locked_facts only. Memory objects — the embedded memories your agent recalls — are not bi-temporal; they record ingestion time as information, nothing more. As-of reconstruction answers from the two governed tables.

The two questions it answers

  1. What is true right now?
    SELECT fact_value FROM locked_facts
    WHERE entity_id = $entity AND fact_key = $key
      AND valid_range @> now();
    
  2. What did the agent believe was true at 2:00 PM last Tuesday?
    SELECT fact_value FROM locked_facts
    WHERE entity_id = $entity AND fact_key = $key
      AND valid_range @> $t::timestamptz
      AND tx_range    @> $t::timestamptz;
    

That second query is the heart of the audit guarantee. Given any past moment, Mnemix reconstructs the exact set of facts that were active then — which is exactly what re-running a gate verdict requires.

Supersession instead of update

When a fact changes, the substrate runs a supersession transaction (via the evolve_fact procedure) rather than an UPDATE:

  1. Bound the old fact — close its valid_range at the supersession moment.
  2. Insert the new fact — its valid_range opens where the old one closed.
  3. Link them — the new row's supersedes_ref_id points at the old row.
jest    ┃━━━━━━━━━━━━━━━━━━━━┫                          (valid_range closed) ◀──┐
        2025-01-01        2026-05-29                                            │
vitest                     ┣━━━━━━━━━━━━━━━━━━▶ active     supersedes_ref_id ───┘

The old row stays queryable forever. Nothing is lost.

Why this beats a vector store

A vector database stores the latest text and retrieves what's similar. It has no notion of "what was true before this was overwritten." Mnemix retrieves provenance-backed truth as of a point in time. When an enterprise asks "why did the agent approve this $5,000 refund three weeks ago?", Mnemix replays the exact evidence ref and locked fact that were active at that millisecond — not today's values.

Evidence is bi-temporal too

The same range pair lives on evidence_refs. Evidence can expire (a quote valid for 30 days) or be superseded (a corrected statement) without deleting the record that an agent once relied on it.

Out of scope (v1)

  • Time-bounded locked facts authored as "valid until DATE" — a post-launch ticket. Today, bounding happens via supersession, not a pre-declared expiry.

See also