Add workspace snapshots and full reset

Implement the 2.8.0 workspace milestone with named snapshots and full-sandbox reset across the CLI, Python SDK, and MCP server.

Persist the immutable baseline plus named snapshot archives under each workspace, add workspace reset metadata, and make reset recreate the sandbox while clearing command history, shells, and services without changing the workspace identity or diff baseline.

Refresh the 2.8.0 docs, roadmap, and Python example around reset-over-repair, then validate with uv lock, UV_CACHE_DIR=.uv-cache make check, UV_CACHE_DIR=.uv-cache make dist-check, and a real guest-backed create/snapshot/reset/diff smoke test outside the sandbox.
This commit is contained in:
Thales Maciel 2026-03-12 12:41:11 -03:00
parent f504f0a331
commit 18b8fd2a7d
20 changed files with 1429 additions and 29 deletions

View file

@ -22,7 +22,7 @@ Networking: tun=yes ip_forward=yes
```bash
$ uvx --from pyro-mcp pyro env list
Catalog version: 2.7.0
Catalog version: 2.8.0
debian:12 [installed|not installed] Debian 12 environment with Git preinstalled for common agent workflows.
debian:12-base [installed|not installed] Minimal Debian 12 environment for shell and core Unix tooling.
debian:12-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled.
@ -73,6 +73,8 @@ $ uvx --from pyro-mcp pyro demo
$ uvx --from pyro-mcp pyro workspace create debian:12 --seed-path ./repo
$ uvx --from pyro-mcp pyro workspace sync push WORKSPACE_ID ./changes
$ uvx --from pyro-mcp pyro workspace diff WORKSPACE_ID
$ uvx --from pyro-mcp pyro workspace snapshot create WORKSPACE_ID checkpoint
$ uvx --from pyro-mcp pyro workspace reset WORKSPACE_ID --snapshot checkpoint
$ uvx --from pyro-mcp pyro workspace export WORKSPACE_ID note.txt --output ./note.txt
$ uvx --from pyro-mcp pyro workspace shell open WORKSPACE_ID
$ uvx --from pyro-mcp pyro workspace service start WORKSPACE_ID app --ready-file .ready -- sh -lc 'touch .ready && while true; do sleep 60; done'
@ -107,6 +109,17 @@ $ uvx --from pyro-mcp pyro workspace diff WORKSPACE_ID
+++ b/src/note.txt
@@ ...
$ uvx --from pyro-mcp pyro workspace snapshot create WORKSPACE_ID checkpoint
[workspace-snapshot-create] snapshot_name=checkpoint kind=named entry_count=... bytes_written=...
$ uvx --from pyro-mcp pyro workspace reset WORKSPACE_ID --snapshot checkpoint
Workspace reset from snapshot: checkpoint (named)
[workspace-reset] destination=/workspace entry_count=... bytes_written=...
Workspace ID: ...
State: started
Command count: 0
Reset count: 1
$ uvx --from pyro-mcp pyro workspace export WORKSPACE_ID src/note.txt --output ./note.txt
[workspace-export] workspace_id=... workspace_path=/workspace/src/note.txt output_path=... artifact_type=file entry_count=... bytes_written=... execution_mode=guest_vsock
@ -157,10 +170,11 @@ $ uvx --from pyro-mcp pyro workspace service stop WORKSPACE_ID worker
Use `--seed-path` when the workspace should start from a host directory or a local
`.tar` / `.tar.gz` / `.tgz` archive instead of an empty `/workspace`. Use
`pyro workspace sync push` when you need to import later host-side changes into a started
workspace. Sync is non-atomic in `2.7.0`; if it fails partway through, delete and recreate the
workspace. Use `pyro workspace diff` to compare the current `/workspace` tree to its immutable
create-time baseline, and `pyro workspace export` to copy one changed file or directory back to
the host. Use `pyro workspace exec` for one-shot commands and `pyro workspace shell *` when you
workspace. Sync is non-atomic in `2.8.0`; if it fails partway through, prefer `pyro workspace reset`
to recover from `baseline` or one named snapshot. Use `pyro workspace diff` to compare the current
`/workspace` tree to its immutable create-time baseline, `pyro workspace snapshot *` to create
named checkpoints, and `pyro workspace export` to copy one changed file or directory back to the
host. Use `pyro workspace exec` for one-shot commands and `pyro workspace shell *` when you
need a persistent interactive PTY session in that same workspace. Use `pyro workspace service *`
when the workspace needs long-running background processes with typed readiness checks. Internal
service state and logs stay outside `/workspace`, so service runtime data does not appear in

View file

@ -83,7 +83,7 @@ uvx --from pyro-mcp pyro env list
Expected output:
```bash
Catalog version: 2.7.0
Catalog version: 2.8.0
debian:12 [installed|not installed] Debian 12 environment with Git preinstalled for common agent workflows.
debian:12-base [installed|not installed] Minimal Debian 12 environment for shell and core Unix tooling.
debian:12-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled.
@ -177,6 +177,7 @@ After the CLI path works, you can move on to:
- persistent workspaces: `pyro workspace create debian:12 --seed-path ./repo`
- live workspace updates: `pyro workspace sync push WORKSPACE_ID ./changes`
- baseline diff: `pyro workspace diff WORKSPACE_ID`
- snapshots and reset: `pyro workspace snapshot create WORKSPACE_ID checkpoint` and `pyro workspace reset WORKSPACE_ID --snapshot checkpoint`
- host export: `pyro workspace export WORKSPACE_ID note.txt --output ./note.txt`
- interactive shells: `pyro workspace shell open WORKSPACE_ID`
- long-running services: `pyro workspace service start WORKSPACE_ID app --ready-file .ready -- sh -lc 'touch .ready && while true; do sleep 60; done'`
@ -193,6 +194,9 @@ pyro workspace create debian:12 --seed-path ./repo
pyro workspace sync push WORKSPACE_ID ./changes --dest src
pyro workspace exec WORKSPACE_ID -- cat src/note.txt
pyro workspace diff WORKSPACE_ID
pyro workspace snapshot create WORKSPACE_ID checkpoint
pyro workspace reset WORKSPACE_ID --snapshot checkpoint
pyro workspace reset WORKSPACE_ID
pyro workspace export WORKSPACE_ID src/note.txt --output ./note.txt
pyro workspace shell open WORKSPACE_ID
pyro workspace shell write WORKSPACE_ID SHELL_ID --input 'pwd'
@ -213,9 +217,10 @@ Workspace commands default to the persistent `/workspace` directory inside the g
the identifier programmatically, use `--json` and read the `workspace_id` field. Use `--seed-path`
when the workspace should start from a host directory or a local `.tar` / `.tar.gz` / `.tgz`
archive. Use `pyro workspace sync push` for later host-side changes to a started workspace. Sync
is non-atomic in `2.7.0`; if it fails partway through, delete and recreate the workspace from its
seed. Use `pyro workspace diff` to compare the current workspace tree to its immutable create-time
baseline, and `pyro workspace export` to copy one changed file or directory back to the host. Use
is non-atomic in `2.8.0`; if it fails partway through, prefer `pyro workspace reset` to recover
from `baseline` or one named snapshot. Use `pyro workspace diff` to compare the current workspace
tree to its immutable create-time baseline, `pyro workspace snapshot *` to capture named
checkpoints, and `pyro workspace export` to copy one changed file or directory back to the host. Use
`pyro workspace exec` for one-shot commands and `pyro workspace shell *` when you need an
interactive PTY that survives across separate calls. Use `pyro workspace service *` when the
workspace needs long-running background processes with typed readiness probes. Service metadata and

View file

@ -24,6 +24,10 @@ Top-level commands:
- `pyro workspace exec`
- `pyro workspace export`
- `pyro workspace diff`
- `pyro workspace snapshot create`
- `pyro workspace snapshot list`
- `pyro workspace snapshot delete`
- `pyro workspace reset`
- `pyro workspace service start`
- `pyro workspace service list`
- `pyro workspace service status`
@ -63,11 +67,14 @@ Behavioral guarantees:
- `pyro workspace sync push WORKSPACE_ID SOURCE_PATH [--dest WORKSPACE_PATH]` imports later host-side directory or archive content into a started workspace.
- `pyro workspace export WORKSPACE_ID PATH --output HOST_PATH` exports one file or directory from `/workspace` back to the host.
- `pyro workspace diff WORKSPACE_ID` compares the current `/workspace` tree to the immutable create-time baseline.
- `pyro workspace snapshot *` manages explicit named snapshots in addition to the implicit `baseline`.
- `pyro workspace reset WORKSPACE_ID [--snapshot SNAPSHOT_NAME|baseline]` recreates the full sandbox and restores `/workspace` from the chosen snapshot.
- `pyro workspace service *` manages long-running named services inside one started workspace with typed readiness probes.
- `pyro workspace exec` runs in the persistent `/workspace` for that workspace and does not auto-clean.
- `pyro workspace shell *` manages persistent PTY sessions inside a started workspace.
- `pyro workspace logs` returns persisted command history for that workspace until `pyro workspace delete`.
- Workspace create/status results expose `workspace_seed` metadata describing how `/workspace` was initialized.
- Workspace create/status/reset results expose `reset_count` and `last_reset_at`.
- `pyro workspace status` includes aggregate `service_count` and `running_service_count` fields.
## Python SDK Contract
@ -89,6 +96,10 @@ Supported public entrypoints:
- `Pyro.push_workspace_sync(workspace_id, source_path, *, dest="/workspace")`
- `Pyro.export_workspace(workspace_id, path, *, output_path)`
- `Pyro.diff_workspace(workspace_id)`
- `Pyro.create_snapshot(workspace_id, snapshot_name)`
- `Pyro.list_snapshots(workspace_id)`
- `Pyro.delete_snapshot(workspace_id, snapshot_name)`
- `Pyro.reset_workspace(workspace_id, *, snapshot="baseline")`
- `Pyro.start_service(workspace_id, service_name, *, command, cwd="/workspace", readiness=None, ready_timeout_seconds=30, ready_interval_ms=500)`
- `Pyro.list_services(workspace_id)`
- `Pyro.status_service(workspace_id, service_name)`
@ -124,6 +135,10 @@ Stable public method names:
- `push_workspace_sync(workspace_id, source_path, *, dest="/workspace")`
- `export_workspace(workspace_id, path, *, output_path)`
- `diff_workspace(workspace_id)`
- `create_snapshot(workspace_id, snapshot_name)`
- `list_snapshots(workspace_id)`
- `delete_snapshot(workspace_id, snapshot_name)`
- `reset_workspace(workspace_id, *, snapshot="baseline")`
- `start_service(workspace_id, service_name, *, command, cwd="/workspace", readiness=None, ready_timeout_seconds=30, ready_interval_ms=500)`
- `list_services(workspace_id)`
- `status_service(workspace_id, service_name)`
@ -157,6 +172,10 @@ Behavioral defaults:
- `Pyro.push_workspace_sync(...)` imports later host-side directory or archive content into a started workspace.
- `Pyro.export_workspace(...)` exports one file or directory from `/workspace` to an explicit host path.
- `Pyro.diff_workspace(...)` compares the current `/workspace` tree to the immutable create-time baseline.
- `Pyro.create_snapshot(...)` captures one named `/workspace` checkpoint.
- `Pyro.list_snapshots(...)` lists the implicit `baseline` plus any named snapshots.
- `Pyro.delete_snapshot(...)` deletes one named snapshot while leaving `baseline` intact.
- `Pyro.reset_workspace(...)` recreates the full sandbox from `baseline` or one named snapshot and clears command, shell, and service history.
- `Pyro.start_service(...)` starts one named long-running process in a started workspace and waits for its typed readiness probe when configured.
- `Pyro.list_services(...)`, `Pyro.status_service(...)`, `Pyro.logs_service(...)`, and `Pyro.stop_service(...)` manage those persisted workspace services.
- `Pyro.exec_vm(...)` runs one command and auto-cleans that VM after the exec completes.
@ -190,6 +209,10 @@ Persistent workspace tools:
- `workspace_exec`
- `workspace_export`
- `workspace_diff`
- `snapshot_create`
- `snapshot_list`
- `snapshot_delete`
- `workspace_reset`
- `service_start`
- `service_list`
- `service_status`
@ -214,6 +237,8 @@ Behavioral defaults:
- `workspace_sync_push` imports later host-side directory or archive content into a started workspace, with an optional `dest` under `/workspace`.
- `workspace_export` exports one file or directory from `/workspace` to an explicit host path.
- `workspace_diff` compares the current `/workspace` tree to the immutable create-time baseline.
- `snapshot_create`, `snapshot_list`, and `snapshot_delete` manage explicit named snapshots in addition to the implicit `baseline`.
- `workspace_reset` recreates the full sandbox and restores `/workspace` from `baseline` or one named snapshot.
- `service_start`, `service_list`, `service_status`, `service_logs`, and `service_stop` manage persistent named services inside a started workspace.
- `vm_exec` runs one command and auto-cleans that VM after the exec completes.
- `workspace_exec` runs one command in a persistent `/workspace` and leaves the workspace alive.

