Skip to content
dsh.fish
Bundle

@zoytown/dsh-rewind

DeepSeek Harness (dsh) plugin that snapshots your workspace at every turn into a shadow git repository, so you can restore the files to any earlier turn. Your own .git is never touched, and plain directories work too.

Source
zoyluoblue
stars
1 stars
License
MIT
Updated
Updated 5 days ago

Readme

# @zoytown/dsh-rewind

English | [中文](README.zh.md)

**Put the files back.** A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugin that
saves your workspace at the end of every turn, so when the agent takes a wrong turn you can restore
the files to any earlier point — from the Settings page or with `/rewind`.

Your own git repository is never touched. Directories that are not git repositories work too.

![The Rewind page in dsh Settings, listing each turn of a DeepSeek Harness session with the files it changed and a Restore button](assets/settings-rewind-en.webp)

---

## Why

The agent has been editing for six turns. Turn 3 was right; turns 4 through 6 made it worse. You want
turn 3 back.

`git` cannot help: you have not committed since turn 1, and the agent's intermediate states were never
commits in the first place. `git checkout .` throws away everything, including the parts that were good.

The harness does not save workspace files anywhere — its built-in checkpointing (`dsh-session-checkpoint-policy`)
governs when the *session log* becomes durable and has nothing to do with your files.

This plugin fills that gap.

## What it does

- **Saves every turn.** When a turn ends, the workspace is committed into a shadow repository of the
  plugin's own. Turns that changed nothing cost one tree comparison and produce no entry.
- **Restores any turn.** Files edited since then are reverted, files created since then are deleted —
  the workspace *becomes* that state rather than having it layered on top.
- **Every restore is reversible.** The state being overwritten is saved first, so an unwanted restore is
  undone by restoring the entry above it.
- **Never touches your git.** Snapshots live in `$DSH_HOME/rewind/`, not in your `.git`. Your index,
  `HEAD`, stashes, reflog, and `gc` are outside the blast radius, and a plain directory gets the same
  protection a repository does.

## Install

```bash
npx -y @deepseek-ai/dsh plugin --profile web add @zoytown/dsh-rewind
```

Then restart `dsh`. A **Rewind** entry appears in the Settings sidebar.

Requires `git` on the machine. Without it the plugin reports that snapshots are paused and everything
else in dsh keeps working.

To remove it:

```bash
npx -y @deepseek-ai/dsh plugin --profile web remove @zoytown/dsh-rewind
```

Snapshots outlive the plugin; delete `$DSH_HOME/rewind/` to reclaim the space.

## Using it

### The Settings page

Settings → **Rewind** lists every saved point for the current workspace, newest first, with what changed
and which tools ran. **Restore** asks for confirmation, spelling out that newer files will be deleted,
then puts the files back.

The badge marks the snapshot your files currently equal — after a restore it moves to the restored entry,
not the newest one.

![The dsh Rewind confirmation dialog, warning that newer files will be deleted before restoring a snapshot](assets/confirm-restore-en.webp)

### The `/rewind` command

Costs no tokens and runs no model turn — it is handled by the harness, not sent to the model.

| Command | What it does |
|---|---|
| `/rewind` | Restore the previous turn of this session |
| `/rewind 3` | Restore the end of turn 3 |
| `/rewind a1b2c3d4` | Restore a snapshot by id (a unique prefix of 4+ characters) |
| `/rewind list` | List this workspace's snapshots |
| `/rewind save` | Save the current state now, outside a turn |

A restore prints the id of the state it overwrote, so `/rewind <that id>` undoes it.

## What is and is not saved

Snapshots respect your `.gitignore`, so ignored files — `.env`, build output, `node_modules` — are
**not saved and not restored**. This keeps snapshots small, and it means a restore leaves those files
exactly as they are.

On top of that, the `excludes` list below is applied to **every** workspace, git repository or not. It
exists so a directory with no ignore rules of its own does not swallow `node_modules`, but it is not
conditional: a project that deliberately tracks `dist/` or `build/` in git will not have those captured
either. Set `excludes: []` if you want nothing beyond your own `.gitignore`.

`.git` itself is always excluded and cannot be configured otherwise: saving it would double the size of
every snapshot, and restoring it would overwrite your real commit history with a stale copy.

## Configuration

Edit the `dsh-rewind` row in your profile's `cordis.yml`:

```yaml
- id: dsh-rewind
  name: '@zoytown/dsh-rewind'
  config:
    gitTimeoutMs: 60000
    retentionDays: 30
    maxSessions: 50
    listLimit: 200
    exposeTool: 'off'
```

| Option | Default | Meaning |
|---|---|---|
| `gitTimeoutMs` | `60000` | Deadline for one git command. Raise it for very large workspaces. |
| `excludes` | common dependency and build directories | Extra paths never saved, applied to every workspace on top of your `.gitignore`. Set to `[]` to rely on your `.gitignore` alone. |
| `retentionDays` | `30` | Sessions whose newest snapshot is older than this are dropped. |
| `maxSessions` | `50` | How many sessions to keep per workspace, newest first. |
| `listLimit` | `200` | Most snapshots shown at once. |
| `exposeTool` | `'off'` | Whether the model can use rewind. See below. |

### Letting the agent rewind itself

`exposeTool` is `'off'` by default, which keeps rewind a human-only capability.

| Value | The agent can |
|---|---|
| `'off'` | nothing — rewind is yours alone |
| `'read-only'` | read its own history (`rewind_list`) |
| `'full'` | also restore the workspace (`rewind_restore`) |

