How Semantica Handles Bi-Temporal Facts in the ContextGraph: A Complete Guide

Semantica stores every relationship as a bi-temporal fact using four timestamps—valid_from/valid_until for real-world validity and recorded_at/superseded_at for system history—enabling point-in-time snapshots, full audit trails, and Allen algebra-based temporal reasoning.

The semantica-agi/semantica repository implements a sophisticated bi-temporal data model within its ContextGraph, ensuring that every edge captures both when a fact was true in reality and when it was recorded in the system. This dual-timeline approach prevents data loss during updates and supports complex historical queries across the knowledge graph.

The BiTemporalFact Data Model

At the core of Semantica’s temporal support is the BiTemporalFact dataclass defined in semantica/kg/temporal_model.py. This class wraps every relationship with explicit temporal boundaries, distinguishing between two independent but equally critical timelines.

Valid-Time vs. Transaction-Time

Each bi-temporal fact tracks four distinct timestamp fields:

  • Valid-time (valid_from, valid_until): Represents when the relationship held true in the real world. This interval is independent of the database state and answers "when was this fact actually valid?"
  • Transaction-time (recorded_at, superseded_at): Represents when the fact was stored or replaced in the ContextGraph. This provides the audit trail and answers "when did we know this?"

The system uses TemporalBound.OPEN to model intervals that extend indefinitely, allowing facts to remain valid or current without arbitrary end dates.

Factory Pattern and Normalization

The BiTemporalFact class provides the from_relationship() factory method to convert raw relationship dictionaries into fully-typed temporal objects. This method handles missing values, applies UTC normalization, and sets appropriate defaults for open-ended intervals. As implemented in semantica-agi/semantica, the factory ensures that legacy data without temporal fields gracefully degrades to sensible defaults while preserving type safety.

Building the ContextGraph with Temporal Support

When constructing the graph, GraphBuilder (located in semantica/kg/graph_builder.py) integrates bi-temporal facts through the deserialize_relationship_temporal_fields() utility. Initializing the builder with enable_temporal=True activates strict validation and automatic timestamp normalization for every incoming relationship.

During the build process, the ContextGraph normalizes each edge’s temporal fields before storage. This guarantees that downstream components can rely on consistent datetime representations and non-null temporal boundaries, regardless of input format variations.

Querying and Reasoning Over Time

The bi-temporal foundation enables three primary capabilities that distinguish Semantica from static knowledge graphs.

Point-in-Time Snapshots

The TemporalGraphQuery class in semantica/kg/temporal_query.py provides query_at_time() for reconstructing historical states. This method filters the ContextGraph to return only entities and relationships whose valid-time interval contains the requested timestamp.

When you query the graph "as of" June 15, 2023, the engine excludes relationships that started after that date or ended before it, producing a self-consistent sub-graph that reflects the real-world state at that exact moment.

Transaction-Time Versioning and Auditing

The TemporalVersionManager leverages recorded_at and superseded_at to maintain immutable history. When apply_revision() processes an update, it marks the existing fact with a superseded_at timestamp and inserts the new fact with a fresh recorded_at value.

This mechanism creates a complete audit trail without destructive updates, enabling "time-travel" queries that ask "what did the graph look like when we recorded this change?" rather than just "what was true in reality?"

Temporal Reasoning with Allen Algebra

The TemporalReasoningEngine in semantica/kg/temporal_reasoning.py applies formal Allen interval algebra to BiTemporalFact objects. It computes temporal relationships such as overlap, containment, precedence, and gaps between intervals across both the valid-time and transaction-time dimensions.

This engine powers advanced analytics including temporal path finding, conflict detection between overlapping claims, and consistency checking across the knowledge graph.

Practical Implementation Examples

The following patterns demonstrate how to create, store, query, and revise bi-temporal facts in the ContextGraph.


# Create a bi-temporal fact from raw relationship data

from semantica.kg import BiTemporalFact

raw_rel = {
    "subject": "Customer123",
    "predicate": "has_subscription",
    "object": "PremiumPlan",
    "valid_from": "2023-01-01",
    "valid_until": "2024-01-01",
    # recorded_at auto-filled; superseded_at defaults to OPEN

}
fact = BiTemporalFact.from_relationship(raw_rel)

# Build the ContextGraph with temporal normalization enabled

from semantica.kg import GraphBuilder

builder = GraphBuilder(enable_temporal=True)
graph = builder.build(sources=[{"relationships": [raw_rel]}])

# Query a point-in-time snapshot using valid-time filtering

from semantica.kg import TemporalGraphQuery

tgq = TemporalGraphQuery()
snapshot = tgq.query_at_time(graph, query="", at_time="2023-06-15")

# Returns only relationships valid on June 15, 2023

# Supersede the previous fact with a new revision

new_rel = {
    "subject": "Customer123",
    "predicate": "has_subscription",
    "object": "BasicPlan",
    "valid_from": "2024-01-02",
    "valid_until": "2025-01-01",
    "recorded_at": "2024-01-02T12:00:00Z",  # Transaction-time of this update

}
new_fact = BiTemporalFact.from_relationship(new_rel)
graph["relationships"].append(new_fact.to_relationship_fields())

Summary

  • Bi-temporal facts in Semantica capture both real-world validity (valid_from/valid_until) and system history (recorded_at/superseded_at) within the BiTemporalFact dataclass.
  • GraphBuilder normalizes temporal fields during ingestion, ensuring consistent storage in semantica/kg/graph_builder.py.
  • Point-in-time queries reconstruct historical graph states by filtering on valid-time intervals using TemporalGraphQuery.
  • Immutable versioning preserves full audit trails through transaction-time timestamps managed by TemporalVersionManager.
  • Temporal reasoning applies Allen interval algebra via TemporalReasoningEngine to detect conflicts, overlaps, and temporal paths.

Frequently Asked Questions

What is the difference between valid-time and transaction-time in Semantica?

Valid-time indicates when a fact was true in the real world, using valid_from and valid_until fields, while transaction-time tracks when the fact was recorded or superseded in the ContextGraph using recorded_at and superseded_at. This separation allows the system to distinguish between historical reality and the history of database changes.

How does Semantica handle relationships that are currently valid?

Semantica uses TemporalBound.OPEN to represent unbounded intervals. When a relationship has no specified end date, valid_until defaults to OPEN, indicating the fact remains valid indefinitely. Similarly, superseded_at remains OPEN until a newer version explicitly closes the interval.

Can I query the ContextGraph as it existed on a specific date?

Yes. The TemporalGraphQuery.query_at_time() method filters relationships to include only those whose valid-time range contains the specified timestamp. This returns a consistent sub-graph representing the real-world state at that exact moment, excluding future or expired relationships.

What temporal operations does the TemporalReasoningEngine support?

The TemporalReasoningEngine implements Allen interval algebra to compute relationships between temporal intervals, including overlaps, meets, contains, starts, finishes, and precedes. These operations apply across both valid-time and transaction-time dimensions to enable conflict detection, consistency validation, and temporal path analysis.

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 →