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, or datetime objects to UTC-aware datetimes
  • serialize_temporal_value: Converts internal representations to ISO-8601 format with compact "Z" suffix
  • relationship_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.OPEN usage 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 entity
  • compare_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: BiTemporalFact in temporal_model.py normalizes valid-time and transaction-time metadata on every edge
  • Version layer: TemporalVersionManager in managers.py provides snapshot creation, tagging, and persistence with configurable storage backends
  • Reasoning layer: temporal_reasoning.py implements 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:

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 →