`'full'` is genuinely useful — "this approach is wrong, put the files back and start over" is a recovery
move no amount of prompting replaces — and genuinely risky: an agent that misjudges can discard work you
wanted. Every restore still saves the overwritten state first, so it is reversible, but the decision to
enable it should be yours and deliberate.

## FAQ

### How do I undo what the agent just did to my files?

Type `/rewind`. It restores the workspace to the end of the previous turn. If you want a specific point,
`/rewind list` shows every saved turn and `/rewind 3` restores turn 3.

### Does this commit to my git repository?

No. Snapshots go into a separate repository under `$DSH_HOME/rewind/`, and your `.git` is never opened
for writing. Your index, `HEAD`, stashes, and reflog are unchanged, and `git status` shows exactly what
it showed before.

### Does it work if my project is not a git repository?

Yes. The shadow repository is the plugin's own, so your directory does not need to be a repository —
this is the case rewind protects best, because there is nothing else protecting it.

### Does rewinding undo the conversation too?

No. Rewind restores **files only**. The session log is append-only by design in DeepSeek Harness, and the
transcript stays exactly as it was — so after a restore the agent still remembers what it did, which is
usually what you want when telling it to try a different approach.

### I installed it but there is no Rewind page. What now?

Restart `dsh` — plugins are mounted at boot. If it is still missing, run
`npx -y @deepseek-ai/dsh --profile web --dump-config` and check that a `dsh-rewind` row appears; if it does
not, the package was installed as a plain dependency rather than a bundle.

### Will it slow down my turns?

No. Snapshotting runs after the turn ends and is never awaited by the agent loop, and a turn that changed
no files costs one tree comparison and stores nothing.

### How much disk does it use?

Snapshots share storage the way git commits do: unchanged files are stored once. The cost of a workspace is
roughly one copy of it plus the changes since. Sessions older than `retentionDays` (30 by default) are
deleted automatically.

### Can the agent rewind by itself?

Only if you turn it on. `exposeTool` is `'off'` by default; set it to `'full'` to let the agent restore
the workspace, or `'read-only'` to let it read its history without restoring.

## Known limitations

- **Files ignored by `.gitignore` are not saved or restored.** If the agent breaks `.env` or something
  under `node_modules`, rewind cannot bring it back.
- **A restore does not touch the conversation.** Files go back; the transcript does not.
- **Retention is per session, not per snapshot.** A single very long session keeps all of its snapshots;
  the limits drop whole sessions once they are old or too numerous.
- **Sandboxed or remote subprocess providers are not supported in 0.1.** The shadow repository is created
  on the machine dsh runs on, so a provider that executes tools elsewhere (E2B) would snapshot the wrong
  filesystem. The default local provider is what this version targets.
- **`git` must be installed.** Without it the page says snapshots are paused; nothing else in dsh is affected.
- **Files inside a nested git repository are not captured.** A sub-repository is recorded the way git
  records one — as a pointer, not as files — so rewind neither saves nor restores anything inside it.
- **Restoring from the panel or `/rewind` is refused while the agent is mid-turn.** Those paths wait for an
  idle agent. The opt-in `rewind_restore` tool is the deliberate exception: the agent calls it during its
  own turn, so that path does not wait (it is barred from running beside other tool calls instead).
- **A restore interrupted partway — a git timeout, a killed process — can leave the workspace between the
  two states.** The safety snapshot is always written first, so nothing is lost: re-running the restore, or
  restoring the safety entry, settles it. But do not read an error from a restore as a guarantee that
  nothing on disk moved.
- **`excludes` applies to every workspace, not only to directories without a `.gitignore`.** A project that
  tracks `dist/` or `build/` in git will not have those files captured or restored unless you set
  `excludes: []`.

## How it works

Each workspace gets a git repository under `$DSH_HOME/rewind/<key>/repo.git`, bound to your directory
with `--work-tree`. Saving is `add` → `write-tree` → `commit-tree` → `update-ref`, which writes only
inside that repository: no file in your workspace is touched and no `HEAD` moves. Restoring is
`read-tree -m -u`, which makes the workspace equal the snapshot — including deleting files the snapshot
does not have.

Every git command runs with hooks disabled, signing disabled, and your global excludes neutralized, so
your repository's own hooks never fire and a signing passphrase is never demanded. Git runs through the
harness's subprocess service, so it executes wherever your files actually live.

Sessions are separate chains inside one workspace repository, so they share storage while staying
individually restorable and individually expirable.

## Privacy

Nothing leaves your machine. The plugin has no telemetry and makes no outbound network requests.

Its two HTTP routes are local surfaces for the Settings page, and both require the request to arrive over
the loopback interface **and** to address the server by a loopback `Host` — the second check is what a
DNS-rebinding page cannot satisfy. The restore route additionally requires POST and refuses a request
labelled cross-site. Anything already able to run code on your machine as you can, of course, reach them,
exactly as it could reach dsh itself.

## Verified

The behaviour described above was exercised on **2026-08-19** against DeepSeek Harness packages
`0.1.0-rc.6`/`0.1.0-rc.7` and git `2.50.1`, including a full save → restore → undo cycle through the
Settings page. DeepSeek Harness is a developer preview; re-check these claims after a harness upgrade.

## Requirements

- `git` available where dsh runs tools
- Node `^22.19 || >=24`
- DeepSeek Harness (developer preview — expect breaking changes)

## License

MIT

Install

dsh plugin --profile web add github:zoyluoblue/deepseek-harness-rewind

Profile: web

  • This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source