How to Build Bi-Temporal Knowledge Graphs with Semantica: Complete Developer Guide
Semantica provides a full-stack framework for creating and querying bi-temporal knowledge graphs through three integrated layers: the TemporalModel for data structures, TemporalVersionManager for version control, and temporal reasoning engines for time-aware queries.
Building bi-temporal knowledge graphs requires tracking two distinct time dimensions: valid time (when a fact is true in the real world) and transaction time (when the fact was recorded in the system). Semantica, an open-source knowledge graph framework, implements this capability through a specialized semantica/kg package that combines low-level temporal modeling with high-level versioning and query interfaces.
This guide walks through the complete implementation pipeline using actual source code from the semantica-agi/semantica repository.
Understanding Bi-Temporal Data in Semantica
What Makes Semantica's Approach Distinctive
Unlike standard knowledge graphs that treat relationships as static, Semantica's bi-temporal model captures the full lifecycle of every edge. This enables powerful capabilities:
- Point-in-time queries: Retrieve the graph state as it existed at any specific moment
- Rollback and auditing: Trace when information entered the system and when it was superseded
- "What-if" analysis: Create hypothetical versions without affecting production data
- Compliance and provenance: Meet regulatory requirements for data lineage tracking
The implementation spans four core modules that work together seamlessly.
The Temporal Model Layer: BiTemporalFact
The foundation lives in semantica/kg/temporal_model.py. This module defines BiTemporalFact, a wrapper that normalizes temporal metadata on every relationship.
Core Data Structures
A BiTemporalFact attaches four critical fields to each edge:
| Field | Time Dimension | Purpose |
|---|---|---|
valid_from |
Valid time | When the relationship became true in reality |
valid_until |
Valid time | When the relationship ceased being true |
recorded_at |
Transaction time | When the system first captured this fact |
superseded_at |
Transaction time | When a newer version replaced this record |
The TemporalBound.OPEN constant indicates an unbounded interval—either "still true" for valid time or "current record" for transaction time.
Temporal Utilities
temporal_model.py provides essential helper functions:
parse_temporal_value: Normalizes strings, numbers, ordatetimeobjects to UTC-aware datetimesserialize_temporal_value: Converts internal representations to ISO-8601 format with compact "Z" suffixrelationship_to_json_ready: Ensures round-trip serialization without losing temporal semantics
These utilities handle edge cases like timezone conversions and open-ended intervals automatically.
Version Management with TemporalVersionManager
The versioning system in semantica/change_management/managers.py implements a lightweight VCS for knowledge graphs. This component is exposed through semantica/kg/temporal_query.py as the public TemporalVersionManager API.
Key Operations
| Method | Function |
|---|---|
create_version(graph, timestamp, version_label) |
Captures a complete snapshot at a specific transaction time |
list_tags() |
Returns all stored version labels (e.g., "v2020", "v2023") |
tag_version(label) |
Assigns human-readable identifiers to snapshots |
validate_snapshot(snapshot) |
Enforces bi-temporal constraints and detects anomalies |
The manager persists snapshots to configurable backends including SQLite, PostgreSQL, or any custom VectorStore implementation.
Snapshot Integrity
validate_snapshot() performs critical checks:
- No overlapping transaction intervals for the same edge
- Proper
TemporalBound.OPENusage for current records - Consistent valid-time ranges within transaction-time boundaries
These validations prevent the data corruption patterns common in naive bi-temporal implementations.
Temporal Reasoning and Querying
The reasoning layer in semantica/reasoning/temporal_reasoning.py enables sophisticated time-aware analysis.
Allen Interval Relations
Semantica implements the full set of Allen interval algebra relations for comparing temporal intervals:
from semantica.reasoning.temporal_reasoning import TemporalReasoner
reasoner = TemporalReasoner()
relation = reasoner.allen_relation(fact_a, fact_b)
# Returns: 'before', 'meets', 'overlaps', 'starts', 'during', 'finishes', 'equal', etc.
Access this through Python API or the CLI: semantica temporal allen ...
Graph Analytics
version_history(entity_id): Returns chronological state series for any entitycompare_snapshots(v1, v2): Identifies added, removed, and modified edges between versions
These operations support forensic analysis, change detection, and longitudinal studies of knowledge evolution.
Visualization Capabilities
The semantica/visualization/temporal_visualizer.py module provides interactive rendering through Plotly.
Available Views
| Method | Output |
|---|---|
visualize_timeline(kg, output="interactive") |
Interactive timeline of all temporal edges |
visualize_version_history(history, timestamps, output="interactive") |
Entity property evolution over transaction time |
These visualizations are exercised in tests/visualization/reproduce_notebooks.py with comprehensive examples.
Complete Implementation Examples
Creating a Bi-Temporal Knowledge Graph
from semantica.kg.knowledge_graph import KnowledgeGraph
from semantica.kg.temporal_model import BiTemporalFact
kg = KnowledgeGraph()
# Add a bi-temporal edge with explicit valid-time bounds
edge = {
"source": "Alice",
"target": "AcmeCorp",
"type": "EMPLOYED_BY",
"valid_from": "2020-01-01",
"valid_until": "2022-06-30"
# recorded_at auto-populated; superseded_at defaults to OPEN
}
kg.add_relationship(edge)
The add_relationship call internally wraps the dict in BiTemporalFact to normalize temporal fields.
Capturing Version Snapshots
from semantica.kg.temporal_query import TemporalVersionManager
tvm = TemporalVersionManager(storage_path="tmp/semantica.db")
# Snapshot at specific transaction time with semantic label
snapshot = tvm.create_version(
kg,
timestamp="2021-03-15",
version_label="v2021_q1"
)
Transaction time (recorded_at) is set to the snapshot timestamp for all new edges.
Querying Entity History
history = tvm.version_history(entity_id="Alice")
for version, ts in history:
print(f"At {ts.isoformat()} -> {version['relationships']}")
Returns (state, timestamp) tuples in chronological order.
Rendering Interactive Timelines
from semantica.visualization.temporal_visualizer import TemporalVisualizer
viz = TemporalVisualizer()
viz.visualize_timeline(snapshot, output="interactive")
Outputs a Plotly figure showing valid-time intervals colored by relationship type.
Comparing Historical States
v1 = tvm.create_version(kg, timestamp="2020-12-31", version_label="v2020")
v2 = tvm.create_version(kg, timestamp="2022-12-31", version_label="v2022")
diff = tvm.compare_snapshots(v1, v2)
print("Added edges:", diff["added"])
print("Removed edges:", diff["removed"])
print("Changed edges:", diff["changed"])
The diff structure identifies structural changes between any two transaction-time points.
Summary
Building bi-temporal knowledge graphs with Semantica requires understanding three integrated layers:
- Model layer:
BiTemporalFactintemporal_model.pynormalizes valid-time and transaction-time metadata on every edge - Version layer:
TemporalVersionManagerinmanagers.pyprovides snapshot creation, tagging, and persistence with configurable storage backends - Reasoning layer:
temporal_reasoning.pyimplements Allen-interval relations, history queries, and snapshot comparison
The framework enforces bi-temporal constraints automatically, supports interactive visualization, and exposes both Python API and CLI interfaces for diverse workflow integration.
Frequently Asked Questions
What is the difference between valid time and transaction time in Semantica?
Valid time represents when a fact was true in the modeled world (e.g., Alice worked at AcmeCorp from January 2020 to June 2022). Transaction time represents when the system recorded that fact (e.g., the employment relationship was entered on March 15, 2021). Semantica tracks both through independent interval fields on every BiTemporalFact, enabling queries like "What did we believe on date X about the world on date Y?"
How does TemporalVersionManager store multiple versions efficiently?
The manager stores complete snapshots with copy-on-write semantics: edges are annotated with recorded_at and superseded_at timestamps rather than duplicated. When a new version is created, existing edges receive superseded_at markers only if modified. This approach, implemented in semantica/change_management/managers.py, balances query performance with storage efficiency across SQLite, PostgreSQL, or custom backends.
Can I migrate an existing knowledge graph to bi-temporal storage?
Yes. When adding relationships to a KnowledgeGraph without temporal fields, BiTemporalFact automatically assigns recorded_at to the current UTC time and sets both valid_from and valid_until to None. For historical data, populate valid_from and valid_until explicitly before calling add_relationship. The parse_temporal_value function in temporal_model.py accepts ISO-8601 strings, Unix timestamps, or datetime objects for flexible data ingestion.
How do I query the graph as it existed at a specific point in history?
Use TemporalVersionManager.create_version() with your target timestamp as both the snapshot time and filter predicate. The resulting snapshot contains only edges where recorded_at <= timestamp < superseded_at (transaction-time filter) and optionally where valid_from <= timestamp < valid_until (valid-time filter). For ad-hoc queries without persisting a snapshot, the temporal_query.py module provides helper functions that apply these filters dynamically.
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 →