SmartMemory
Concepts

Bi-Temporal Versioning

Bi-Temporal Versioning & Time-Travel Queries

SmartMemory tracks two independent time dimensions for every memory item, so you can answer both "what did we believe was true on date X?" and "what did the system actually have stored on date X?".

The two times

DimensionSemantics
Valid timeWhen the fact is/was true in the real world
Transaction timeWhen the system recorded the version

Each version of an item carries four timestamps:

@dataclass
class TemporalVersion:
    item_id: str
    content: str
    metadata: dict
    valid_time_start: datetime | None
    valid_time_end: datetime | None
    transaction_time_start: datetime | None
    transaction_time_end: datetime | None
    version: int

Source: smart-memory-core/smartmemory/temporal/queries.py.

A version's transaction-time interval [transaction_time_start, transaction_time_end) is closed on the left and exclusive on the right. The current version has transaction_time_end = None. The same shape applies to valid time: an open valid_time_end means "still believed to be true."

How versions are created

Every update() on an existing item produces a new version. The previous version's transaction_time_end is set, a new row is written with a fresh transaction_time_start = now(), and the version counter increments. Deletes become a tombstone version with change_type = "deleted".

In FalkorDB this is stored on the node properties — there's no separate HAS_VERSION edge in the current implementation. VersionTracker (smartmemory/temporal/version_tracker.py) is the read+write mechanism; TemporalQueries is the user-facing query layer.

The query API

Service routes live under /memory/temporal/* and are wired in smart-memory-service/memory_service/api/routes/temporal.py. The JS SDK mirrors them on client.memories.*:

EndpointSDK methodReturns
GET /memory/temporal/{id}/historygetHistory(id, opts)All versions of id
GET /memory/temporal/at/{timestamp}timeTravel(ts)The state of every item at ts
GET /memory/temporal/{id}/at/{timestamp}getItemAtTime(id, ts)The version of id valid at ts
GET /memory/temporal/{id}/changesgetChanges(id, opts)Change events between since and until
POST /memory/temporal/{id}/compare?v1=&v2=compareVersions(id,…)Field-level diff between two versions
POST /memory/temporal/{id}/rollbackrollback(id, opts)Replay a past version as the new HEAD
GET /memory/temporal/{id}/audit(REST only)Full audit trail with provenance
GET /memory/temporal/search/during(REST only)All items valid during a window
GET /memory/temporal/compliance/report(REST only)Aggregate retention / change report
GET /memory/temporal/relationships/{rel_id}/history etc.(REST only)Bi-temporal queries on edges

Worked examples

History of a single item

history = memory.temporal.get_history("item123")
for v in history:
    print(f"v{v.version} valid={v.valid_time_start.date()}—"
          f"{v.valid_time_end and v.valid_time_end.date() or 'now'} "
          f"recorded={v.transaction_time_start.isoformat()}")

"What did we know on Sept 1?"

state = memory.temporal.at_time("2024-09-01")
# → list of TemporalVersion, one per item that existed at that transaction time

The same operation in the JS SDK:

const past = await client.memories.timeTravel('2024-09-01T00:00:00Z', {
  limit: 100,
});

Compare two versions

const diff = await client.memories.compareVersions('item123', 3, 5);
// diff.changed_fields → ["content", "metadata.tags"]

Rollback

await client.memories.rollback('item123', { toVersion: 3 });
// or
await client.memories.rollback('item123', { toTime: '2024-09-15T12:00:00Z' });

A rollback writes a new version with the chosen prior content — it does not delete the intervening versions, so the audit trail stays intact.

Use cases

  • Audit & compliance — answer "what did this user see on date X" exactly.
  • Backdated facts — set valid_time_start in the past when ingesting historical records (e.g. importing a CRM snapshot from last quarter).
  • Belief revision — when a fact changes, the old version stays queryable; models can still be replayed against the prior state.
  • Diff / blamecompareVersions shows which field changed between any two versions.

Caveats

  • Time-travel queries iterate version chains in FalkorDB — for items with hundreds of versions, prefer getChanges() with a tight since/until window over getHistory() with no bound.
  • valid_time_end is set when a successor version's valid_time_start is written — backfilling old facts requires explicitly setting the prior version's valid_time_end.
  • Rollback is best-effort for relations: edges follow the same bi-temporal model but rollback only restores node properties, not edge fan-out from evolvers that ran in the meantime.

Key code surfaces

  • smartmemory/temporal/queries.pyTemporalQueries (user-facing API)
  • smartmemory/temporal/version_tracker.py — version writes
  • smartmemory/temporal/relationships.py — bi-temporal edges
  • memory_service/api/routes/temporal.py — REST endpoints

On this page