How Semantica's Bi-Temporal Graph Model Manages Valid Time vs. Recorded Time

Semantica stores every knowledge-graph relationship as a bi-temporal fact with four timestamps: valid_from/valid_until for real-world truth intervals and recorded_at/superseded_at for system-ingestion history.

The Semantica AGI framework implements a robust bi-temporal data model that independently tracks when facts are true in reality versus when they were recorded in the system. This architecture enables complete historical reconstruction of both domain state and system knowledge evolution.


What Is Bi-Temporal Modeling?

Bi-temporal modeling separates two distinct temporal dimensions for every fact in the knowledge graph:

Timeline Purpose Fields
Valid time When the fact is true in the real world valid_from, valid_until
Transaction time (recorded time) When the fact was entered or modified in the system recorded_at, superseded_at

Each relationship in Semantica carries both timelines, allowing queries to reconstruct the graph as it was known at any system time, or as it truly existed at any world time.


Core Implementation in temporal_model.py

The BiTemporalFact dataclass in semantica/kg/temporal_model.py encapsulates both timelines with automatic defaults and conversion utilities.

from semantica.kg import BiTemporalFact, TemporalBound

rel = {
    "source": "alice",
    "target": "acme_corp",
    "type": "ceo_of",
    "valid_from": "2018-01-01",
    "valid_until": "2022-06-01",
    # recorded_at omitted → auto-stamped at ingestion

}
fact = BiTemporalFact.from_relationship(rel)

print(fact.valid_from)   # 2018-01-01 00:00:00+00:00

print(fact.recorded_at)  # e.g. 2024-03-05 12:34:56+00:00 (auto-stamp)

Key implementation details from the source code:

  • TemporalBound.OPEN is the sentinel for unbounded intervals (ongoing facts with no valid_until or superseded_at)
  • _default_recorded_at provides fallback logic: uses valid_from if present, otherwise current UTC time
  • Factory method from_relationship handles ingestion-time stamping and boundary parsing
  • to_relationship_fields serializes back to storage-compatible format with ISO8601 strings

The file also contains helper functions parse_temporal_value, parse_temporal_bound, serialize_temporal_value, and serialize_temporal_bound for consistent temporal handling across the codebase.


Querying by Valid Time vs. Recorded Time

Semantica provides distinct query mechanisms for each timeline, implemented across temporal_query.py and provenance/manager.py.

Valid-Time Queries: "What Was True Then?"

from semantica.kg import TemporalGraphQuery

query = TemporalGraphQuery(temporal_granularity="day")
snapshot = query.reconstruct_at_time(kg, at_time="2020-05-01")

# Returns edges where valid_from ≤ 2020-05-01 < valid_until

The TemporalGraphQuery.query_at_time method filters on valid_from and valid_until to return the domain state as of a specific real-world date. This supports historical reasoning with Allen interval algebra (implemented in semantica/kg/temporal_reasoning.py).

Transaction-Time Queries: "What Did We Know Then?"

from semantica.provenance.manager import ProvenanceManager

mgr = ProvenanceManager(storage=my_storage)
history = mgr.revision_history(fact_id="rel-123")

# Each entry: version, valid_from, valid_until, recorded_at, author

ProvenanceManager.query_recorded_between and TemporalVersionManager.revision_history filter on recorded_at and superseded_at to reconstruct the system's knowledge state at prior ingestion moments. This enables audit trails and reproducibility.


How the Timelines Evolve Independently

A single relationship can undergo multiple changes that affect only one timeline:

  1. Valid-time extension: A contract's end date is extended from 2022-06-01 to 2024-12-31

    • New valid_until value
    • New recorded_at timestamp for the amendment
    • Prior version gets superseded_at stamped
  2. Correction without valid-time change: Fixing a typo in a relationship type

    • valid_from/valid_until unchanged
    • New recorded_at for the correction
    • Full provenance preserved

This separation ensures that domain semantics and system audit trails remain orthogonal concerns.


Key Files and Their Roles

File Role
semantica/kg/temporal_model.py Core bi-temporal data model (BiTemporalFact, parsing/serialization)
semantica/kg/temporal_reasoning.py Allen interval algebra operations on valid-time fields
semantica/kg/temporal_query.py Point-in-time and range queries across both timelines
semantica/provenance/manager.py Transaction-time provenance tracking
docs/reference/temporal.md Official documentation and API reference

Summary

  • Four timestamps per fact: valid_from, valid_until, recorded_at, superseded_at
  • Independent axes: Valid time tracks real-world truth; transaction time tracks system knowledge
  • Automatic defaults: recorded_at auto-stamps at ingestion if omitted
  • Open-ended support: TemporalBound.OPEN represents unbounded intervals
  • Query flexibility: Reconstruct domain state or system state at any historical point
  • Full provenance: Every amendment preserves prior versions with complete temporal metadata

Frequently Asked Questions

What happens if recorded_at is not provided when creating a relationship?

The BiTemporalFact.from_relationship factory falls back to the valid_from timestamp if present, otherwise uses datetime.now(utc) via the _default_recorded_at helper. This ensures every fact has a defined transaction time without requiring explicit user input.

How does Semantica represent facts that are currently true or currently known?

Open-ended intervals use the TemporalBound.OPEN sentinel. For valid time, this means no known end date to the fact's truth. For transaction time, it means the fact has not yet been superseded by a newer version.

Can I query for facts that were valid in the real world before the system recorded them?

Yes. Valid-time queries operate purely on valid_from/valid_until, independent of recorded_at. This allows retrospective modeling where domain history predates system ingestion—common when migrating legacy data into Semantica.

What's the difference between superseded_at and valid_until?

valid_until marks when a fact ceased being true in reality (domain time). superseded_at marks when a newer version replaced it in the system (recorded time). A fact can be superseded with a correction while its valid time remains unchanged, or its valid time can extend without any system-level superseding event.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →