Skip to content

Working-directory snapshots with interactive rollback #398

Description

@dpup

Summary

Add working-directory snapshots with interactive rollback to Moat runs: capture content-addressable snapshots of the run's writable paths before/during execution, then let the user review and restore individual files or the whole directory after the agent finishes.

Moat captures logs, network, and traces, but has no "undo what the agent did to disk." For a sandbox built to run untrusted agents, the ability to inspect and revert filesystem changes is a natural safety net.

Motivation

  • Safety net for untrusted agents. If an agent makes a mess (deletes files, mangles code, writes junk), the user can revert cleanly instead of relying on git (which doesn't cover untracked files, non-git dirs, or partial reverts).
  • Review-then-keep workflow. Snapshots let a user diff what changed and selectively accept/reject changes per file rather than all-or-nothing.
  • Reuses content-hashing machinery. Shares the same content-addressable hashing as filesystem-state attestation (see companion issue) — the snapshot store and the attestation roots can be built on one content-addressed object store.

Proposed mechanism

  • Content-addressable store. Snapshot files keyed by SHA-256 of content, with deduplication so unchanged/identical files cost nothing on subsequent snapshots.
  • Snapshot points. At minimum a pre-run snapshot; optionally periodic snapshots during the run so a user can roll back to an intermediate state, not just the start.
  • Integrity. Commit each snapshot with a Merkle root so a restore can verify it's reconstructing the exact captured state.
  • Restore granularity. Interactive restore of the whole directory or individual files; show a diff/summary (added / modified / deleted) before restoring.

Smart defaults (important for practicality)

  • Auto-exclude regenerable trees: .git, node_modules, target, dist, .next, build/cache dirs.
  • Size guard: auto-exclude any directory above a configurable file-count threshold (e.g. 10,000 files) to avoid hangs / huge snapshot stores on large projects.
  • Record what was excluded so the snapshot's scope is explicit.

Configuration / CLI sketch

# moat.yaml
snapshots:
  enabled: true
  paths: [./]
  exclude: [.git, node_modules, target]
  max_files_per_dir: 10000
  interval: 0   # 0 = pre-run only; >0 = periodic during run
moat snapshot list <run-id>            # list snapshots for a run
moat snapshot diff <run-id> [snap]     # summary of changes vs. a snapshot
moat snapshot restore <run-id> [snap] [--path FILE ...]   # whole-dir or per-file restore

Scope for a first cut

  • Content-addressable snapshot store with SHA-256 dedup
  • Pre-run snapshot of writable paths
  • Default exclusion list + per-dir file-count size guard
  • moat snapshot list / diff / restore
  • Whole-directory restore
  • Per-file restore
  • Periodic in-run snapshots (interval)
  • Docs: how snapshots/rollback work, storage location, and exclusion behavior

Open questions

  • Storage location & lifecycle. Where do snapshots live (per-run storage under ~/.moat/runs/<id>/?) and when are they garbage-collected? A large snapshot store could grow quickly without a retention policy.
  • Container vs. host capture. For containerized runs, is the snapshot taken on the host side of the writable mount, or inside the container? Host-side is simpler and avoids trusting the (possibly compromised) container.
  • Relationship to git. Should restore warn / interact with an existing git working tree to avoid clobbering staged changes?
  • Periodic snapshot triggers. Time-based interval vs. event-based (e.g., snapshot before each tool/exec) — which is more useful and less surprising?

Relationship to other work

  • Companion to the filesystem-state attestation issue — both build on content-addressed hashing of the writable paths; ideally they share one object store and exclusion logic.
  • Independent of the kernel-sandbox work; this is a host-side capture/restore feature and does not require kernel enforcement.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Fields

    No fields configured for Feature.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions