English · 中文 · Español · Português · हिन्दी
Claude Code /rewind, done right for DeepSeek Harness.
A capability-seam plugin that adds workspace file snapshots + session-boundary rollback to DeepSeek Harness: before every mutating tool execution the plugin captures your workspace (git-first, copy fallback), and one /rewind command restores the files and forks the session back to the checkpoint's turn boundary — so the model context and the files on disk always agree.
Topics:
dsh·dsh-plugin·deepseek-harness·rewind·checkpoint·session-fork·workspace-safety·undo·cordis-plugin
TL;DR
- 📸 Snapshot before every mutation — every write path (
write,edit,str_replace_editor,bash, …) is captured first, silently, viafs/*-intent+tools/pre-executepass-through listeners. - 🧵 git-first, no history risk — snapshots are unreferenced git objects (
stash create/commit-tree); restore is worktree-only. Non-git directories fall back to incremental directory snapshots. - ⏪ One command to go back —
/rewindlists checkpoints;/rewind <id>confirms, restores files, then forks the session at the checkpoint's turn boundary and returns the new session id. - 🔒 Fail-closed by design — restore requires human confirmation; no answerer means no restore. No
git reset --hard, nogit clean, no message-level editing, ever.
| Plugin | What it sells | Restores files? | Rewinds the session? |
|---|---|---|---|
| dsh-checkpoint-rewind (this) | git-object snapshots + turn-boundary fork + one-shot restore | ✅ full workspace state | ✅ fork-seeded child session |
| Anionex/dsh-turn-rewind | persistent Change Ledger of per-mutation deltas | ✅ by replaying inverse deltas | ✅ its own ledger model |
| LingLambda/dsh-undo | pure context rollback to the last completed step | ❌ | ✅ context only |
| Mongfayi/dsh-recall | message recall (remove a turn and everything after) | ❌ (explicitly) | ✅ turn removal |
The difference in one sentence: dsh-checkpoint-rewind captures the workspace state with side-effect-free git primitives before each mutation and makes "back to step N" one approved command — files restored first, session forked second, each phase logged. No delta bookkeeping to drift, no message-level editing (that belongs to a different plugin), no cross-device sync.
- Snapshots before every mutation — a prepend pass-through listener on
fs/write-intent/fs/edit-intentplustools/pre-executefor non-fs mutators (bash,subprocess, …), so every change path is covered without stealing the policy decision slot. - Provider seam —
gitfirst:git stash create/git commit-treeproduce unreferenced snapshot objects that never touch your worktree, index, or history; restore is worktree-onlygit restore. Non-git directories degrade tocopy(incremental directory snapshots with hardlink reuse), clearly labeled in the list. - Step-level mapping, turn-level forks — every checkpoint records its turn/step;
step/endbackfills the step mapping ("back to step N" = nearest snapshot ≤ N) andturn/endbackfills the fork boundary, using the harness's realctx.sessions.forkprimitive. - Two-phase rewind transaction —
/rewind <id>asks for confirmation (userQuestions / approval seam, fail-closed when no answerer), restores files first, then forks; a restore failure never forks, a fork failure reports "files restored, session not forked" and leaves the checkpoint intact. - Durable registry + quotas — checkpoint records live in
ctx.storageDomain(domaincheckpoints; SQLite backend = rows, JSON backend = a human-readable file);maxSnapshots(per session, default 50),maxSnapshotBytes(global, default 512 MiB),pruneOnTurnEnd, oldest-first. - Reconstructable by design —
/rewindoutput rides the harness's owncommand/run+command/doneevents;checkpoint/snapshot|bound|prune|rewindsession events are declared and appended automatically once a host build knows them (rc.6 adaptive gate). - Web-ready projection — a session-projection unit
checkpointsis registered wheneverctx.sessionProjectionsexists, so a shell panel can render the checkpoint strip from the event log with zero plugin changes.
- DeepSeek Harness
0.1.0-rc.6(npmnext), Node^22.19 || >=24 git(only for the git provider; without a git repo the copy provider takes over automatically)
dsh-checkpoint-rewind ships as a bundle plugin (no build step, pure ESM):
dsh plugin add dsh-checkpoint-rewind # enters your profile's bundle stack
# restart dsh — done. /rewind is live in the Web UI.Or mount it directly for experiments:
pnpm dsh web --patch ./cordis.patch.ymlWorkspace mutations now create checkpoints automatically. In the Web UI (or any interactive adapter):
/rewind
rewind: 3 checkpoints (newest last):
#a1b2c3d4-e5f6-… · (git) · turn 2 step 1 · 2026-08-14 12:00:01 · trigger: bash · 4 files · 1.2 MiB · fork: ready
#b2c3d4e5-f6a7-… · (git) · turn 2 step 3 · 2026-08-14 12:00:41 · trigger: str_replace_editor · 2 files · 310 KiB · fork: ready
#c3d4e5f6-a7b8-… · (copy) · turn 3 step 1 · 2026-08-14 12:01:10 · trigger: write · 1 file · 90 KiB · fork: pending (turn not closed)
run "/rewind <id>" to restore files and fork the session from that checkpoint
/rewind b2c3d4e5-f6a7-…
The plugin asks "Restore the workspace files to this checkpoint and fork the session?" → on approval it restores the files, forks the session at the checkpoint's turn boundary, and returns the new session id:
rewind: restored 2 file(s) from checkpoint b2c3d4e5-f6a7-… (provider git)
and forked a new session at seq 87 (end of turn 2).
session: session-123
Open the new session to continue from before that turn; this session keeps its later history.
Headless runs print the same result with resume guidance; the Web shell can use the returned session: id to navigate (see Web UI).
A real assembled-headless run (npm run test:integration): the agent modifies a.txt in turn 1 and b.txt in turn 2, then a /rewind restores both files and forks the session. (Transcript is verbatim output.)
[rewind-integration] copy flow: mounted; workspace C:\Users\me\Temp\dsh-rewind-int-ws-mpnQDg
[rewind-integration] /rewind list:
rewind: 2 checkpoints (newest last):
#5889f233-6730-44dd-98dd-3b24cca09e77 · (copy) · turn 1 step 1 · 2026/8/14 04:30:18 · trigger: fs/write-intent · 2 files · 10 B · fork: ready
#03fb9ea6-8b50-4284-b768-98d5acb155f0 · (copy) · turn 2 step 1 · 2026/8/14 04:30:18 · trigger: fs/write-intent · 2 files · 10 B · fork: ready
run "/rewind <id>" to restore files and fork the session from that checkpoint
[rewind-integration] [user-questions] asked: Restore the workspace files to this checkpoint and fork the session?
[rewind-integration] /rewind result: rewind: restored 2 file(s) from checkpoint 5889f233-… (provider copy)
and forked a new session at seq 3 (end of turn 1).
session: session-1
Open the new session to continue from before that turn; this session keeps its later history.
[rewind-integration] fork ok: child session-1 seedLength 4 parent integration-session
[rewind-integration] copy flow: PASS
[rewind-integration] git flow: mounted; workspace C:\Users\me\Temp\dsh-rewind-int-git-MhDhwe
[rewind-integration] git restore ok; HEAD intact: 9c21ee5e
[rewind-integration] git flow: PASS
[rewind-integration] integration: ALL PASSEverything is a Config field (cordis.yml can change it; nothing is hardcoded):
| Key | Default | Meaning |
|---|---|---|
enabled |
true |
Master switch; false removes the command, listeners, and providers entirely. |
provider |
auto |
Snapshot provider: auto (git if available, else copy) · git (fail loud on non-git dirs) · copy. |
gitBin |
git |
Git executable path. |
snapshotDir |
$DSH_HOME/dsh-checkpoint-rewind |
Root for copy-provider snapshots. |
maxSnapshots |
50 |
Checkpoints kept per session (oldest pruned first). |
maxSnapshotBytes |
536870912 (512 MiB) |
Global content quota across all sessions (oldest pruned first). |
pruneOnTurnEnd |
true |
Run quota pruning when a turn ends. |
mutationTools |
['bash','write','edit','str_replace_editor'] |
Tools treated as mutating at tools/pre-execute (fs tools are covered by fs/*-intent regardless). |
excludeGlobs |
['node_modules','.git','.dsh','dist','build'] |
Directories/files skipped by the copy provider (.git and the snapshot dir are always excluded). |
confirmVia |
auto |
Confirmation channel: auto (userQuestions first, then approval) · userQuestions · approval. |
listLimit |
10 |
Checkpoints shown by bare /rewind. |
- insert:
- id: checkpoint-rewind
name: dsh-checkpoint-rewind
config:
provider: auto
maxSnapshots: 50
maxSnapshotBytes: 536870912
pruneOnTurnEnd: true
confirmVia: auto- Git history is untouchable. The git provider runs only whitelisted side-effect-free primitives —
stash create,commit-tree,restore --worktree,ls-tree,diff-tree,ls-files,status,rev-parse— enforced by a runtime assertion. Noreset --hard, noclean, no index/history mutation, ever. - Restore requires approval. Overwriting user files always goes through the confirmation seam with
asksemantics; a missing, throwing, or answering-no answerer fails closed. - Overwrite rollback, never deletion. Both providers restore captured files and report files created after the checkpoint (git: untracked files; copy: manifest extras) instead of deleting them.
- Two-phase transaction, fixed order. Files first, fork second; every phase is logged; a failed restore leaves files, checkpoints, and session untouched.
- Model-visible ⟺ logged. Everything a user or model sees is reconstructable from the session log (
command/run+command/doneand, once the host knows them,checkpoint/*events) plus the durablecheckpointsdomain.
checkpoint/snapshot (creation) → checkpoint/bound (step/end and turn/end backfill) → /rewind (list / confirm / two-phase restore):
flowchart LR
subgraph capture["per mutation"]
A["fs/write-intent · fs/edit-intent<br/>tools/pre-execute (prepend, pass-through)"] --> B["ProviderRegistry.resolve(auto)"]
B --> C["git: stash create / commit-tree<br/>(unreferenced objects)"]
B --> D["copy: incremental dir + hardlinks"]
C --> E[("checkpoints domain<br/>(ctx.storageDomain)")]
D --> E
E --> F["checkpoint/snapshot event (adaptive)"]
end
subgraph session["session events"]
G["step/end"] --> H["backfill stepEndSeq (step mapping ≤N)"]
I["turn/end"] --> J["backfill forkSeq (fork boundary)"]
H --> E
J --> E
end
K["/rewind <id>"] --> L{"confirm (userQuestions / approval)<br/>fail-closed"}
L -->|allow| M["phase 1: provider.restore(ref)"]
M -->|ok| N["phase 2: ctx.sessions.fork(session, forkSeq)"]
N --> O["new session id → Web UI / headless resume"]
M -->|fail| P["no fork · checkpoint kept · error"]
N -->|fail| Q["files restored · 'session not forked' reported"]
Full decision record, event vocabulary, and the provider seam contract: ARCHITECTURE.md.
The plugin declares checkpoint/snapshot, checkpoint/bound, checkpoint/prune, and checkpoint/rewind as log-only SessionEventMap members. Harness rc.6 has no plugin event-registration surface and Session.append cannot mark unknown types ignorable, so appending them would make the session unreadable on reload. The plugin therefore appends through an adaptive gate (KNOWN_SESSION_EVENT_TYPES): skipped today, enabled automatically once a host build includes the types. Until then the authoritative audit chain is command/run + command/done (harness-known) plus the durable checkpoints storage domain.
The plugin returns the new session id in the command result (session: <id>) and the Web shell can navigate there. The session-projection unit checkpoints is shipped: whenever ctx.sessionProjections exists, the plugin registers the unit (folds checkpoint/snapshot|bound|prune|rewind into a whole-value list, stateVersion 0) — it stays an empty list on rc.6 hosts until a harness build ships the checkpoint/* vocabulary, then fills in with zero plugin changes. What remains a shell-side follow-up: the read-only panel rendering that projection (see ARCHITECTURE.md).
Does this replace git? No — it uses git where available. In a git repo you get byte-perfect, deduplicated snapshot objects without touching history; in any other directory the copy provider does the same with plain files. Regular commits remain your long-term history.
Why not git reset --hard? Because destroying state is not the job of a safety net. The plugin only creates unreferenced objects and performs worktree-only restores, so a bad rewind can never lose history, the index, or files created after the checkpoint.
Can I rewind to a step in the middle of a turn? File restoration is step-precise (nearest snapshot ≤ N). The session fork, however, respects the harness's fork granularity: the child session ends at the checkpoint's turn/end, because ctx.sessions.fork rejects prefixes inside an open turn. Files and conversation stay consistent at that boundary.
What happens if nobody can answer the confirmation? Nothing is touched — the plugin fails closed (unavailable/rejected), keeps the checkpoint, and returns an explanatory error.
npm install
npm test # 60 unit tests: snapshot creation/dedup/concurrency, git & non-git paths,
# ≤N boundary mapping, prune quotas, two-phase failure matrix, approval
# rejection, adaptive event gate, checkpoints projection unit
# (real Cordis + real SessionStore/CommandRuntime/SessionProjectionRegistry)
npm run test:integration # assembled-headless verification: agent modifies 2 files across 2 turns,
# /rewind list → restore → file contents + fork context assertedApache License 2.0 — see LICENSE and THIRD_PARTY_NOTICES.md.
- dsh-memento — bounded, approval-gated cross-session memory (same plugin conventions).
- Anionex/dsh-turn-rewind · LingLambda/dsh-undo — the alternatives this plugin differentiates from (table above).