Skip to content

Bitemporality

Every memory in MemLayer carries two independent time dimensions. Most databases record only when data was written. MemLayer also tracks when the information was true in the real world.

When MemLayer recorded the memory. Set automatically, cannot be changed. Answers: “When did the system learn this?”

When the information was true in the real world. Determined from the content itself (the LLM resolves temporal references like “yesterday” into dates). Answers: “When was this actually true?”

  • valid_from — when the fact became true
  • valid_to — when the fact stopped being true (null if still current)

Consider: on Monday, a user says “I work at Acme Corp.” On Wednesday: “I just started at Beta Inc.”

With a single timeline, the Monday memory would be overwritten. With bitemporality, both are preserved:

Memoryvalid_fromvalid_tosystem_time
”Works at Acme Corp”MondayWednesdayMonday
”Works at Beta Inc”Wednesday(null)Wednesday

This enables two distinct kinds of temporal queries:

“What did the agent know on Tuesday?” — Query by system_time. On Tuesday, only the Acme memory existed.

“Where did the user work on Tuesday?” — Query by valid_time. The Acme memory was valid (valid_from=Monday, valid_to=Wednesday).

When a memory is updated during retain:

  1. The old memory’s valid_to is set to the current time
  2. A new memory is created with valid_from set to now and valid_to as null
  3. Both versions share the same entity_id

Every historical state of the knowledge graph is preserved and queryable.

The recall endpoint accepts as_of to query the knowledge graph at a specific point in time:

GET /api/v1/recall?query=where+does+the+user+work&as_of=2025-01-15T00:00:00Z

A memory is considered valid at time T if its valid_from is at or before T, and its valid_to is either null or after T.

Even without as_of, bitemporality influences recall. Superseded memories (those with valid_to set) receive a 30% score penalty:

adjusted_score = original_score * 0.7 (superseded memories)
adjusted_score = original_score * 1.0 (current memories)

Current information ranks higher, but historical data still surfaces if highly relevant.