N7: workspace rewind - snapshot files before write_file / edit_file, rewind by turn - #309
Conversation
…rewind by turn
createFsTools(fs, { checkpoints }) with a WorkspaceCheckpoints backs up every
file write_file and edit_file change (content, and mode where the provider has
the new optional getMode/chmod), grouped by session and turn. list() shows the
changed paths per turn; rewind(toTurn, { dryRun, force }) restores them,
deleting files the agent created and skipping files changed since. Retention:
newest maxTurns turns per session, content only for a turn's first write of a
path, files over maxFileBytes not copied. Memory and file stores.
Closes #219
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md
|
Merged origin/main again (5fe0b2f, CHANGELOG tidy #308 + githubChannel #306; CHANGELOG conflict resolved keeping both sides). Re-ran the full verification list on 0559a61: tsc, lint, build, create-lousho-agent build, test:types, docs:verify-snippets (215 type-check, 8 run), docs:llms:check all exit 0; test:coverage 238 files passed / 1 skipped, 3481 tests passed / 7 skipped; fallow: no dead code, 0 above threshold; Agent Forge typecheck, typecheck:server, test (119 passed), test:server (130 passed) all exit 0; pack-smoke all checks passed. |
# Conflicts: # CHANGELOG.md
|
Merged origin/main again (3d2da03, N10a route auth #307; CHANGELOG conflict resolved keeping both entries; llms regenerated). Full verification on 270f541: tsc, lint, build, create-lousho-agent build, test:types, docs:verify-snippets (221 type-check, 8 run), docs:llms:check all exit 0. test:coverage: first run had 1 timing failure in src/tools/built-in/http.test.ts ('rejects near the configured timeout', 4328 ms vs < 2000 ms, machine under load; unrelated to this change). The file passes alone (23/23), and the re-run with |
N7: workspace rewind
createFsTools(fs, { checkpoints })with a newWorkspaceCheckpointsbacks up every file thatwrite_fileandedit_filechange. The backups are grouped by session and turn.checkpoints.rewind(toTurn, { sessionId, dryRun, force })puts the files back as they were before that turn.What changed
src/tools/workspace/checkpoints.ts:WorkspaceCheckpoints(list,rewind,clear),WorkspaceFileBackup,WorkspaceCheckpointStore,MemoryWorkspaceCheckpointStoreandRewindResult.src/tools/workspace/checkpointFileStore.ts:FileWorkspaceCheckpointStore(dir)writes one JSON file per session. Each write goes to a temp file that is then renamed into place; the rename is retried on WindowsEPERM/EBUSY/EACCES. Session ids that are not plain file names are hashed.src/tools/workspace/keyedQueue.ts: serializes work per key. It covers writes to one path (toolConcurrency) and the file store's read-modify-write for one session.fsTools.ts: addsFsToolsOptions.checkpoints. Without it the tools behave exactly as before (no extra reads).edit_fileis split intoprepareEdit+ write, so a failed edit records nothing.FsProvidergets optionalgetMode/chmod, whichNodeWorkspaceimplements with its usual path confinement. A forced restore of a file that was deleted outside the agent then gets its permission bits back.usermessages inctx.messagesminus one. Session id:ctx.sessionId, with a checkpointed turn's.turn-<n>suffix removed so all turns of a session group together;'default'when there is none.Safety
../x), or one that resolves outside the root through a symlink or junction, makes the whole rewind throw, the dry run included, and nothing changes. Restores go through the provider, soNodeWorkspaceconfinement applies.Retention (documented)
maxTurnsturns per session are kept (default 20). Rewinding to an older turn throws instead of doing a partial restore.maxFileBytes(default 1,000,000) are not copied; rewind reports them as'too-large'and never deletes them.omitted: 'same-turn'and keep only the hash of what was written.clear({ sessionId })forgets a session.Docs (no headings added, renamed or removed)
docs/workspace-tools.md: acheckpointsrow in thecreateFsToolsoptions table, and paragraphs plus a snippet at the end of## The toolscovering turn numbering,list(),rewind()with a dry run, thechanged-sincerule andforce, what is not tracked, and retention. Under## Writing your own provider, one paragraph on the optionalgetMode/chmod.docs/build-a-coding-agent.md: one bullet under## Next stepspointing to rewind.llms.txt/llms-full.txtregenerated.Deviations from the ticket text
session.fork()(N3a) does not exist yet. The docs point toagent.fork()(Fork and replay) for branching the conversation.removeBeforeTurnandearliestTurn, which implement retention. Backups have two optional fields:mode, andomitted: 'too-large' | 'same-turn'.Verification (local, Windows 11, Node 26, after merging origin/main)
npx tsc --noEmit: exit 0npm run lint: exit 0 (0 warnings)npm run build: exit 0npm run build --workspace=packages/create-lousho-agent: exit 0npm run test:types: no errorsnpm run docs:verify-snippets -- --skip-build: all 213 snippets type-check, 8 also run cleanly. The new snippet was also run againstdist/, and its output matches the comments.npm run docs:llms:check: exit 0npm run test:coverage: 236 files passed, 1 skipped; 3423 tests passed, 7 skippednpm run fallow: dead code: no issues; complexity: 0 above thresholdtypecheckexit 0;typecheck:serverexit 0;test119 passed;test:server130 passednpm run pack-smoke: all checks passedNot verified locally: the POSIX permission-bits test in
checkpoints.node.test.tsis skipped on Windows and runs in Linux CI. Mode restoring is also covered on every OS with a mode-awareMemoryWorkspaceincheckpoints.test.ts.Live test spend: none (the ticket makes no live calls).
New doc pages: none. Edited only:
docs/workspace-tools.md,docs/build-a-coding-agent.md.Closes #219
🤖 Generated with Claude Code