How to Implement Time-Travel Queries Using ContextGraph.state_at() in Semantica
Use ContextGraph.state_at() to retrieve an immutable, serializable snapshot of the graph at any historical moment by passing an ISO-8601 string, datetime object, or Unix epoch timestamp.
The semantica-agi/semantica framework provides native temporal support through the ContextGraph class, enabling you to audit historical states and reconstruct decision trails without mutating live data. By leveraging the state_at() method, you can implement robust time-travel queries that filter entities and relationships by their validity windows, making it ideal for compliance tracking and temporal reasoning applications.
How ContextGraph.state_at() Works Internally
The state_at() method in semantica/context/context_graph.py (lines 2499-2529) constructs historical views through three deterministic stages that validate temporal boundaries against your query timestamp.
Timestamp Normalization
Before filtering, the method normalizes your input via ContextGraph._normalize_timestamp() (lines 2510-2515). This internal helper accepts ISO-8601 strings, Unix epoch integers, or timezone-aware datetime objects, converting any format into a timezone-naive UTC datetime instance. This ensures consistent comparison logic regardless of how you specify the query moment.
Temporal Validity Checks
Each graph component implements an is_active(at_time) method that evaluates validity windows:
ContextNode.is_active()(lines 333-350): ReturnsTrueonly whenvalid_from≤at_time≤valid_until.ContextEdge.is_active()(lines 996-1013): Applies identical boundary checks to relationships.
These methods enforce closed-interval semantics, meaning entities are visible at their exact start and end timestamps.
Snapshot Construction
The final phase assembles a dictionary containing:
timestamp: The normalized UTC query time.nodes/entities: Active node payloads.edges/relationships: Active edge payloads.decisions: Temporal views of decision-type nodes (lines 2515-2528).
Because this operation creates a read-only view, you can safely execute multiple time-travel queries to reconstruct history or compare divergent states without side effects.
Practical Implementation Examples
Querying with Different Timestamp Formats
The following example demonstrates adding temporally bounded nodes and edges, then retrieving snapshots using three supported input types:
from semantica.context import ContextGraph
from datetime import datetime, timezone
g = ContextGraph()
# Create entities with explicit validity windows
g.add_node(
"patient_123",
node_type="entity",
content="John Doe",
valid_from="2023-01-01T00:00:00Z",
valid_until="2023-12-31T23:59:59Z",
)
g.add_edge(
"patient_123",
"diagnosis_456",
edge_type="has_diagnosis",
valid_from="2023-05-01",
valid_until="2023-07-31",
)
# Three equivalent ways to query June 15, 2023
snapshot_iso = g.state_at("2023-06-15T12:00:00Z")
snapshot_dt = g.state_at(datetime(2023, 6, 15, tzinfo=timezone.utc))
snapshot_ts = g.state_at(1686825600) # Unix epoch seconds
assert "patient_123" in [n["id"] for n in snapshot_iso["nodes"]]
assert any(e["type"] == "has_diagnosis" for e in snapshot_iso["edges"])
Reconstructing Historical Views After Retractions
When you retract an entity using retract_node(), the operation updates the valid_until boundary rather than deleting data. This preserves historical views while hiding entities from future snapshots:
# Close the patient's validity as of August 1, 2023
g.retract_node(kind="entity", node_id="patient_123", at="2023-08-01T00:00:00Z")
# Future queries exclude the retracted node
future = g.state_at("2023-09-01")
assert not any(n["id"] == "patient_123" for n in future["nodes"])
# Past queries still include the entity
past = g.state_at("2023-07-01")
assert any(n["id"] == "patient_123" for n in past["nodes"])
Auditing Decisions at Specific Points in Time
The decisions key provides filtered access to historical decision records. This pattern supports compliance audits and provenance tracking:
g.record_decision(
category="loan_approval",
scenario="first_homebuyer",
reasoning="good credit",
outcome="approved",
confidence=0.95,
entities=["customer_001"],
valid_from="2024-01-01",
valid_until="2024-06-30",
)
g.record_decision(
category="loan_approval",
scenario="first_homebuyer",
reasoning="low income",
outcome="rejected",
confidence=0.85,
entities=["customer_001"],
valid_from="2024-07-01",
valid_until="2024-12-31",
)
# February snapshot returns the approval
snap_feb = g.state_at("2024-02-15")
print([d["outcome"] for d in snap_feb["decisions"]]) # ['approved']
# August snapshot returns the rejection
snap_aug = g.state_at("2024-08-01")
print([d["outcome"] for d in snap_aug["decisions"]]) # ['rejected']
Summary
ContextGraph.state_at()is the primary entry point for time-travel queries in Semantica, accepting ISO-8601 strings, datetime objects, or Unix epochs.- The method relies on
_normalize_timestamp()(lines 2510-2515) to ensure consistent UTC comparison logic. - Temporal validity is enforced through
is_active()methods onContextNode(lines 333-350) andContextEdge(lines 996-1013) using closed-interval bounds. - Returned snapshots contain
nodes,edges, anddecisionsarrays filtered to the query moment without mutating the underlying graph. - This architecture enables immutable historical auditing, difference analysis between timestamps, and temporal provenance tracking.
Frequently Asked Questions
What timestamp formats does ContextGraph.state_at() accept?
According to the semantica-agi/semantica source code, the method accepts ISO-8601 formatted strings (e.g., "2023-06-15T12:00:00Z"), Python datetime objects (timezone-aware or naive), and Unix epoch integers representing seconds since 1970-01-01 UTC. The internal _normalize_timestamp() helper within context_graph.py (lines 2510-2515) handles conversion to a standardized timezone-naive UTC datetime.
Does calling state_at() modify the underlying graph?
No. The state_at() method is strictly read-only. It constructs a new serializable dictionary containing copies of active node and edge data without altering the in-memory graph structure. This immutability guarantee allows you to safely call the method multiple times to compare different historical states or generate audit trails.
How does Semantica handle timezone-aware datetime objects?
The _normalize_timestamp() implementation converts all timezone-aware datetime objects to timezone-naive UTC before comparison. This ensures that valid_from and valid_until boundaries are evaluated consistently regardless of the input timezone, preventing off-by-hour errors in temporal queries.
Can I use state_at() to detect changes between two points in time?
Yes. By capturing two snapshots at different timestamps and comparing their nodes, edges, and decisions arrays, you can compute temporal diffs. Because each snapshot returns the exact set of active elements at that moment, you can identify additions, removals, or modifications by comparing entity IDs and their validity windows between the two dictionaries.
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 →