View file

@ -2,14 +2,15 @@
This roadmap turns the agent-workspace vision into release-sized milestones.
Current baseline is `2.7.0`:
Current baseline is `2.8.0`:
- workspace persistence exists and the public surface is now workspace-first
- host crossing currently covers create-time seeding, later sync push, and explicit export
- persistent PTY shell sessions exist alongside one-shot `workspace exec`
- immutable create-time baselines now power whole-workspace diff
- multi-service lifecycle exists with typed readiness and aggregate workspace status counts
- no snapshot, reset, or secrets contract exists yet
- named snapshots and full workspace reset now exist
- no secrets or explicit host port publication contract exists yet
Locked roadmap decisions:
@ -32,7 +33,7 @@ also expected to update:
2. [`2.5.0` PTY Shell Sessions](task-workspace-ga/2.5.0-pty-shell-sessions.md) - Done
3. [`2.6.0` Structured Export And Baseline Diff](task-workspace-ga/2.6.0-structured-export-and-baseline-diff.md) - Done
4. [`2.7.0` Service Lifecycle And Typed Readiness](task-workspace-ga/2.7.0-service-lifecycle-and-typed-readiness.md) - Done
5. [`2.8.0` Named Snapshots And Reset](task-workspace-ga/2.8.0-named-snapshots-and-reset.md)
5. [`2.8.0` Named Snapshots And Reset](task-workspace-ga/2.8.0-named-snapshots-and-reset.md) - Done
6. [`2.9.0` Secrets](task-workspace-ga/2.9.0-secrets.md)
7. [`2.10.0` Network Policy And Host Port Publication](task-workspace-ga/2.10.0-network-policy-and-host-port-publication.md)
8. [`3.0.0` Stable Workspace Product](task-workspace-ga/3.0.0-stable-workspace-product.md)

View file

@ -1,5 +1,7 @@
# `2.8.0` Named Snapshots And Reset
Status: Done
## Goal
Turn reset into a first-class workflow primitive and add explicit named