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.

A branch taken from an earlier version starts from what the database held then, and stays isolated as the parent keeps committing past that point.

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.

agents: this page as markdown → /docs/learn/time-travel.md