Time travel
Strata keeps what each version held, so reading the past is a read rather than a restore. There is no snapshot to mount and no backup to unpack: you name a point and run an ordinary command against it.
This page assumes Commits and versions, which is where versions, timestamps and history come from.
Reading at a point
Add --as-of to a read and it answers as of that position on the timeline:
strata kv get k --as-of 4
The rule is the latest committed state at or before the point you named. That “or before” is the whole behaviour, and it is easy to miss until you watch it. Take a key written at timestamp 3 and again at 5, with somebody else’s write landing at 4:
--as-of |
answer | why |
|---|---|---|
| 2 | nothing | the key did not exist yet |
| 3 | v1 |
the write landed here |
| 4 | v1 |
nothing touched this key at 4, so 3 is still the truth |
| 5 | v2 |
the second write |
| 6 | error | past the end of the timeline |
You do not have to hit a point where your record changed. Any point resolves to what a reader would have seen standing there.
Deletions are respected the same way. A key deleted at timestamp 7 reads as present at 6 and absent at 8, because the tombstone is part of the history rather than an absence in it.
Which reads take a point
Temporal reading is not uniform across the five data models. Before you build on it, know what you have.
| model | reads that take --as-of |
|---|---|
| Key-value | get, list |
| JSON | get, list |
| Events | get, count, list, types, by-type |
| Vectors | get, query |
| Graph | meta, get-node, list-nodes, get-edge, neighbors, nodes-by-type, bfs, and every analytic |
| Branches | diff |
Three patterns are worth pulling out of that table.
scan never takes a point, and list always does. That holds for key-value,
JSON and vectors alike. If you need a full-row historical read, list the keys
as of your point and get each one at the same point.
Graph goes deepest. Not just the reads but the whole analytics suite, so you can run a community detection or a PageRank against the graph as it stood earlier and compare. That is a genuinely different question from “what does the graph look like now”, and it is one command:
strata graph wcc g --as-of 7
component_count: 2
against a graph that today reports one component, because the edge that joined them landed after that point.
Events are the exception, and for a reason. event range and
event range-time do not take --as-of, because the log already carries its own
clock. Each entry records when it occurred, so asking for a window of the log is
already a question about time. What you cannot do is ask what the log looked like
at an earlier commit, which is a different question and one the log’s own
ordering makes largely moot.
Enumerating the past takes care
A paginated read is several commands, and the database can move between them. JSON states this explicitly: each page reads the latest committed state unless you pin it, so documents created behind your cursor never appear and ones ahead of it show up in later pages.
For a stable enumeration, pass the same --as-of on every page:
strata json list --limit 2 --as-of 42
strata json list --limit 2 --as-of 42 --cursor k:2
Without that, a long enumeration over a changing database is not a snapshot of anything. With it, every page answers as of the same instant.
Two clocks, and which flag reads each
History gives you three ways to name a moment, and they are not interchangeable:
{ "version": 38, "timestamp": 88, "committed_at": "2026-09-07 00:09:21.577135 -05:00" }
--as-of takes the timestamp: a position on the logical commit timeline, a
per-commit counter that has nothing to do with the calendar. Passing the
version instead does not raise an error, and in a fresh database the two
numbers happen to be equal so the mistake works. In a database that came from a
clone they differ and it quietly returns nothing.
--as-of-time takes a real moment, and accepts most ways of writing one:
strata kv get k --as-of-time 2026-09-05
strata kv get k --as-of-time "2026-09-05 15:00"
strata kv get k --as-of-time 2026-09-05T15:00:00Z
A time without an offset is read as local time. The two flags cannot be
combined; doing so fails with invalid_argument.executor.as_of_conflict.
Use the logical clock when you need exact reproducibility, and the wall clock
when you have a real moment in mind. committed_at is best effort: it comes
from the machine that made the commit, so a clock adjustment can move it
backwards. Only the logical timeline is guaranteed ordered.
Wall-clock reads stop at the last commit
Asking for a moment later than the newest dated commit is an error rather than a synonym for “now”:
strata kv get k --as-of-time "2026-09-07 09:00"
history_unavailable.engine.persistence_history:
wall-clock instant is after the latest dated commit
That is deliberate. A point-in-time read that silently answered from the present whenever the instant was in the future would be indistinguishable from one that found what you asked for.
Two more limits are worth knowing before you rely on the wall clock. Commits made before this feature existed carry no instant at all, so a wall-clock query landing before the first dated commit refuses with its own reason. And commits produced by replay or import are undated by design, because sampling a clock during deterministic replay would make it non-deterministic.
Branching from the past
A point-in-time read answers a question. A branch from that point gives you somewhere to work.
strata branch fork default before --version 3
before now holds the database as it stood at version 3, and it is an ordinary
branch: you can write to it, compare it against the branch it came from, and
merge it. The parent keeps moving without disturbing it.
This is the recovery story, and it is worth seeing as one. A bad migration does not need a restore: branch from the version before it, check the state is what you expected, and merge that back. Nothing was rolled back, nothing was lost, and the bad versions stay in the history where you can still read them.
History is retained, not permanent
Time travel reaches as far back as the retained history and no further. Ask for
a point beyond the timeline and Strata says so with
history_unavailable.engine.persistence_history rather than answering from the
nearest thing it has.
That is the right failure. A point-in-time read that silently substitutes a different point is worse than one that refuses, particularly for the audit and recovery work this feature exists for.