Valid-Time Succession
The problem succession solves
Section titled “The problem succession solves”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.
The succession rule
Section titled “The succession rule”A claim pair qualifies as a clean temporal succession when all of the following hold:
- Same
(subject, predicate), different value. - Both claims have
valid_time_confidence >= 0.7(the trust threshold — both windows are trusted). - Both claims have bounded windows:
valid_fromis present on the successor;valid_untilis present on the incumbent. - 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 wherevalid_untilis exclusive).
When all four conditions hold, the reconciler (C3) classifies the relationship as Succession — a distinct classification from NoConflict, but one that also routes to the cheap path. The gate (C7) treats Succession the same as NoConflict for routing purposes: CommittedCheap for the successor claim. No oracle is needed.
Read-time instant selection
Section titled “Read-time instant selection”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_timewindow containsq. - Only that claim appears in the belief projection;
has_conflictis 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) |
Overlapping windows are still Contested
Section titled “Overlapping windows are still Contested”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.
Conflict classification and the identical-value shortcut
Section titled “Conflict classification and the identical-value shortcut”When a new claim arrives that matches an incumbent’s value (same subject, predicate, and JSON value), the reconciler applies a fast path: if the windows do not overlap, it classifies as NoConflict and commits the claim cheaply, rather than routing to Contested. This idempotent re-statement shortcut assumes that re-affirming a current belief is safe.
However, in a succession scenario where the current incumbent is a narrowed member of a multi-claim line, the identical-value shortcut must account for older, differently-valued siblings. If a new claim’s valid-time window overlaps an older claim with a different value — even though it matches the current incumbent’s value — it is still a genuine conflict and must be routed to Contested for oracle resolution. The shortcut only applies when the candidate’s window does not overlap any other live claim whose value differs from the candidate’s; overlapping the incumbent itself (same value) is never disqualifying.
Example: The line has claims A=“Berlin” [2020,2022) and B=“Paris” [2022,∞). A new claim C=“Paris” [2021,∞) matches B’s value but overlaps A. The reconciler correctly routes C to Contested, not a fast-path CommittedCheap, because C genuinely conflicts with the earlier state A.
Write-time vs. read-time succession
Section titled “Write-time vs. read-time succession”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.
Combined bi-temporal point queries: valid_at + as_of_tx_time
Section titled “Combined bi-temporal point queries: valid_at + as_of_tx_time”as_of_tx_time and valid_at are two independent parameters on query_memory (shipped in
0.3.0). as_of_tx_time restricts which ValidityAssertion records are visible — the
transaction-time axis (“what did the engine know by this point in its own log?”). valid_at
independently selects the claim whose valid-time window contains the given instant — the
valid-time axis (“what was true in the real world at this point in time?”).
valid_at semantics (0.4.0): When valid_at is supplied, the fold asks what was believed to hold at that instant. Claims explicitly ended (by end_fact, assert_validity, or an affirmed adjudication) are considered with their window narrowed to their bound; an instant outside every claim’s window yields NoBelief (including a single open claim asked about an instant before its start). A bound carries the date granularity of the bounding instant: for an affirmed adjudication, the winning claim’s start_granularity; for end_fact, the date precision as given (e.g., end_fact(..., "2024-09") ends the claim at month precision, never a fabricated day boundary). Example:
| Scenario | Query | Result |
|---|---|---|
| Open claim Alice from 2021 | valid_at=2022-06-01 |
Alice |
Alice end_fact at 2025-01, Bob starts 2025-01 |
valid_at=2024-12-01 |
Alice (before bound) |
| (same) | valid_at=2025-06-01 |
Bob (after bound) |
| (same) | valid_at=before Alice's start |
NoBelief |
A claim bounded by an oracle Deny, or dispositioned Quarantined / Invalidated / Rejected, never re-enters point-in-time reads — it was never a believed state. When the widened candidate set is not a clean trusted succession (untrusted valid-time or genuine overlap after narrowing), valid_at falls back to the line’s Contested result rather than picking a winner.
The two compose: the transaction-time filter is applied first, then valid-time selection narrows the result to a single claim. Supplying both answers the full bi-temporal question “what did the system believe, as of transaction time T, was true in the world at valid-time V?” See Query Patterns and Python (Advanced): Point-in-time valid-time query for worked examples, and the As-Of / Bi-Temporal Correctness Benchmark for a reproducible demonstration of the combined query.
Supplying valid-time in practice
Section titled “Supplying valid-time in practice”To enable succession, supply a valid-time window with high confidence when ingesting claims:
import mempillfrom 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", "valid_time_confidence": 0.9}, "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", "valid_time_confidence": 0.9}, # 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"]) # Resolvedprint(result["belief"]["primary"]["fact"]["value"]) # BobKey invariants honored
Section titled “Key invariants honored”- 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 toContested. - 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_memorycall. 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.
Next steps
Section titled “Next steps”- Claim Store: Bi-temporal — the two time axes and confidence scoring
- Contested and Dispositions — what happens when windows overlap
- The Adjudication Gate — how the reconciler feeds conflict classification to C7
- Key Invariants — I1, I2, I7, I8 in full detail