Bi-temporal Model
How CortexDB tracks two independent time axes — when something was true in the world, and when the system learned it.
CortexDB records two time axes on every derived record, so you can ask both "what was true on 2026-04-15?" and "what did we know on 2026-04-15?"
- Valid time —
valid_from/valid_to: when the fact was true in the world. - Recorded time —
recorded_from/recorded_to: when CortexDB learned it.
null on a *_to field means "open / still current." Events carry only observed_at and
recorded_at; the four-field bi-temporal shape appears on derived layers (Facts, Beliefs, …).
Querying a point in time
Pass a temporal block to /v1/recall, /v1/answer, or the layer-read endpoints:
{
"scope": "org:acme/dept:eng/user:alice",
"query": "seat count",
"temporal": {
"as_of": "2026-04-15T00:00:00Z",
"natural": "last 30 days",
"reference_date": "2026-05-01T00:00:00Z"
}
}valid_during / recorded_during are 2-element arrays
Range filters take a two-element array [start, end], not an object. This is enforced — an object
returns 422 INVALID_BODY: "invalid type: map, expected an array of length 2".
// correct
"valid_during": ["2026-04-01T00:00:00Z", "2026-04-30T00:00:00Z"]
// WRONG → 422
"valid_during": { "from": "2026-04-01", "to": "2026-04-30" }Dates may be RFC-3339 datetimes or YYYY-MM-DD (end date inclusive). Applies to recorded_during too.
Supersession
When a newer fact arrives for the same (subject, predicate), the prior fact's valid_to (or
recorded_to) closes rather than being overwritten — the full history is retained. Walk it with
GET /v1/facts/timeline.
Verified against v0.9.9
temporal.as_of, temporal.natural, and temporal.reference_date all accepted (200); the array-shape
rule confirmed live. See ../CORTEXDB_DOCS_AUDIT.md for the repro.