The Problem: Time as an Afterthought

Every knowledge graph has temporal data. Almost none of them can query it well.

The dominant pattern is time as a qualifier: a triple like (:Napoleon :heldPosition :Emperor) gets a start date and end date as metadata hanging off it. This works for point lookups — “was Napoleon emperor in 1810?” — but it fails catastrophically for the queries that actually matter:

These queries are expensive because time is organized around triples instead of triples being organized around time.

The Insight: Time as a Leading Index Key

Loka already maintains three index orderings over every triple — SPO, POS, and OSP — so that any access pattern hits an index. Ontochronology adds a fourth ordering with time as the leading key:

IndexKey OrderAnswers
SPOSubject → Predicate → Object“What do we know about Alice?”
POSPredicate → Object → Subject“Who is a Person?”
OSPObject → Subject → Predicate“What links to Tokyo?”
VECTOR(p)Per-predicate HNSW graph“What’s semantically similar?”
TSPOTime → Subject → Predicate → Object“What existed at time T?”

With TSPO, “give me the complete world state at time T” is a single range scan on the first key component. No joins. No filter passes. No graph traversal. Every triple valid at T just falls out.

This is almost embarrassingly cheap. Time is 1D. A range scan on 1D ordered data is a B-tree lookup — the simplest index structure in computer science. Loka already pays the hard tax for HNSW (approximate search in 768+ dimensions). Temporal indexing is a rounding error by comparison.

Not Always a Clock

The “T” in TSPO is not necessarily a UTC timestamp. It is an ordered scalar — any value that can be sorted and range-scanned. The B-tree doesn’t care what the bytes represent. It only needs a total ordering.

DomainOrdering AxisExample Values
Historical eventsUTC timestamps (default)"1804-05-18", "2024-03-14T10:00"
ScreenplaysScene numbers1, 2, 3.5
Video productionFrame numbers0, 1, 2, …, 86400
Film continuityMinutes into movie0.0, 12.5, 90.3
NovelsPage numbers1, 42, 300
Religious textsBook.chapter.verse1.1.1, 66.22.21
Legal proceedingsExhibit / transcript page1, 2, 47

The axis type is a database-wide setting configured at creation time. You do not mix frame numbers and UTC timestamps in the same TSPO index — that would make range scans meaningless.

# Database creation with non-default axis
loka create movie.sdb --temporal-axis=integer    # frame/scene numbers
loka create scripture.sdb --temporal-axis=float  # chapter.verse as float
loka create events.sdb                            # default: UTC timestamp
Same index, different semantics. The SPARQL+ operators (AT_TIME, DURING, WORLD_STATE, TEMPORAL_DIFF) work identically regardless of axis type. The operator names reference time because that’s the common case, but they accept any ordered scalar matching the database’s configured axis.

Three Ways to Say When

Every triple can carry up to three temporal signifiers. None are required — some facts are intrinsically atemporal. A triple can have any combination, and can have multiple valid time intervals.

SignifierMeaningWhen to Use
Assertion time “Known to be the case at this time” When start/end times are unknown. The fallback — a proxy for “true as of when we recorded it.”
Start time “Became true at this time” When the onset is known.
End time “Stopped being true at this time” When the termination is known. Absent = still true.

Assertion time is the crutch. In a perfect world, every fact would have known start and end times. But for most historical data, most extracted data, and most real-time observations, we don’t know when a state began or ended. We only know it was observed at a certain time.

Precision, Not Confidence

Timestamps carry a precision level derived from their format:

Precise

"1977-10-29"^^loka:temporal
→ day precision

Imprecise

"1909"^^loka:temporal
→ year precision

Precision is not confidence. A fact with year-level precision isn’t “less certain” — the granularity is genuinely part of the truth claim. “Born in 1909” is as certain as “died October 29, 1977” — we just don’t have a more specific date.

RDF-star Representation

Temporal data uses RDF-star annotations — statements about statements:

