DURABLE file-state rewind: put the working tree back the way it was before a turn, without owning a git/branch/commit lifecycle.
Two independent coverage sources, combined:
SNAPSHOT POOL — an :around op-hook on every mutating tool
(write/patch/struct_patch/fs/format_code/struct_rename)
captures each touched path's PRE-mutation state before the op runs.
Content lands in a content-addressed pool (objects/aa/<sha256>), so
the same bytes are stored once no matter how many turns touch them.
The first capture of a path in a turn WINS — later writes in the same
turn never overwrite the turn-start pre-image.
GIT BASELINE — at the FIRST hooked op of a turn we record HEAD plus
the full dirty set (git status --porcelain -z -uall) and snapshot the
pre-image of every DIRTY file. That closes the hole every other agent's
rewind leaves open: a sed -i, a formatter, a build step, any shell
write. A file that was CLEAN at turn start is recoverable from
git show <baseline-head>:<path>; a file that was DIRTY at turn start
already has its bytes in the pool. Coverage is therefore COMPLETE for a
git workspace and honestly reported as PARTIAL otherwise.
Everything is journalled as NDJSON (journal.ndjson) under
~/.vis/rewind/<session>/, so rewind survives a restart — the history is
NOT process-scoped. Entries are append-only and keyed by turn; a
truncated/corrupt trailing line (crash mid-append) is skipped, never fatal.
Restore semantics for turn T: every path touched in turns >= T is set back
to the EARLIEST recorded pre-image at or after T. A file created inside the
rewound region is deleted; a deleted file is recreated; a symlink is
recreated as a symlink; a recursively deleted directory is rebuilt and any
file created inside it since is pruned.
This layer owns FILES ONLY. Conversation truncation is the channel's job —
points exposes the turn ids to truncate to. Because that boundary is
invisible to a user typing /rewind, the slash READS the session store for
each turn's context size and says out loud, in every branch, that the
conversation stays.
DURABLE file-state rewind: put the working tree back the way it was before a
turn, without owning a git/branch/commit lifecycle.
Two independent coverage sources, combined:
1. SNAPSHOT POOL — an `:around` op-hook on every mutating tool
(`write`/`patch`/`struct_patch`/`fs`/`format_code`/`struct_rename`)
captures each touched path's PRE-mutation state before the op runs.
Content lands in a content-addressed pool (`objects/aa/<sha256>`), so
the same bytes are stored once no matter how many turns touch them.
The first capture of a path in a turn WINS — later writes in the same
turn never overwrite the turn-start pre-image.
2. GIT BASELINE — at the FIRST hooked op of a turn we record `HEAD` plus
the full dirty set (`git status --porcelain -z -uall`) and snapshot the
pre-image of every DIRTY file. That closes the hole every other agent's
rewind leaves open: a `sed -i`, a formatter, a build step, any `shell`
write. A file that was CLEAN at turn start is recoverable from
`git show <baseline-head>:<path>`; a file that was DIRTY at turn start
already has its bytes in the pool. Coverage is therefore COMPLETE for a
git workspace and honestly reported as PARTIAL otherwise.
Everything is journalled as NDJSON (`journal.ndjson`) under
`~/.vis/rewind/<session>/`, so rewind survives a restart — the history is
NOT process-scoped. Entries are append-only and keyed by turn; a
truncated/corrupt trailing line (crash mid-append) is skipped, never fatal.
Restore semantics for `turn` T: every path touched in turns >= T is set back
to the EARLIEST recorded pre-image at or after T. A file created inside the
rewound region is deleted; a deleted file is recreated; a symlink is
recreated as a symlink; a recursively deleted directory is rebuilt and any
file created inside it since is pruned.
This layer owns FILES ONLY. Conversation truncation is the channel's job —
`points` exposes the turn ids to truncate to. Because that boundary is
invisible to a user typing `/rewind`, the slash READS the session store for
each turn's context size and says out loud, in every branch, that the
conversation stays.Master switch. Bound false in tests that must observe an unhooked tool.
Master switch. Bound false in tests that must observe an unhooked tool.
Capture the per-turn git baseline (HEAD + dirty set + dirty pre-images).
This is what makes shell/sed -i/formatter writes recoverable.
Capture the per-turn git baseline (HEAD + dirty set + dirty pre-images). This is what makes `shell`/`sed -i`/formatter writes recoverable.
Cap on turn-start DIRTY files pre-imaged from the git baseline.
Cap on turn-start DIRTY files pre-imaged from the git baseline.
Files larger than this are NOT pooled; they are journalled as uncovered
with a reason so plan can report partial coverage instead of lying.
Files larger than this are NOT pooled; they are journalled as `uncovered` with a reason so `plan` can report partial coverage instead of lying.
Cap on descendants captured when a whole directory is about to be deleted or
moved. Above it the directory is journalled uncovered.
Cap on descendants captured when a whole directory is about to be deleted or moved. Above it the directory is journalled `uncovered`.
Override the ~/.vis/rewind store root (tests point this at a temp dir).
Override the `~/.vis/rewind` store root (tests point this at a temp dir).
(around-hook env op-kw args next-fn):around op-hook. Snapshots BEFORE the op, sweeps AFTER it, and is
transparent to the operation in every path — including when the op throws,
because a failed write can still have partially mutated the file.
`:around` op-hook. Snapshots BEFORE the op, sweeps AFTER it, and is transparent to the operation in every path — including when the op throws, because a failed write can still have partially mutated the file.
(journal session-id)Every journal entry for session-id, in append order. A truncated or corrupt
line (crash mid-append) is skipped — a damaged tail can never make the whole
history unreadable. turn is normalized to a long on the way out, so a line
that is valid JSON but carries the wrong TYPE cannot poison every reader.
Every journal entry for `session-id`, in append order. A truncated or corrupt line (crash mid-append) is skipped — a damaged tail can never make the whole history unreadable. `turn` is normalized to a long on the way out, so a line that is valid JSON but carries the wrong TYPE cannot poison every reader.
Tool ops whose arguments name the files they are about to change.
Tool ops whose arguments name the files they are about to change.
(plan session-id turn)What restore! would do to put the tree back to its state BEFORE turn.
restore holds ONE entry per path — the EARLIEST pre-image recorded at or
after turn, which is exactly that path's turn-start state.
What `restore!` would do to put the tree back to its state BEFORE `turn`. `restore` holds ONE entry per path — the EARLIEST pre-image recorded at or after `turn`, which is exactly that path's turn-start state.
(points session-id)Rewind targets for a session, newest last: one entry per turn that changed files, with the prompt that triggered it.
Rewind targets for a session, newest last: one entry per turn that changed files, with the prompt that triggered it.
(record-pre! {:keys [session turn turn-id op user-request]}
paths
&
[{:keys [recurse?]}])Snapshot the pre-mutation state of paths for this session/turn. The FIRST
capture of a path in a turn wins; repeat calls are cheap no-ops. Returns the
entries actually appended.
Snapshot the pre-mutation state of `paths` for this session/turn. The FIRST capture of a path in a turn wins; repeat calls are cheap no-ops. Returns the entries actually appended.
(reset-memo!)Drop the in-memory per-turn memo (tests; also safe at runtime — the journal is the source of truth).
Drop the in-memory per-turn memo (tests; also safe at runtime — the journal is the source of truth).
(restore! session-id turn & [{:keys [is-dry-run]}])Put every path touched at or after turn back to its turn-start state.
:is-dry-run reports the plan without touching the tree. Returns the plan
plus a per-path applied list; a per-path failure is reported, never
aborts the rest (a half-applied restore is still strictly closer to the
target than not trying).
Put every path touched at or after `turn` back to its turn-start state. `:is-dry-run` reports the plan without touching the tree. Returns the plan plus a per-path `applied` list; a per-path failure is reported, never aborts the rest (a half-applied restore is still strictly closer to the target than not trying).
(store-dir session-id)Per-session store directory: <root>/<session>.
Per-session store directory: `<root>/<session>`.
Tool ops that can change arbitrary files WITHOUT naming them. These get a post-op git sweep so their damage is still rewindable.
Tool ops that can change arbitrary files WITHOUT naming them. These get a post-op git sweep so their damage is still rewindable.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |