How to Configure Bi-Temporal Facts Using valid_time vs recorded_time in Semantica
Semantica manages bi-temporal data through two orthogonal dimensions—valid_time representing when a fact is true in the real world, and recorded_at tracking when the system ingested the fact—with the ProvenanceManager automatically handling system timestamps while allowing explicit control over business-time validity periods.
The semantica-agi/semantica repository implements a bi-temporal knowledge graph that distinguishes between when facts are true in reality versus when they were recorded in the system. Understanding how to configure bi-temporal facts using valid_time versus recorded_time enables sophisticated audit trails and historical reasoning across your data.
Understanding Valid-Time vs Recorded-Time
Semantica stores temporal information across two independent axes. This separation allows you to answer both "When was this true?" and "When did we know it?" simultaneously.
Valid-Time (Business Time)
Valid-time represents the period during which a fact is intended to be true in the real world. In Semantica's implementation, this is typically expressed as valid_from and valid_until timestamps on entities, or as a single valid_time property on relationships. You populate these values from source data or supply them explicitly when creating facts.
Recorded-Time (System Time)
Recorded-time (stored as recorded_at) indicates when the system actually recorded the fact. This dimension serves audit, versioning, and provenance requirements. According to the source code in semantica/provenance/manager.py, the ProvenanceManager automatically injects the current ISO-8601 timestamp via datetime.now().isoformat() if you omit this field when adding entries (see lines 907-913 in the _record_entry() method).
Adding Bi-Temporal Facts to the Graph
When constructing your knowledge graph, you explicitly set valid-time boundaries while allowing the system to handle recorded-time automatically.
Setting Valid-Time Properties
Use the GraphBuilder to create relationships with explicit valid_time properties. The system reads this property and stores it as valid_from on the edge, as implemented in GraphBuilder.load_from_neo4j() at lines 1204-1208 of semantica/kg/graph_builder.py.
from semantica.kg.graph_builder import GraphBuilder
builder = GraphBuilder()
builder.add_relationship(
source_id="employee_001",
target_id="company_acme",
rel_type="EMPLOYED_AT",
properties={
"valid_time": "2023-01-01T00:00:00Z/2025-12-31T23:59:59Z", # Business time
"department": "Engineering"
# "recorded_at" will be added automatically by ProvenanceManager
}
)
Automatic Recorded-Time Assignment
If you do not provide recorded_at in your payload, the ProvenanceManager automatically assigns the current system time. You only need to supply this field explicitly when you require a custom audit timestamp, such as when backfilling historical data with specific ingestion dates. The test suite in tests/test_401_temporal_provenance_export.py (lines 40-58) validates this auto-population behavior.
Querying Bi-Temporal Data
The ProvenanceManager exposes specific query helpers that filter on either temporal dimension independently or in combination.
Query by Valid-Time with query_as_of()
To retrieve facts that were valid at a specific business date regardless of when they were recorded, use the query_as_of() method. This method is defined at lines 1498-1510 in semantica/provenance/manager.py.
from semantica.provenance.manager import ProvenanceManager
prov_mgr = ProvenanceManager()
# Get all facts valid on March 15, 2024
current_facts = prov_mgr.query_as_of("2024-03-15T00:00:00Z")
Query by Recorded-Time with query_recorded_between()
For audit purposes, filter facts by when they entered the system using query_recorded_between(). This returns only facts recorded within your specified audit window.
# Retrieve facts recorded in Q1 2024 for compliance auditing
recent_facts = prov_mgr.query_recorded_between(
"2024-01-01T00:00:00Z",
"2024-03-31T23:59:59Z"
)
Combining Both Dimensions
True bi-temporal queries answer what was known as of a certain valid-time and what had been recorded by a specific audit time. Combine both filtering methods to achieve this.
# Facts valid on June 1, 2023 that were recorded by January 1, 2024
bi_temporal = prov_mgr.query_as_of("2023-06-01T00:00:00Z")
bi_temporal = [
f for f in bi_temporal
if f["recorded_at"] <= "2024-01-01T00:00:00Z"
]
The comprehensive test suite in tests/test_395_temporal_semantics_comprehensive.py (lines 240-254) validates that facts with valid_from values are queried correctly while recorded_at handles audit filtering independently.
Implementation Details in the Source Code
Several key components handle the bi-temporal logic:
-
semantica/kg/graph_builder.py(load_from_neo4j(), lines 1204-1208): Extracts thevalid_timerelationship property and persists it asvalid_fromon graph edges. -
semantica/provenance/manager.py(ProvenanceManagerclass): Contains_record_entry()(lines 907-913) for automaticrecorded_atinjection, and the query primitivesquery_as_of()andquery_recorded_between()(lines 1498-1510). -
tests/test_401_temporal_provenance_export.py(lines 40-58): Confirms that records receive automaticrecorded_attimestamps when none are provided. -
tests/test_395_temporal_semantics_comprehensive.py(lines 240-254): Validates the interaction between valid-time boundaries and recorded-time audit filters.
Summary
-
Bi-temporal facts separate business time (
valid_time) from system time (recorded_at) to enable historical reasoning and audit trails. -
Provide
valid_timeon relationships orvalid_from/valid_untilon entities to define when facts are true in reality. -
Allow auto-population of
recorded_atby theProvenanceManagerunless you need custom audit timestamps for backfilled data. -
Query by valid-time using
query_as_of()to see the business perspective at any historical point. -
Query by recorded-time using
query_recorded_between()for system audit and compliance checks. -
Combine both filters to answer sophisticated bi-temporal questions about what was known when.
Frequently Asked Questions
What is the difference between valid_time and recorded_at in Semantica?
valid_time represents the business period when a fact is true in the real world, while recorded_at tracks when the system actually stored the fact. The former answers "When did this happen?" and the latter answers "When did we learn about it?" These orthogonal dimensions enable bi-temporal reasoning across your knowledge graph.
How does Semantica automatically handle recorded_at timestamps?
The ProvenanceManager automatically injects recorded_at using datetime.now().isoformat() when you add facts without specifying this field. This occurs in the _record_entry() method within semantica/provenance/manager.py (lines 907-913), ensuring every fact has a system audit timestamp even if you only supply valid-time boundaries.
Can I query for facts that were valid at a certain time but recorded later?
Yes. First call query_as_of() with your target valid-time, then filter the results by checking the recorded_at field against your audit cutoff date. This bi-temporal pattern allows you to reconstruct what information was available to the system as of a specific audit date while respecting the actual business validity periods of the underlying facts.
Where are bi-temporal properties stored in the Semantica codebase?
Valid-time properties reside in the knowledge graph layer (semantica/kg/graph_builder.py), where valid_time on relationships becomes valid_from on edges. Recorded-time properties are managed by the provenance layer (semantica/provenance/manager.py), which handles automatic timestamp assignment and provides the query APIs for both temporal dimensions.
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 →