# Assertion time only (the crutch)
<< :building_42 :locatedIn :MainStreet >>
    loka:assertedAt "1847"^^loka:temporal .

# Full valid-time interval
<< :napoleon :heldPosition :Emperor >>
    loka:validFrom "1804-05-18"^^loka:temporal ;
    loka:validTo   "1814-04-11"^^loka:temporal .

# Open-ended (still true)
<< :alice :worksAt :Acme >>
    loka:validFrom "2023-01-15"^^loka:temporal .

# Atemporal (no temporal annotation at all)
:water :chemicalFormula "H2O" .

SPARQL+ Temporal Operators

New operators scope graph patterns to specific moments or intervals:

AT_TIME — Point-in-time query

# Who was where at 10am on March 14th?
SELECT ?person ?location WHERE {
  AT_TIME("2024-03-14T10:00:00"^^xsd:dateTime) {
    ?person :locatedIn ?location .
    ?person rdf:type :Suspect .
  }
}

DURING — Interval query

# Who was at the courthouse during the hearing?
SELECT ?person ?location WHERE {
  DURING("2024-03-14T09:00", "2024-03-14T11:00") {
    ?person :locatedIn :Courthouse .
  }
}

WORLD_STATE — Complete snapshot

# Everything that was true at this moment
SELECT ?s ?p ?o WHERE {
  WORLD_STATE("2024-03-14T10:00:00"^^xsd:dateTime) {
    ?s ?p ?o .
  }
}

TEMPORAL_DIFF — What changed?

# What changed between scene 3 and scene 4?
SELECT ?change ?s ?p ?o WHERE {
  TEMPORAL_DIFF("scene_3_end", "scene_4_start") {
    ?s ?p ?o .
    BIND(loka:changeType AS ?change)
  }
}

Composition with Vector Search

# Find semantically similar documents about people
# who existed at a specific time
SELECT ?doc ?entity WHERE {
  AT_TIME("2024-06-01"^^xsd:dateTime) {
    ?entity rdf:type :Person .
    ?doc :mentions ?entity .
  }
  VECTOR_SIMILAR(?doc :hasEmbedding "..."^^loka:f32vec, 0.85)
}

Use Cases

Text-to-Video Continuity

Track every entity across scenes. “Who is holding the knife? What are they wearing? Was the door open?” Continuity errors become constraint violations detected by query.

Legal Investigation

Reconstruct timelines from depositions. “Where was the defendant during the relevant window? Which witness statements conflict?” Chain of custody as a temporal graph trace.

Historical Knowledge

Model entities with imprecise temporal bounds. Year-level precision for births, day-level for deaths. Both stored without forcing false precision.

GraphRAG Provenance

Track which extraction pass produced which triples, from which source, at which confidence. “What did we know at time T” vs “what do we know now.”

The Dimensional Spectrum

Ontochronological indexing is one instance of a general pattern: promote the query axis to a leading index key. The same approach extends to coordinates, provenance, and confidence:

DimensionIndexData StructureQuery
Time (1D)TSPOB-tree“Everything at time T”
Coordinates (2–3D)XYSPOR-tree / composite B-tree“Everything at location L”
CombinedTXYSPOComposite range scan“Everything at L during T”
Embeddings (768D+)VECTOR(p)HNSW“Semantically similar”
Loka already pays the hard tax for the high-dimensional end. HNSW solves approximate search in hundreds of dimensions. Temporal and spatial indexing are just B-trees — exact, cheap, and well understood since the 1970s. They are essentially free once you have HNSW.

Architecture

Ontochronological features are purely additive:

Three new reserved predicates trigger temporal indexing:

PredicatePurpose
loka:assertedAtPoint attestation — “known true at this time”
loka:validFromInterval start — “became true at this time”
loka:validToInterval end — “stopped being true at this time”

The query planner treats TSPO exactly like it treats SPO, POS, OSP, and VECTOR — as an access path with cost estimates. Temporal queries that benefit from TSPO get routed there. Queries that don’t simply ignore it.