Skip to content

Valid-Time Succession

Consider two claims about the same predicate:

  • Alice: acme:ceo / held_by = "Alice", valid [2020-01-01, 2024-06-01), valid_time_confidence = 0.9
  • Bob: acme:ceo / held_by = "Bob", valid [2024-06-01, ∞), valid_time_confidence = 0.9

These two claims describe the same predicate at different points in time. They do not conflict — they form a clean temporal succession: Alice held the position before June 2024; Bob holds it from June 2024 onward. Querying “who is CEO today?” should return Bob. Querying “who was CEO in 2022?” should return Alice.

Without succession-aware logic, the engine would see two live functional claims with different values on the same (subject, predicate) and route them to Contested. That is incorrect: there is no genuine ambiguity here, and no oracle is needed. The valid-time windows tell the whole story.

A claim pair qualifies as a clean temporal succession when all of the following hold:

  1. Same (subject, predicate), different value.
  2. Both claims have valid_time_confidence >= 0.7 (the trust threshold — both windows are trusted).
  3. Both claims have bounded windows: valid_from is present on the successor; valid_until is present on the incumbent.
  4. The incumbent’s window ends at or before the successor’s window begins: incumbent.valid_until <= successor.valid_from (non-overlapping, half-open interval semantics where valid_until is exclusive).

When all four conditions hold, the reconciler (C3) classifies the relationship as NoConflict (succession). The gate (C7) routes to the cheap path: CommittedCheap for the successor claim. No oracle is needed.

After both claims are committed with CommittedCheap, querying the belief uses instant-selection in the fold (C2). Given a query instant q (defaulting to now):

  • The fold checks whether all live functional claims on the subject-line form a non-overlapping trusted succession.
  • If they do, it selects the single claim whose valid_time window contains q.
  • Only that claim appears in the belief projection; has_conflict is false.
Query instant Result
q = 2022-03-15 (within Alice’s window) Resolved, value = "Alice"
q = 2024-06-01 (exactly at Bob’s start; Alice’s end is exclusive) Resolved, value = "Bob"
q = 2026-01-01 (within Bob’s open-ended window) Resolved, value = "Bob"
q falls in a gap between Alice’s valid_until and Bob’s valid_from NoBelief (gap)

If the windows overlap — even partially — succession does not apply. Two claims covering the period [2020, 2025) and [2023, ∞) genuinely conflict at the overlap [2023, 2025). The fold returns Contested for any query instant in that overlap. An oracle is needed to resolve which value was correct during the contested period.

Similarly, if one or both claims have valid_time_confidence < 0.7, the engine cannot confidently determine whether the windows overlap, so it falls through to Contested.

Succession is handled at two layers:

Write time (Reconciler C3): When a new claim arrives, the reconciler checks whether it forms a clean succession with the current incumbent. If it does, it classifies as NoConflict → cheap path → CommittedCheap. This prevents a spurious Contested disposition from being written to the ledger.

Read time (TruthEngine C2 fold): When query_memory is called, the fold selects the claim valid at the query instant. This handles:

  • Multi-claim succession chains (A → B → C → …) where the query instant falls in any window.
  • Claims whose succession was detected at write time (both stored as CommittedCheap).
  • Historical queries at different instants returning different beliefs from the same store.

Both layers must work together. The write-time reconciler prevents the ledger disposition from saying Contested when succession was detected; the read-time fold provides correct instant selection.

Known limitation: as_of_tx_time is not a valid-time as_of

Section titled “Known limitation: as_of_tx_time is not a valid-time as_of”

The as_of_tx_time parameter in query_memory restricts which ValidityAssertion records are visible — it does not filter claims by their valid_time window against a specified valid-time instant. The fold always selects the claim valid at now (or the transaction time of the query call).

True bi-temporal point queries combining as_of_tx_time AND as_of_valid_time are a planned future refinement. Today, to query “who was CEO at a past valid-time instant?”, you can use as_of_tx_time to see the store as it was at a past transaction instant, but the fold’s instant-selection uses the transaction time as the valid-time proxy.

To enable succession, supply a valid-time window with high confidence when ingesting claims:

import mempill
from mempill.types import ProvenanceLabel
engine = mempill.open_in_memory()
# First claim: Alice, CEO from 2020 to 2024-06-01.
resp1 = engine.ingest_claim({
"agent_id": "my-agent",
"subject": "acme:ceo",
"predicate": "held_by",
"value": "Alice",
"provenance": ProvenanceLabel.external_user_asserted(),
"cardinality": "Functional",
"confidence": {"value_confidence": 0.95, "valid_time_confidence": 0.9},
"valid_time": {"start": "2020-01-01T00:00:00Z", "end": "2024-06-01T00:00:00Z"},
"criticality": "High",
"derived_from": [],
})
print(resp1["disposition"]) # CommittedCheap
# Second claim: Bob, CEO from 2024-06-01 onward (non-overlapping).
resp2 = engine.ingest_claim({
"agent_id": "my-agent",
"subject": "acme:ceo",
"predicate": "held_by",
"value": "Bob",
"provenance": ProvenanceLabel.external_user_asserted(),
"cardinality": "Functional",
"confidence": {"value_confidence": 0.95, "valid_time_confidence": 0.9},
"valid_time": {"start": "2024-06-01T00:00:00Z"}, # open-ended
"criticality": "High",
"derived_from": [],
})
# Non-overlapping + trusted windows = CommittedCheap (not Contested)
print(resp2["disposition"]) # CommittedCheap
# Query at now (after 2024-06-01): Bob is selected.
result = engine.query_memory({
"agent_id": "my-agent",
"subject": "acme:ceo",
"predicate": "held_by",
})
print(result["belief"]["status"]) # Resolved
print(result["belief"]["primary"]["fact"]["value"]) # Bob
  • I1 — Non-destruction: Alice is not deleted when Bob is committed. Both remain in the store.
  • I2 — Bi-temporal by trust: succession is only recognized when valid_time_confidence >= 0.7. Low-confidence windows fall back to Contested.
  • I7 — Contested first-class: overlapping windows still produce Contested. Succession never silently resolves a genuine overlap.
  • I8 — Read-time canonical: the fold computes instant-selection fresh on every query_memory call. The result is deterministic: same claims + same query instant → same belief.
  • I10 — Fixed-history monotonicity: querying the same fixed history at the same instant always returns the same result.