# Performing Time Travel Queries on Historical Graph States in Semantica: A Complete Guide

> Master time travel queries on historical graph states in Semantica. Explore bi-temporal validity tracking and reconstruct past graph data with this comprehensive guide.

- Repository: [Semantica /semantica](https://github.com/semantica-agi/semantica)
- Tags: how-to-guide
- Published: 2026-08-18

---

**Semantica enables time-travel queries through bi-temporal validity tracking (`valid_from`, `valid_until`) on entities and relationships, allowing you to reconstruct historical graph states using `TemporalGraphQuery` and persist snapshots via `TemporalVersionManager`.**

The Semantica knowledge graph engine stores facts with temporal boundaries, making performing time travel queries on historical graph states in Semantica a native capability rather than an afterthought. By leveraging bi-temporal metadata and dedicated query classes, you can retrieve precise graph snapshots from any point in time, analyze evolution patterns, and maintain immutable audit trails.

## Understanding the Temporal Data Model

Semantica represents temporal knowledge through **relationships** (and optionally entities) that carry explicit validity intervals. These intervals are stored as **BiTemporalFact** objects defined in [`semantica/kg/temporal_model.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/kg/temporal_model.py):

```python

# In semantica/kg/temporal_model.py

@dataclass
class BiTemporalFact:
    valid_from: Optional[datetime] = None
    valid_until: Optional[datetime] = None
    transaction_time: Optional[datetime] = None   # optional audit timestamp

```

When the `GraphBuilder` ingests data or the MCP tool adds relationships, the temporal fields are normalized by `deserialize_relationship_temporal_fields` and stored unchanged in the underlying graph structure. This bi-temporal approach separates **valid time** (when a fact was true in the real world) from **transaction time** (when it was recorded in the system).

## Point-in-Time Graph Reconstruction

The primary mechanism for retrieving historical states is the `query_at_time` method in [`semantica/kg/temporal_query.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/kg/temporal_query.py), implemented by the **`TemporalGraphQuery`** class:

```python

# semantica/kg/temporal_query.py → query_at_time

def query_at_time(self, graph, query, at_time, include_history=False, **options):
    query_time = self._parse_time(at_time)
    reconstructed_graph = self.reconstruct_at_time(graph, query_time)
    # …return entities + relationships active at `query_time`

```

The reconstruction process follows three distinct steps:

1. **Parse** the supplied timestamp using `_parse_time` to handle various datetime formats.
2. **Reconstruct** a sub-graph via `reconstruct_at_time` that contains only entities whose `valid_from/valid_until` span the target timestamp, and relationships that are temporally active according to `_relationship_active_at_time`.
3. **Validate** referential integrity to ensure no dangling edges exist in the filtered result.

The heavy lifting occurs in `reconstruct_at_time`:

```python
def reconstruct_at_time(self, graph, at_time, *, time_axis="valid"):
    query_time = at_time if isinstance(at_time, datetime) else self._parse_time(at_time)
    reconstructed = copy.deepcopy(graph)
    entity_index = {
        self._entity_id(e): e
        for e in graph.get("entities", [])
        if self._entity_active_at_time(e, query_time, time_axis=time_axis)
    }
    # Keep only relationships whose temporal axis is active at `query_time`

```

This method builds an **entity index** of active entities and filters relationships accordingly, guaranteeing that the resulting sub-graph obeys referential integrity.

## Querying Across Time Ranges

For analyzing periods rather than specific moments, use **`query_time_range`**:

```python
def query_time_range(self, graph, query, start_time, end_time, **options):
    start, end = map(self._parse_time, (start_time, end_time))
    # Scan relationships and keep those intersecting the interval [start, end]

```

This method scans every relationship in the graph and retains those whose validity interval overlaps the requested range. The result reflects **all** facts that were true *anywhere* within the specified interval, enabling trend analysis and period-based auditing.

## Detecting Temporal Patterns and Evolution

Beyond simple retrieval, `TemporalGraphQuery` integrates **`TemporalPatternDetector`** and **`TemporalReasoningEngine`** for advanced analytics:

- **Pattern detection** (`query_temporal_pattern`) uncovers recurring sequences, cycles, or trends across the temporal axis.
- **Evolution analysis** (`analyze_evolution`) aggregates metrics such as new versus retired entities per interval to provide a high-level view of graph changes.

Both features rely on low-level helpers like `_relationship_active_at_time` and `_compare_times`. You can invoke them as follows:

```python
tgq = TemporalGraphQuery()
patterns = tgq.query_temporal_pattern(kg, pattern_type="cycle")
evolution = tgq.analyze_evolution(kg, granularities=["year"])

```

## Persisting and Comparing Graph Versions

While `TemporalGraphQuery` operates on **in-memory** graphs, **`TemporalVersionManager`** adds **persistent** versioning capabilities. Found in [`semantica/kg/temporal_query.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/kg/temporal_query.py) (and utilizing storage backends from [`semantica/change_management/managers.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/change_management/managers.py)), this component enables snapshot management:

```python

# semantica/kg/temporal_query.py → TemporalVersionManager

manager = TemporalVersionManager(storage_path="versions.db")   # SQLite backend

snapshot = manager.create_version(graph, version_label="v2024-01")
versions = manager.list_versions()
diff = manager.compare_versions("v2023-12", "v2024-01")

```

**Key capabilities include:**

- **Snapshots**: `create_version` copies current entities and relationships into a lightweight dictionary, adding a timestamp and optional metadata.
- **Persistent storage**: When a `storage_path` is supplied, snapshots serialize to an SQLite database via `SQLiteVersionStorage`; otherwise, `InMemoryVersionStorage` maintains transient copies.
- **Diffing**: `compare_versions` loads two snapshots (by label or dictionary) and produces detailed change reports—added, removed, or modified entities and relationships—essential for audit trails and change-impact analysis.

## Visualizing Historical Graph States

For interactive exploration, the optional **`TemporalVisualizer`** class (located in [`semantica/visualization/temporal_visualizer.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/visualization/temporal_visualizer.py)) renders timelines and version histories. It wraps Plotly for interactive charts, with graceful degradation if dependencies are missing:

```python
from semantica.visualization.temporal_visualizer import TemporalVisualizer

tgq = TemporalGraphQuery()
kg_snapshot = tgq.reconstruct_at_time(kg, "2023-06-15")
viz = TemporalVisualizer()
viz.visualize_timeline(kg_snapshot, output="interactive")   # interactive timeline

viz.visualize_version_history(version_manager.list_versions(), output="static")

```

If Plotly is unavailable, the visualizer raises a `ProcessingError` (covered by the test suite) rather than failing silently.

## End-to-End Workflow Example

All modules are independently importable, enabling a **pipeline-style** workflow:

```python
from semantica.ingest import FileIngestor
from semantica.kg import GraphBuilder, TemporalGraphQuery, TemporalVersionManager

# 1️⃣ Ingest data

docs = FileIngestor().ingest_directory("./contracts/")

# 2️⃣ Build KG (temporal fields are automatically normalised)

builder = GraphBuilder()
kg = builder.build(docs)

# 3️⃣ Take a point‑in‑time snapshot

tgq = TemporalGraphQuery()
snapshot_2022 = tgq.reconstruct_at_time(kg, "2022-12-31")

# 4️⃣ Persist the snapshot

manager = TemporalVersionManager(storage_path="kg_versions.db")
manager.create_version(snapshot_2022, version_label="v2022‑end")

```

This architecture cleanly separates **querying**, **versioning**, and **visualization**, making it straightforward to embed temporal reasoning into data pipelines, multi-agent workflows, or compliance-driven audit systems.

## Summary

- **Bi-temporal modeling** in [`semantica/kg/temporal_model.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/kg/temporal_model.py) stores `valid_from` and `valid_until` on every fact, enabling precise historical reconstruction.
- **`TemporalGraphQuery`** provides `query_at_time` for point-in-time snapshots and `query_time_range` for interval-based analysis.
- **`reconstruct_at_time`** performs the heavy lifting of filtering entities and relationships while maintaining referential integrity.
- **`TemporalVersionManager`** persists named snapshots to SQLite or memory and supports diffing between versions for audit trails.
- **`TemporalVisualizer`** offers optional Plotly-based timelines for human inspection of historical states.

## Frequently Asked Questions

### What is the difference between `query_at_time` and `reconstruct_at_time`?

`query_at_time` is the high-level API that parses the input timestamp, calls `reconstruct_at_time` to build the filtered sub-graph, and returns the result with metadata such as entity counts. `reconstruct_at_time` is the lower-level engine that performs the actual graph filtering, building an entity index of active nodes and removing relationships that fall outside the temporal boundary. You typically use `query_at_time` for standard retrieval, while `reconstruct_at_time` is useful when you need the raw graph object for further manipulation.

### How does Semantica store temporal validity information?

Temporal validity is stored via the `BiTemporalFact` dataclass in [`semantica/kg/temporal_model.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/kg/temporal_model.py), which attaches `valid_from` and `valid_until` fields to relationships and optionally to entities. When data is ingested through the `GraphBuilder` or MCP tools, the `deserialize_relationship_temporal_fields` function normalizes these values before storage, ensuring consistent datetime handling throughout the query engine.

### Can I compare two different historical versions of a knowledge graph?

Yes. The `TemporalVersionManager` class provides `compare_versions`, which accepts two version labels (for example, `"v2023-12"` and `"v2024-01"`) and produces a detailed diff report. This report identifies added, removed, and modified entities and relationships, making it ideal for audit trails and understanding change impact between any two points in history.

### What storage backends are available for temporal version management?

`TemporalVersionManager` supports two storage backends: `SQLiteVersionStorage` for persistent, disk-based versioning (specified via the `storage_path` parameter), and `InMemoryVersionStorage` for transient, session-based snapshots when no path is provided. The SQLite backend is recommended for production audit requirements, while the in-memory option suits temporary analysis or testing scenarios.