Time travel is reading the database as it was at an earlier point, not restoring a backup. Because every write is a commit with a version and timestamp, the past is still there — and any read can ask for it. This works the same across all five primitives, because they share one versioned substrate.
The commit clock
Each successful write returns a commit: a monotonically increasing version and
a small logical timestamp. That timestamp is a commit clock — the first user
write is a low integer, and it advances by one per commit — not a wall-clock time.
You capture it from a write receipt (data.commit.timestamp under --json) and
hand it back to a read to pin that read to that moment.
strata ./mydb json set user:1 '$.plan' '"pro"' # receipt carries commit.timestamp, say 7
strata ./mydb json set user:1 '$.plan' '"free"' # a later commit, say 8
Reading the past with --as-of
Every read verb accepts --as-of <timestamp>. The read sees the state as of that
commit, ignoring everything written after it:
strata ./mydb json get user:1 '$.plan' # "free" (latest)
strata ./mydb --as-of 7 json get user:1 '$.plan' # "pro" (as of commit 7)
The same --as-of applies to KV, JSON, vectors, events, and the graph — pass one
timestamp and you get a consistent snapshot of the whole database at that
commit, not a per-primitive patchwork. That is the payoff of a shared substrate;
Combining primitives shows it across all five
at once.
Alongside point-in-time reads, most primitives expose a history verb that lists
a key’s prior revisions newest-first, so you can see what changed as well as
read as of a change.
Two kinds of timestamp
Do not confuse the commit clock with data timestamps:
- The commit timestamp (
--as-of) is the logical clock above — small integers from a write receipt. It selects a database snapshot. - The event log additionally stamps each event with a
wall-clock microsecond
timestamp, used byevent range-timeto select events by real time. That is a property of the event payload, not the snapshot clock.
When in doubt: --as-of travels the database’s history; range-time filters
events by their own recorded time.
Forking at a point in time
Time travel composes with branches. You can not only read a past commit — you can fork from one, creating a new branch anchored to that moment and then evolving it independently:
strata ./mydb branch fork main investigate --timestamp 7
That gives you a live branch that starts from the state at commit 7, leaving
main untouched — the basis for “reproduce the bug as of last Tuesday, then poke
at it” workflows.
The retention boundary
History is retained, but not unbounded. Asking for a version or timestamp that
has aged out of retained history is a distinct, typed error —
history_unavailable — rather than a silent wrong answer or a not-found. Branch
on that class if your workflow reaches far back; see
error handling.
Next
- Commits — the write side: how versions and timestamps are produced.
- Branches — isolation, and forking from a past point.
- Combining primitives — one snapshot across all five primitives.