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"
}
}as_of pins both axes at once (known by then, and true then). To pin one axis alone, use
as_of_valid (records true at that instant) or as_of_recorded (records known at that instant);
both are accepted on v0.10.1 (the meaning is from the pinned temporal_block schema).
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.
On recall, valid_during filters events by capture time
In a /v1/recall or /v1/answer temporal block, valid_during keeps the events whose observed_at falls
in the window. It is
not the facts' valid time. To favour memories by the dates they talk about, capture context.refers_to
and recall with temporal.refers_during; see Languages & Multilingual Memory.
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.
Accepted temporal fields
temporal.as_of, temporal.natural and temporal.reference_date are all accepted, and range filters
take the two-element array form shown above.