Bi-Temporal Memory

Append-only factual memory with dual time axes for auditable decision-making.

BiTemporalMemory tracks facts along two independent time axes:

Unlike HistoneStore (epigenetic marks) or EpisodicMemory (tiered decay), bi-temporal memory is append-only — corrections close old records and create new ones, so the full history is always reconstructible.

Why Two Time Axes?

A single timestamp conflates two different questions:

  1. “What is true now?” (valid-time query)
  2. “What did the system believe at decision time?” (record-time query)

These diverge whenever facts are ingested with delay or corrected retroactively — the norm in compliance, healthcare, and finance workflows.

Quick Start

from operon_ai import BiTemporalMemory

mem = BiTemporalMemory()

# Record: risk tier is "medium" (valid day 1, ingested day 3)
fact = mem.record_fact("client:42", "risk_tier", "medium",
                       valid_from=day1, recorded_from=day3, source="crm")

# Correct: actually "high", retroactive to day 1 (recorded day 5)
mem.correct_fact(fact.fact_id, "high",
                 valid_from=day1, recorded_from=day5, source="manual_review")

Querying

Three retrieval methods correspond to three questions:

# What is true at day 2? (active records only)
mem.retrieve_valid_at(at=day2, subject="client:42")        # → "high"

# What had the system recorded by day 2?
mem.retrieve_known_at(at=day2, subject="client:42")         # → [] (not yet ingested)

# What did the system believe on day 2, given what it knew by day 4?
mem.retrieve_belief_state(at_valid=day2, at_record=day4)    # → "medium"

# Same question, given what it knows by day 6 (after correction)?
mem.retrieve_belief_state(at_valid=day2, at_record=day6)    # → "high"

History and Audit

# Full correction history, sorted by record time
mem.history("client:42")

# What changed between two points on either axis?
mem.diff_between(day4, day6, axis="record")

# World-time timeline, sorted by valid_from
mem.timeline_for("client:42")

Append-Only Semantics

Corrections never mutate existing records. Instead:

  1. The old record’s recorded_to is set (closing it on the record-time axis).
  2. A new record is appended with supersedes pointing to the old fact_id.

This means any past belief state is always reconstructible — the foundation for compliance auditing.

Data Model

TypePurpose
BiTemporalFactImmutable fact with dual time intervals (frozen dataclass)
BiTemporalQueryFilter spec for point-in-time queries
BiTemporalMemoryMutable store with append-only write semantics
FactSnapshotQuery result container
CorrectionResultResult of correct_fact()
SubstrateViewFrozen read-only envelope of facts for stage handlers (v0.20.0)

All types are exported from the top level: from operon_ai import BiTemporalMemory, BiTemporalFact, SubstrateView, ...

Relationship to Other Memory Systems

SystemTime ModelMutationUse Case
HistoneStoreSingle timestampMutable marksEpigenetic metadata on retrieval
EpisodicMemorySingle timestampMutable (decay, promote)Tiered memory with relevance scoring
BiTemporalMemoryDual timestampsAppend-onlyAuditable fact tracking with corrections

SkillOrganism Integration

Since v0.20.0, BiTemporalMemory can serve as a shared substrate for SkillOrganism workflows. Pass substrate=BiTemporalMemory() to skill_organism(...), then use per-stage hooks to read and write facts:

SkillStage fieldPurpose
read_querySubject string or callable → SubstrateView injected before stage runs
fact_extractorCallable → emits assert/correct/invalidate events after stage runs
emit_output_factConvenience flag: auto-records (task, stage.name, output)
fact_tagsDefault tags applied to all facts emitted by this stage

Handlers receive the SubstrateView as an additional argument (via arity-aware dispatch — existing handlers are unaffected). See Skill Organisms for the full three-layer context model.

Examples