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 theBiTemporalFactdataclass. GraphBuildernormalizes temporal fields during ingestion, ensuring consistent storage insemantica/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
TemporalReasoningEngineto 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →