Operon v0.19: Bi-Temporal Memory

Append-Only Facts, Dual Time Axes, and Belief-State Reconstruction for Auditable Agent Systems

Bogdan Banu · March 2026 · github.com/coredipper/operon

Release: v0.19.0
Abstract

v0.18 gave operon a thinner front door. v0.19 goes deeper in the opposite direction: it adds a memory subsystem that tracks not just what is true, but when it became true and when the system learned about it. These are two different questions, and conflating them is the source of a whole class of bugs in agent systems that ingest delayed or corrected information. The new BiTemporalMemory is append-only — corrections close old records and create new ones, so any past belief state can always be reconstructed. This is the foundation for compliance auditing, temporal reasoning, and eventually richer forms of agent self-knowledge.

1. The Problem with a Single Timestamp

Most agent memory systems give each fact a single timestamp: when was this stored? That works fine as long as the system learns about things exactly when they happen. In practice, that is almost never the case.

A client’s risk tier changes on Monday. The CRM sync runs on Wednesday. A manual review on Friday discovers the tier should have been “high,” not “medium.” Now there are three different moments in play, and a single timestamp cannot distinguish between them.

This is not a niche scenario. It is the norm in compliance, healthcare, finance, and anywhere else where facts arrive with delay or get corrected retroactively. And it matters for agents because the question “what did the system believe when it made that decision?” is not the same as “what is true now?”

The Core Distinction

Valid time is when a fact is true in the world. Record time is when the system learned about it. These diverge whenever information arrives late or gets corrected — and an agent that conflates them cannot explain its own past decisions.

2. Bi-Temporal Data in Databases

The idea is not new. Snodgrass formalized bi-temporal data models in the 1990s, and SQL:2011 added standard syntax for temporal tables with PERIOD FOR and AS OF semantics. What is newer is bringing this into the agent memory layer directly, where the two time axes govern what the agent can retrieve and reason about at any given point.

The implementation in operon is deliberately simple. It is an in-memory append-only store with no database dependency. The point is not to compete with temporal databases. The point is to give agent workflows the right retrieval semantics without requiring external infrastructure.

3. The Data Model

A BiTemporalFact is a frozen dataclass with two time intervals:

BiTemporalFact(
    fact_id="a1b2c3d4",
    subject="client:42",
    predicate="risk_tier",
    value="medium",
    valid_from=day1,       # when the fact became true in the world
    valid_to=None,         # None = still true (open-ended)
    recorded_from=day3,    # when the system ingested it
    recorded_to=None,      # None = currently active record
    source="crm",
    confidence=1.0,
    tags=(),
    supersedes=None,       # points to the fact this corrects
)

The frozen dataclass is a deliberate departure from the mutable dataclasses used elsewhere in operon’s memory layer. The justification is simple: in an append-only store, corrections produce new facts rather than mutating existing ones. Immutability is not a constraint here — it is the semantics.

TypePurpose
BiTemporalFactImmutable fact with dual time intervals
BiTemporalQueryFilter spec for point-in-time queries
BiTemporalMemoryMutable store with append-only write semantics
FactSnapshotQuery result container
CorrectionResultResult of correct_fact()

4. Write Semantics: Record, Correct, Never Mutate

The store exposes two write operations. record_fact() appends a new fact. correct_fact() closes the old record by setting its recorded_to and appends a new fact with supersedes pointing back to the original.

from operon_ai import BiTemporalMemory

mem = BiTemporalMemory()

# Day 1: risk tier becomes "medium" in the world
# Day 3: system ingests it
fact = mem.record_fact("client:42", "risk_tier", "medium",
                       valid_from=day1, recorded_from=day3, source="crm")

# Day 5: manual review corrects to "high", retroactive to day 1
correction = mem.correct_fact(fact.fact_id, "high",
                              valid_from=day1, recorded_from=day5,
                              source="manual_review")

The old fact is never deleted or overwritten. Its recorded_to is set to the correction time, closing it on the record-time axis. The new fact carries a fresh fact_id and an open recorded_to. Both coexist in the store. This is what makes past belief states reconstructible.

Why Append-Only

If you mutate facts in place, you lose the ability to answer “what did the system believe at decision time?” That question is the foundation of compliance auditing. Append-only semantics make it structurally impossible to lose that information.

5. Three Retrieval Questions

The dual time axes give rise to three distinct retrieval modes, each answering a different question:

What is true at time T?

# Active records valid at day 2
mem.retrieve_valid_at(at=day2, subject="client:42")  # → "high"

This filters on the valid-time axis and only returns currently active records (those with open recorded_to). It reflects the corrected world-state.

What had the system recorded by time T?

# What was known by day 2?
mem.retrieve_known_at(at=day2, subject="client:42")  # → [] (nothing yet)

This filters on the record-time axis only. It answers “what information had arrived by this point?” without judging whether it was valid. Since the fact was not ingested until day 3, the result is empty.

What did the system believe at time T?

# Belief state: valid at day 2, known by day 4
mem.retrieve_belief_state(at_valid=day2, at_record=day4)  # → "medium"

# Same question, but known by day 6 (after correction)
mem.retrieve_belief_state(at_valid=day2, at_record=day6)  # → "high"

This is the intersection: facts that were both valid at at_valid and recorded by at_record. It reconstructs exactly what the agent would have believed at any past moment. The first query returns “medium” because the correction had not arrived yet. The second returns “high” because it had.

6. History, Diff, and Timeline

Three more methods support auditing and debugging:

# Full correction history
mem.history("client:42")

# What changed on the record-time axis between day 4 and day 6?
mem.diff_between(day4, day6, axis="record")

# World-time narrative
mem.timeline_for("client:42")

7. Relationship to Existing Memory Systems

Operon already has two memory subsystems, and bi-temporal memory is not a replacement for either. They solve different problems:

SystemTime ModelMutationUse Case
HistoneStore Single timestamp Mutable marks Epigenetic metadata on retrieval
EpisodicMemory Single timestamp Mutable (decay, promote) Tiered memory with relevance scoring
BiTemporalMemory Dual timestamps Append-only Auditable fact tracking with corrections

HistoneStore annotates retrievals with epigenetic marks — access frequency, recency, context. EpisodicMemory manages tiered decay and promotion between working, short-term, and long-term memory. Neither tracks corrections or supports dual-axis retrieval. BiTemporalMemory fills a different niche: domains where the audit trail is the point.

8. Article Updates

v0.19 also updates the research article in four places:

The article is built with tectonic. The temporal coalgebra formalization is the most interesting addition: it describes how the dual time intervals form a product of interval domains, and how corrections trace paths through that product space.

9. Validation

SuiteTestsStatus
Bi-temporal memory unit tests 41 All pass
Example 69 smoke path (valid vs record divergence) 1 Passes
Example 70 smoke path (compliance audit) 1 Passes
Full regression suite at release 928+ All pass
Reference Implementation: operon_ai/memory/bitemporal.py, examples/69_bitemporal_memory.py, examples/70_bitemporal_compliance_audit.py

10. What Comes Next

Bi-temporal memory is Phase 1 of a longer roadmap. The next phases build on this foundation: causal event logging that tracks why decisions were made, not just what facts were available; persistent storage backends so the append-only log survives process restarts; and eventually temporal reasoning primitives that let agents query their own epistemic history as part of planning.

The deeper motivation is that agents that cannot explain their past decisions are agents you cannot trust with consequential ones. Bi-temporal memory is the first step toward that kind of accountability — not as a governance layer bolted on after the fact, but as a structural property of the memory system itself.

Code and release: github.com/coredipper/operon, operon-ai on PyPI, bi-temporal memory docs