Promote stable workspace product for 3.0.0
Freeze the current workspace-first surface as the stable 3.0 contract and reposition the landing docs, CLI help, and public contract around the stable workspace path after the one-shot proof. Bump the package and catalog compatibility to 3.0.0, add a dedicated workspace walkthrough tape/GIF, and mark the 3.0.0 roadmap milestone done while keeping runtime capability unchanged in this release. Validation: uv lock; UV_CACHE_DIR=.uv-cache make check; UV_CACHE_DIR=.uv-cache make dist-check; UV_CACHE_DIR=.uv-cache uv build; UV_CACHE_DIR=.uv-cache uvx --from twine twine check dist/*; built-wheel CLI smoke for pyro --help and pyro workspace --help; vhs validate plus rendered workspace-first-run.gif outside the sandbox because vhs crashes when sandboxed.
This commit is contained in:
parent
c82f4629b2
commit
f2d20ef30a
15 changed files with 255 additions and 42 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
|
@ -2,6 +2,16 @@
|
||||||
|
|
||||||
All notable user-visible changes to `pyro-mcp` are documented here.
|
All notable user-visible changes to `pyro-mcp` are documented here.
|
||||||
|
|
||||||
|
## 3.0.0
|
||||||
|
|
||||||
|
- Promoted the workspace-first product surface to stable across the CLI, Python SDK, and MCP
|
||||||
|
server, with `pyro run` retained as the stable one-shot entrypoint.
|
||||||
|
- Repositioned the main docs, help text, examples, and walkthrough assets around the stable
|
||||||
|
workspace path: create, sync, exec or shell, services, snapshots/reset, diff/export, and
|
||||||
|
delete.
|
||||||
|
- Froze the `3.x` public contract around the current workspace surface without introducing new
|
||||||
|
runtime capability in this release.
|
||||||
|
|
||||||
## 2.10.0
|
## 2.10.0
|
||||||
|
|
||||||
- Replaced the workspace-level boolean network toggle with explicit workspace network policies:
|
- Replaced the workspace-level boolean network toggle with explicit workspace network policies:
|
||||||
|
|
|
||||||
49
README.md
49
README.md
|
|
@ -1,10 +1,10 @@
|
||||||
# pyro-mcp
|
# pyro-mcp
|
||||||
|
|
||||||
`pyro-mcp` runs one-shot commands and repeated workspaces inside ephemeral Firecracker microVMs using curated Linux environments such as `debian:12`.
|
`pyro-mcp` is a stable agent workspace product for one-shot commands and persistent work inside ephemeral Firecracker microVMs using curated Linux environments such as `debian:12`.
|
||||||
|
|
||||||
[](https://pypi.org/project/pyro-mcp/)
|
[](https://pypi.org/project/pyro-mcp/)
|
||||||
|
|
||||||
This is for coding agents, MCP clients, and developers who want isolated command execution in ephemeral microVMs.
|
This is for coding agents, MCP clients, and developers who want isolated command execution and stable disposable workspaces in ephemeral microVMs.
|
||||||
|
|
||||||
It exposes the same runtime in three public forms:
|
It exposes the same runtime in three public forms:
|
||||||
|
|
||||||
|
|
@ -18,9 +18,10 @@ It exposes the same runtime in three public forms:
|
||||||
- Vision: [docs/vision.md](docs/vision.md)
|
- Vision: [docs/vision.md](docs/vision.md)
|
||||||
- Workspace roadmap: [docs/roadmap/task-workspace-ga.md](docs/roadmap/task-workspace-ga.md)
|
- Workspace roadmap: [docs/roadmap/task-workspace-ga.md](docs/roadmap/task-workspace-ga.md)
|
||||||
- First run transcript: [docs/first-run.md](docs/first-run.md)
|
- First run transcript: [docs/first-run.md](docs/first-run.md)
|
||||||
|
- Stable workspace walkthrough GIF: [docs/assets/workspace-first-run.gif](docs/assets/workspace-first-run.gif)
|
||||||
- Terminal walkthrough GIF: [docs/assets/first-run.gif](docs/assets/first-run.gif)
|
- Terminal walkthrough GIF: [docs/assets/first-run.gif](docs/assets/first-run.gif)
|
||||||
- PyPI package: [pypi.org/project/pyro-mcp](https://pypi.org/project/pyro-mcp/)
|
- PyPI package: [pypi.org/project/pyro-mcp](https://pypi.org/project/pyro-mcp/)
|
||||||
- What's new in 2.10.0: [CHANGELOG.md#2100](CHANGELOG.md#2100)
|
- What's new in 3.0.0: [CHANGELOG.md#300](CHANGELOG.md#300)
|
||||||
- Host requirements: [docs/host-requirements.md](docs/host-requirements.md)
|
- Host requirements: [docs/host-requirements.md](docs/host-requirements.md)
|
||||||
- Integration targets: [docs/integrations.md](docs/integrations.md)
|
- Integration targets: [docs/integrations.md](docs/integrations.md)
|
||||||
- Public contract: [docs/public-contract.md](docs/public-contract.md)
|
- Public contract: [docs/public-contract.md](docs/public-contract.md)
|
||||||
|
|
@ -57,7 +58,7 @@ What success looks like:
|
||||||
```bash
|
```bash
|
||||||
Platform: linux-x86_64
|
Platform: linux-x86_64
|
||||||
Runtime: PASS
|
Runtime: PASS
|
||||||
Catalog version: 2.10.0
|
Catalog version: 3.0.0
|
||||||
...
|
...
|
||||||
[pull] phase=install environment=debian:12
|
[pull] phase=install environment=debian:12
|
||||||
[pull] phase=ready environment=debian:12
|
[pull] phase=ready environment=debian:12
|
||||||
|
|
@ -73,6 +74,40 @@ git version ...
|
||||||
The first pull downloads an OCI environment from public Docker Hub, requires outbound HTTPS
|
The first pull downloads an OCI environment from public Docker Hub, requires outbound HTTPS
|
||||||
access to `registry-1.docker.io`, and needs local cache space for the guest image.
|
access to `registry-1.docker.io`, and needs local cache space for the guest image.
|
||||||
|
|
||||||
|
## Stable Workspace Path
|
||||||
|
|
||||||
|
`pyro run` is the stable one-shot entrypoint. `pyro workspace ...` is the stable path when an
|
||||||
|
agent needs one sandbox to stay alive across repeated commands, shells, services, checkpoints,
|
||||||
|
diffs, exports, and reset.
|
||||||
|
|
||||||
|
The commands below use plain `pyro ...`. Run the same flow with `uvx --from pyro-mcp pyro ...`
|
||||||
|
for the published package, or `uv run pyro ...` from a source checkout.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv tool install pyro-mcp
|
||||||
|
WORKSPACE_ID="$(pyro workspace create debian:12 --seed-path ./repo --json | python -c 'import json,sys; print(json.load(sys.stdin)["workspace_id"])')"
|
||||||
|
pyro workspace sync push "$WORKSPACE_ID" ./changes
|
||||||
|
pyro workspace exec "$WORKSPACE_ID" -- cat note.txt
|
||||||
|
pyro workspace snapshot create "$WORKSPACE_ID" checkpoint
|
||||||
|
pyro workspace service start "$WORKSPACE_ID" web --ready-file .web-ready -- sh -lc 'touch .web-ready && while true; do sleep 60; done'
|
||||||
|
pyro workspace reset "$WORKSPACE_ID" --snapshot checkpoint
|
||||||
|
pyro workspace export "$WORKSPACE_ID" note.txt --output ./note.txt
|
||||||
|
pyro workspace delete "$WORKSPACE_ID"
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
That stable workspace path gives you:
|
||||||
|
|
||||||
|
- initial host-in seeding with `--seed-path`
|
||||||
|
- later host-in updates with `workspace sync push`
|
||||||
|
- one-shot commands with `workspace exec` and persistent PTYs with `workspace shell *`
|
||||||
|
- long-running processes with `workspace service *`
|
||||||
|
- explicit checkpoints with `workspace snapshot *`
|
||||||
|
- full-sandbox recovery with `workspace reset`
|
||||||
|
- baseline comparison with `workspace diff`
|
||||||
|
- explicit host-out export with `workspace export`
|
||||||
|
|
||||||
After the quickstart works:
|
After the quickstart works:
|
||||||
|
|
||||||
- prove the full one-shot lifecycle with `uvx --from pyro-mcp pyro demo`
|
- prove the full one-shot lifecycle with `uvx --from pyro-mcp pyro demo`
|
||||||
|
|
@ -141,7 +176,7 @@ uvx --from pyro-mcp pyro env list
|
||||||
Expected output:
|
Expected output:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
Catalog version: 2.10.0
|
Catalog version: 3.0.0
|
||||||
debian:12 [installed|not installed] Debian 12 environment with Git preinstalled for common agent workflows.
|
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-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.
|
debian:12-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled.
|
||||||
|
|
@ -205,7 +240,7 @@ When you are done evaluating and want to remove stale cached environments, run `
|
||||||
If you prefer a fuller copy-pasteable transcript, see [docs/first-run.md](docs/first-run.md).
|
If you prefer a fuller copy-pasteable transcript, see [docs/first-run.md](docs/first-run.md).
|
||||||
The walkthrough GIF above was rendered from [docs/assets/first-run.tape](docs/assets/first-run.tape) using [scripts/render_tape.sh](scripts/render_tape.sh).
|
The walkthrough GIF above was rendered from [docs/assets/first-run.tape](docs/assets/first-run.tape) using [scripts/render_tape.sh](scripts/render_tape.sh).
|
||||||
|
|
||||||
## Persistent Workspaces
|
## Stable Workspaces
|
||||||
|
|
||||||
Use `pyro run` for one-shot commands. Use `pyro workspace ...` when you need repeated commands in one
|
Use `pyro run` for one-shot commands. Use `pyro workspace ...` when you need repeated commands in one
|
||||||
workspace without recreating the sandbox every time.
|
workspace without recreating the sandbox every time.
|
||||||
|
|
@ -248,7 +283,7 @@ Persistent workspaces start in `/workspace` and keep command history until you d
|
||||||
machine consumption, add `--json` and read the returned `workspace_id`. Use `--seed-path` when
|
machine consumption, add `--json` and read the returned `workspace_id`. Use `--seed-path` when
|
||||||
you want the workspace to start from a host directory or a local `.tar` / `.tar.gz` / `.tgz`
|
you want the workspace to 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 want to import
|
archive instead of an empty workspace. Use `pyro workspace sync push` when you want to import
|
||||||
later host-side changes into a started workspace. Sync is non-atomic in `2.10.0`; if it fails
|
later host-side changes into a started workspace. Sync is non-atomic in `3.0.0`; if it fails
|
||||||
partway through, prefer `pyro workspace reset` to recover from `baseline` or one named snapshot.
|
partway through, prefer `pyro workspace reset` to recover from `baseline` or one named snapshot.
|
||||||
Use `pyro workspace diff` to compare the live `/workspace` tree to its immutable create-time
|
Use `pyro workspace diff` to compare the live `/workspace` tree to its immutable create-time
|
||||||
baseline, and `pyro workspace export` to copy one changed file or directory back to the host. Use
|
baseline, and `pyro workspace export` to copy one changed file or directory back to the host. Use
|
||||||
|
|
|
||||||
BIN
docs/assets/workspace-first-run.gif
Normal file
BIN
docs/assets/workspace-first-run.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
98
docs/assets/workspace-first-run.tape
Normal file
98
docs/assets/workspace-first-run.tape
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
Output docs/assets/workspace-first-run.gif
|
||||||
|
|
||||||
|
Require uv
|
||||||
|
Require python3
|
||||||
|
|
||||||
|
Set Shell "zsh"
|
||||||
|
Set FontSize 18
|
||||||
|
Set Width 1400
|
||||||
|
Set Height 860
|
||||||
|
Set Theme "Dracula"
|
||||||
|
Set TypingSpeed 35ms
|
||||||
|
Set Padding 24
|
||||||
|
Set WindowBar Colorful
|
||||||
|
|
||||||
|
Hide
|
||||||
|
Type "cd /home/thales/projects/personal/pyro"
|
||||||
|
Enter
|
||||||
|
Type "setopt interactivecomments"
|
||||||
|
Enter
|
||||||
|
Type "export UV_CACHE_DIR=.uv-cache"
|
||||||
|
Enter
|
||||||
|
Type "export PYRO_ENVIRONMENT_CACHE_DIR=$(mktemp -d)"
|
||||||
|
Enter
|
||||||
|
Type "alias pyro='uv run pyro'"
|
||||||
|
Enter
|
||||||
|
Type "SEED_DIR=$(mktemp -d)"
|
||||||
|
Enter
|
||||||
|
Type "SYNC_DIR=$(mktemp -d)"
|
||||||
|
Enter
|
||||||
|
Type "EXPORT_DIR=$(mktemp -d)"
|
||||||
|
Enter
|
||||||
|
Type 'printf "%s\n" "hello from seed" > "$SEED_DIR/note.txt"'
|
||||||
|
Enter
|
||||||
|
Type 'printf "%s\n" "hello from sync" > "$SYNC_DIR/note.txt"'
|
||||||
|
Enter
|
||||||
|
Type "pyro env pull debian:12 >/dev/null"
|
||||||
|
Enter
|
||||||
|
Show
|
||||||
|
|
||||||
|
Type "# Create a stable workspace from host content and capture its id"
|
||||||
|
Enter
|
||||||
|
Sleep 700ms
|
||||||
|
Type 'pyro workspace create debian:12 --seed-path "$SEED_DIR" --json | tee /tmp/pyro-workspace.json'
|
||||||
|
Enter
|
||||||
|
Sleep 2200ms
|
||||||
|
|
||||||
|
Hide
|
||||||
|
Type 'export WORKSPACE_ID=$(python3 -c "import json; print(json.load(open(\"/tmp/pyro-workspace.json\", encoding=\"utf-8\"))[\"workspace_id\"])")'
|
||||||
|
Enter
|
||||||
|
Show
|
||||||
|
|
||||||
|
Type "# Push a later host-side change into the same workspace"
|
||||||
|
Enter
|
||||||
|
Sleep 700ms
|
||||||
|
Type 'pyro workspace sync push "$WORKSPACE_ID" "$SYNC_DIR"'
|
||||||
|
Enter
|
||||||
|
Sleep 1800ms
|
||||||
|
|
||||||
|
Type "# Run inside the live workspace"
|
||||||
|
Enter
|
||||||
|
Sleep 700ms
|
||||||
|
Type 'pyro workspace exec "$WORKSPACE_ID" -- cat note.txt'
|
||||||
|
Enter
|
||||||
|
Sleep 2000ms
|
||||||
|
|
||||||
|
Type "# Capture a checkpoint, then start one long-running service"
|
||||||
|
Enter
|
||||||
|
Sleep 700ms
|
||||||
|
Type 'pyro workspace snapshot create "$WORKSPACE_ID" checkpoint'
|
||||||
|
Enter
|
||||||
|
Sleep 1800ms
|
||||||
|
Type 'pyro workspace service start "$WORKSPACE_ID" web --ready-file .web-ready -- sh -lc "touch .web-ready && while true; do sleep 60; done"'
|
||||||
|
Enter
|
||||||
|
Sleep 2200ms
|
||||||
|
|
||||||
|
Type "# Reset the full sandbox back to that checkpoint"
|
||||||
|
Enter
|
||||||
|
Sleep 700ms
|
||||||
|
Type 'pyro workspace reset "$WORKSPACE_ID" --snapshot checkpoint'
|
||||||
|
Enter
|
||||||
|
Sleep 2200ms
|
||||||
|
|
||||||
|
Type "# Export one file back to the host and inspect it locally"
|
||||||
|
Enter
|
||||||
|
Sleep 700ms
|
||||||
|
Type 'pyro workspace export "$WORKSPACE_ID" note.txt --output "$EXPORT_DIR/note.txt"'
|
||||||
|
Enter
|
||||||
|
Sleep 1800ms
|
||||||
|
Type 'cat "$EXPORT_DIR/note.txt"'
|
||||||
|
Enter
|
||||||
|
Sleep 1800ms
|
||||||
|
|
||||||
|
Type "# Remove the workspace when the loop is done"
|
||||||
|
Enter
|
||||||
|
Sleep 700ms
|
||||||
|
Type 'pyro workspace delete "$WORKSPACE_ID"'
|
||||||
|
Enter
|
||||||
|
Sleep 2000ms
|
||||||
|
|
@ -22,7 +22,7 @@ Networking: tun=yes ip_forward=yes
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ uvx --from pyro-mcp pyro env list
|
$ uvx --from pyro-mcp pyro env list
|
||||||
Catalog version: 2.10.0
|
Catalog version: 3.0.0
|
||||||
debian:12 [installed|not installed] Debian 12 environment with Git preinstalled for common agent workflows.
|
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-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.
|
debian:12-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled.
|
||||||
|
|
@ -66,7 +66,25 @@ The guest command output and the `[run] ...` summary are written to different st
|
||||||
may appear in either order in terminals or capture tools. Use `--json` if you need a
|
may appear in either order in terminals or capture tools. Use `--json` if you need a
|
||||||
deterministic structured result.
|
deterministic structured result.
|
||||||
|
|
||||||
## 5. Optional next steps
|
## 5. Continue into the stable workspace path
|
||||||
|
|
||||||
|
The commands below use the published-package form. The same stable workspace path works with an
|
||||||
|
installed `pyro` binary by dropping the `uvx --from pyro-mcp` prefix, or with `uv run pyro` from
|
||||||
|
a source checkout.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ uvx --from pyro-mcp pyro workspace create debian:12 --seed-path ./repo --json | tee /tmp/pyro-workspace.json
|
||||||
|
$ export WORKSPACE_ID="$(python -c 'import json,sys; print(json.load(sys.stdin)["workspace_id"])' < /tmp/pyro-workspace.json)"
|
||||||
|
$ uvx --from pyro-mcp pyro workspace sync push "$WORKSPACE_ID" ./changes
|
||||||
|
$ uvx --from pyro-mcp pyro workspace exec "$WORKSPACE_ID" -- cat note.txt
|
||||||
|
$ uvx --from pyro-mcp pyro workspace snapshot create "$WORKSPACE_ID" checkpoint
|
||||||
|
$ uvx --from pyro-mcp pyro workspace service start "$WORKSPACE_ID" web --ready-file .web-ready -- sh -lc 'touch .web-ready && while true; do sleep 60; done'
|
||||||
|
$ 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 delete "$WORKSPACE_ID"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Optional one-shot demo and expanded workspace flow
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ uvx --from pyro-mcp pyro demo
|
$ uvx --from pyro-mcp pyro demo
|
||||||
|
|
@ -187,7 +205,7 @@ $ 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
|
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
|
`.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
|
`pyro workspace sync push` when you need to import later host-side changes into a started
|
||||||
workspace. Sync is non-atomic in `2.10.0`; if it fails partway through, prefer `pyro workspace reset`
|
workspace. Sync is non-atomic in `3.0.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
|
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
|
`/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
|
named checkpoints, and `pyro workspace export` to copy one changed file or directory back to the
|
||||||
|
|
@ -203,6 +221,10 @@ materialized at `/run/pyro-secrets/<name>`, and `--secret-env SECRET_NAME[=ENV_V
|
||||||
secret into one exec, shell, or service call without storing that environment mapping on the
|
secret into one exec, shell, or service call without storing that environment mapping on the
|
||||||
workspace itself.
|
workspace itself.
|
||||||
|
|
||||||
|
The stable workspace walkthrough GIF in the README is rendered from
|
||||||
|
[docs/assets/workspace-first-run.tape](assets/workspace-first-run.tape) with
|
||||||
|
[scripts/render_tape.sh](../scripts/render_tape.sh).
|
||||||
|
|
||||||
Example output:
|
Example output:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,8 @@ pyro run debian:12 -- git --version
|
||||||
|
|
||||||
If you are running from a repo checkout instead, replace `pyro` with `uv run pyro`.
|
If you are running from a repo checkout instead, replace `pyro` with `uv run pyro`.
|
||||||
|
|
||||||
|
After that one-shot proof works, continue into the stable workspace path with `pyro workspace ...`.
|
||||||
|
|
||||||
### 1. Check the host first
|
### 1. Check the host first
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -83,7 +85,7 @@ uvx --from pyro-mcp pyro env list
|
||||||
Expected output:
|
Expected output:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
Catalog version: 2.10.0
|
Catalog version: 3.0.0
|
||||||
debian:12 [installed|not installed] Debian 12 environment with Git preinstalled for common agent workflows.
|
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-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.
|
debian:12-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled.
|
||||||
|
|
@ -131,7 +133,34 @@ deterministic structured result.
|
||||||
If guest execution is unavailable, the command fails unless you explicitly pass
|
If guest execution is unavailable, the command fails unless you explicitly pass
|
||||||
`--allow-host-compat`.
|
`--allow-host-compat`.
|
||||||
|
|
||||||
## 5. Optional demo proof point
|
## 5. Continue into the stable workspace path
|
||||||
|
|
||||||
|
The commands below use plain `pyro ...`. Run the same flow with `uvx --from pyro-mcp pyro ...`
|
||||||
|
for the published package, or `uv run pyro ...` from a source checkout.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv tool install pyro-mcp
|
||||||
|
WORKSPACE_ID="$(pyro workspace create debian:12 --seed-path ./repo --json | python -c 'import json,sys; print(json.load(sys.stdin)["workspace_id"])')"
|
||||||
|
pyro workspace sync push "$WORKSPACE_ID" ./changes
|
||||||
|
pyro workspace exec "$WORKSPACE_ID" -- cat note.txt
|
||||||
|
pyro workspace snapshot create "$WORKSPACE_ID" checkpoint
|
||||||
|
pyro workspace service start "$WORKSPACE_ID" web --ready-file .web-ready -- sh -lc 'touch .web-ready && while true; do sleep 60; done'
|
||||||
|
pyro workspace reset "$WORKSPACE_ID" --snapshot checkpoint
|
||||||
|
pyro workspace export "$WORKSPACE_ID" note.txt --output ./note.txt
|
||||||
|
pyro workspace delete "$WORKSPACE_ID"
|
||||||
|
```
|
||||||
|
|
||||||
|
This is the stable persistent-workspace contract:
|
||||||
|
|
||||||
|
- `workspace create` seeds `/workspace`
|
||||||
|
- `workspace sync push` imports later host-side changes
|
||||||
|
- `workspace exec` and `workspace shell *` keep work inside one sandbox
|
||||||
|
- `workspace service *` manages long-running processes with typed readiness
|
||||||
|
- `workspace snapshot *` and `workspace reset` make reset-over-repair explicit
|
||||||
|
- `workspace diff` compares against the immutable create-time baseline
|
||||||
|
- `workspace export` copies results back to the host
|
||||||
|
|
||||||
|
## 6. Optional demo proof point
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uvx --from pyro-mcp pyro demo
|
uvx --from pyro-mcp pyro demo
|
||||||
|
|
@ -188,7 +217,7 @@ After the CLI path works, you can move on to:
|
||||||
- Python SDK: `from pyro_mcp import Pyro`
|
- Python SDK: `from pyro_mcp import Pyro`
|
||||||
- Demos: `pyro demo` or `pyro demo --network`
|
- Demos: `pyro demo` or `pyro demo --network`
|
||||||
|
|
||||||
## Persistent Workspace
|
## Stable Workspace
|
||||||
|
|
||||||
Use `pyro workspace ...` when you need repeated commands in one sandbox instead of one-shot `pyro run`.
|
Use `pyro workspace ...` when you need repeated commands in one sandbox instead of one-shot `pyro run`.
|
||||||
|
|
||||||
|
|
@ -225,7 +254,7 @@ 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`
|
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`
|
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
|
archive. Use `pyro workspace sync push` for later host-side changes to a started workspace. Sync
|
||||||
is non-atomic in `2.10.0`; if it fails partway through, prefer `pyro workspace reset` to recover
|
is non-atomic in `3.0.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
|
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
|
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
|
checkpoints, and `pyro workspace export` to copy one changed file or directory back to the host. Use
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ CLI path in [install.md](install.md) or [first-run.md](first-run.md).
|
||||||
|
|
||||||
## Recommended Default
|
## Recommended Default
|
||||||
|
|
||||||
Use `vm_run` first for one-shot commands.
|
Use `vm_run` first for one-shot commands, then move to the stable workspace surface when the
|
||||||
|
agent needs to inhabit one sandbox across multiple calls.
|
||||||
|
|
||||||
That keeps the model-facing contract small:
|
That keeps the model-facing contract small:
|
||||||
|
|
||||||
|
|
@ -16,8 +17,8 @@ That keeps the model-facing contract small:
|
||||||
- one ephemeral VM
|
- one ephemeral VM
|
||||||
- automatic cleanup
|
- automatic cleanup
|
||||||
|
|
||||||
Move to `workspace_*` only when the agent truly needs repeated commands in one workspace across
|
Move to `workspace_*` when the agent needs repeated commands, shells, services, snapshots, reset,
|
||||||
multiple calls.
|
diff, or export in one stable workspace across multiple calls.
|
||||||
|
|
||||||
## OpenAI Responses API
|
## OpenAI Responses API
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Public Contract
|
# Public Contract
|
||||||
|
|
||||||
This document defines the supported public interface for `pyro-mcp` `2.x`.
|
This document defines the stable public interface for `pyro-mcp` `3.x`.
|
||||||
|
|
||||||
## Package Identity
|
## Package Identity
|
||||||
|
|
||||||
|
|
@ -9,6 +9,11 @@ This document defines the supported public interface for `pyro-mcp` `2.x`.
|
||||||
- Public Python import: `from pyro_mcp import Pyro`
|
- Public Python import: `from pyro_mcp import Pyro`
|
||||||
- Public package-level factory: `from pyro_mcp import create_server`
|
- Public package-level factory: `from pyro_mcp import create_server`
|
||||||
|
|
||||||
|
Stable product framing:
|
||||||
|
|
||||||
|
- `pyro run` is the stable one-shot entrypoint.
|
||||||
|
- `pyro workspace ...` is the stable persistent workspace contract.
|
||||||
|
|
||||||
## CLI Contract
|
## CLI Contract
|
||||||
|
|
||||||
Top-level commands:
|
Top-level commands:
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
This roadmap turns the agent-workspace vision into release-sized milestones.
|
This roadmap turns the agent-workspace vision into release-sized milestones.
|
||||||
|
|
||||||
Current baseline is `2.10.0`:
|
Current baseline is `3.0.0`:
|
||||||
|
|
||||||
- workspace persistence exists and the public surface is now workspace-first
|
- workspace persistence exists and the public surface is now workspace-first
|
||||||
- host crossing currently covers create-time seeding, later sync push, and explicit export
|
- host crossing currently covers create-time seeding, later sync push, and explicit export
|
||||||
|
|
@ -37,16 +37,13 @@ also expected to update:
|
||||||
5. [`2.8.0` Named Snapshots And Reset](task-workspace-ga/2.8.0-named-snapshots-and-reset.md) - Done
|
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) - Done
|
6. [`2.9.0` Secrets](task-workspace-ga/2.9.0-secrets.md) - Done
|
||||||
7. [`2.10.0` Network Policy And Host Port Publication](task-workspace-ga/2.10.0-network-policy-and-host-port-publication.md) - Done
|
7. [`2.10.0` Network Policy And Host Port Publication](task-workspace-ga/2.10.0-network-policy-and-host-port-publication.md) - Done
|
||||||
8. [`3.0.0` Stable Workspace Product](task-workspace-ga/3.0.0-stable-workspace-product.md)
|
8. [`3.0.0` Stable Workspace Product](task-workspace-ga/3.0.0-stable-workspace-product.md) - Done
|
||||||
9. [`3.1.0` Secondary Disk Tools](task-workspace-ga/3.1.0-secondary-disk-tools.md)
|
9. [`3.1.0` Secondary Disk Tools](task-workspace-ga/3.1.0-secondary-disk-tools.md)
|
||||||
|
|
||||||
## Definition Of Done For The Roadmap
|
## Remaining Follow-Up
|
||||||
|
|
||||||
The workspace product is ready to leave beta when:
|
The core workspace product is now stable. The remaining planned follow-up is intentionally
|
||||||
|
secondary:
|
||||||
|
|
||||||
- the public contract is workspace-first rather than task-first
|
- `3.1.0` secondary disk tools for offline inspection and disk-level workflows
|
||||||
- an agent can inhabit a sandbox through shell, exec, service, diff, export,
|
- no further roadmap milestone changes the stable workspace-first core contract
|
||||||
snapshot, reset, and explicit host-crossing operations
|
|
||||||
- the main docs lead with the workspace product, not one-shot VM execution
|
|
||||||
- the remaining deliberate deferrals are secondary disk tools rather than core
|
|
||||||
workspace features
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
# `3.0.0` Stable Workspace Product
|
# `3.0.0` Stable Workspace Product
|
||||||
|
|
||||||
|
Status: Done
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
|
|
||||||
Freeze the workspace-first public contract and promote the product from a
|
Freeze the workspace-first public contract and promote the product from a
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
[project]
|
[project]
|
||||||
name = "pyro-mcp"
|
name = "pyro-mcp"
|
||||||
version = "2.10.0"
|
version = "3.0.0"
|
||||||
description = "Ephemeral Firecracker sandboxes with curated environments, persistent workspaces, and MCP tools."
|
description = "Stable Firecracker workspaces, one-shot sandboxes, and MCP tools for coding agents."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = { file = "LICENSE" }
|
license = { file = "LICENSE" }
|
||||||
authors = [
|
authors = [
|
||||||
|
|
|
||||||
|
|
@ -458,7 +458,7 @@ class _HelpFormatter(
|
||||||
def _build_parser() -> argparse.ArgumentParser:
|
def _build_parser() -> argparse.ArgumentParser:
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description=(
|
description=(
|
||||||
"Run ephemeral Firecracker microVM workflows from the CLI on supported "
|
"Run stable one-shot and persistent workspace workflows on supported "
|
||||||
"Linux x86_64 KVM hosts."
|
"Linux x86_64 KVM hosts."
|
||||||
),
|
),
|
||||||
epilog=dedent(
|
epilog=dedent(
|
||||||
|
|
@ -469,14 +469,17 @@ def _build_parser() -> argparse.ArgumentParser:
|
||||||
pyro env pull debian:12
|
pyro env pull debian:12
|
||||||
pyro run debian:12 -- git --version
|
pyro run debian:12 -- git --version
|
||||||
|
|
||||||
Need repeated commands in one workspace after that?
|
Continue into the stable workspace path after that:
|
||||||
pyro workspace create debian:12 --seed-path ./repo
|
pyro workspace create debian:12 --seed-path ./repo
|
||||||
pyro workspace sync push WORKSPACE_ID ./changes
|
pyro workspace sync push WORKSPACE_ID ./changes
|
||||||
|
pyro workspace exec WORKSPACE_ID -- cat note.txt
|
||||||
pyro workspace diff WORKSPACE_ID
|
pyro workspace diff WORKSPACE_ID
|
||||||
pyro workspace export WORKSPACE_ID note.txt --output ./note.txt
|
pyro workspace snapshot create WORKSPACE_ID checkpoint
|
||||||
|
pyro workspace reset WORKSPACE_ID --snapshot checkpoint
|
||||||
pyro workspace shell open WORKSPACE_ID
|
pyro workspace shell open WORKSPACE_ID
|
||||||
pyro workspace service start WORKSPACE_ID app --ready-file .ready -- \
|
pyro workspace service start WORKSPACE_ID app --ready-file .ready -- \
|
||||||
sh -lc 'touch .ready && while true; do sleep 60; done'
|
sh -lc 'touch .ready && while true; do sleep 60; done'
|
||||||
|
pyro workspace export WORKSPACE_ID note.txt --output ./note.txt
|
||||||
|
|
||||||
Use `pyro mcp serve` only after the CLI validation path works.
|
Use `pyro mcp serve` only after the CLI validation path works.
|
||||||
"""
|
"""
|
||||||
|
|
@ -675,8 +678,8 @@ def _build_parser() -> argparse.ArgumentParser:
|
||||||
"workspace",
|
"workspace",
|
||||||
help="Manage persistent workspaces.",
|
help="Manage persistent workspaces.",
|
||||||
description=(
|
description=(
|
||||||
"Create a persistent workspace when you need repeated commands in one "
|
"Use the stable workspace contract when you need one sandbox to stay alive "
|
||||||
"sandbox instead of one-shot `pyro run`."
|
"across repeated exec, shell, service, diff, export, snapshot, and reset calls."
|
||||||
),
|
),
|
||||||
epilog=dedent(
|
epilog=dedent(
|
||||||
"""
|
"""
|
||||||
|
|
@ -692,6 +695,9 @@ def _build_parser() -> argparse.ArgumentParser:
|
||||||
pyro workspace service start WORKSPACE_ID app --ready-file .ready -- \
|
pyro workspace service start WORKSPACE_ID app --ready-file .ready -- \
|
||||||
sh -lc 'touch .ready && while true; do sleep 60; done'
|
sh -lc 'touch .ready && while true; do sleep 60; done'
|
||||||
pyro workspace logs WORKSPACE_ID
|
pyro workspace logs WORKSPACE_ID
|
||||||
|
|
||||||
|
`pyro run` remains the fastest one-shot proof. `pyro workspace ...` is the
|
||||||
|
stable path when an agent needs to inhabit one sandbox over time.
|
||||||
"""
|
"""
|
||||||
),
|
),
|
||||||
formatter_class=_HelpFormatter,
|
formatter_class=_HelpFormatter,
|
||||||
|
|
@ -704,7 +710,10 @@ def _build_parser() -> argparse.ArgumentParser:
|
||||||
workspace_create_parser = workspace_subparsers.add_parser(
|
workspace_create_parser = workspace_subparsers.add_parser(
|
||||||
"create",
|
"create",
|
||||||
help="Create and start a persistent workspace.",
|
help="Create and start a persistent workspace.",
|
||||||
description="Create a persistent workspace that stays alive across repeated exec calls.",
|
description=(
|
||||||
|
"Create and start a stable persistent workspace that stays alive across repeated "
|
||||||
|
"exec, shell, service, diff, export, snapshot, and reset calls."
|
||||||
|
),
|
||||||
epilog=dedent(
|
epilog=dedent(
|
||||||
"""
|
"""
|
||||||
Examples:
|
Examples:
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ from typing import Any
|
||||||
from pyro_mcp.runtime import DEFAULT_PLATFORM, RuntimePaths
|
from pyro_mcp.runtime import DEFAULT_PLATFORM, RuntimePaths
|
||||||
|
|
||||||
DEFAULT_ENVIRONMENT_VERSION = "1.0.0"
|
DEFAULT_ENVIRONMENT_VERSION = "1.0.0"
|
||||||
DEFAULT_CATALOG_VERSION = "2.10.0"
|
DEFAULT_CATALOG_VERSION = "3.0.0"
|
||||||
OCI_MANIFEST_ACCEPT = ", ".join(
|
OCI_MANIFEST_ACCEPT = ", ".join(
|
||||||
(
|
(
|
||||||
"application/vnd.oci.image.index.v1+json",
|
"application/vnd.oci.image.index.v1+json",
|
||||||
|
|
@ -48,7 +48,7 @@ class VmEnvironment:
|
||||||
oci_repository: str | None = None
|
oci_repository: str | None = None
|
||||||
oci_reference: str | None = None
|
oci_reference: str | None = None
|
||||||
source_digest: str | None = None
|
source_digest: str | None = None
|
||||||
compatibility: str = ">=2.0.0,<3.0.0"
|
compatibility: str = ">=3.0.0,<4.0.0"
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,10 @@ def test_cli_help_guides_first_run() -> None:
|
||||||
assert "pyro env list" in help_text
|
assert "pyro env list" in help_text
|
||||||
assert "pyro env pull debian:12" in help_text
|
assert "pyro env pull debian:12" in help_text
|
||||||
assert "pyro run debian:12 -- git --version" in help_text
|
assert "pyro run debian:12 -- git --version" in help_text
|
||||||
|
assert "Continue into the stable workspace path after that:" in help_text
|
||||||
|
assert "pyro workspace exec WORKSPACE_ID -- cat note.txt" in help_text
|
||||||
|
assert "pyro workspace snapshot create WORKSPACE_ID checkpoint" in help_text
|
||||||
|
assert "pyro workspace reset WORKSPACE_ID --snapshot checkpoint" in help_text
|
||||||
assert "pyro workspace sync push WORKSPACE_ID ./changes" in help_text
|
assert "pyro workspace sync push WORKSPACE_ID ./changes" in help_text
|
||||||
assert "Use `pyro mcp serve` only after the CLI validation path works." in help_text
|
assert "Use `pyro mcp serve` only after the CLI validation path works." in help_text
|
||||||
|
|
||||||
|
|
@ -62,6 +66,7 @@ def test_cli_subcommand_help_includes_examples_and_guidance() -> None:
|
||||||
assert "Use this from an MCP client config after the CLI evaluation path works." in mcp_help
|
assert "Use this from an MCP client config after the CLI evaluation path works." in mcp_help
|
||||||
|
|
||||||
workspace_help = _subparser_choice(parser, "workspace").format_help()
|
workspace_help = _subparser_choice(parser, "workspace").format_help()
|
||||||
|
assert "stable workspace contract" in workspace_help
|
||||||
assert "pyro workspace create debian:12 --seed-path ./repo" in workspace_help
|
assert "pyro workspace create debian:12 --seed-path ./repo" in workspace_help
|
||||||
assert "pyro workspace sync push WORKSPACE_ID ./repo --dest src" in workspace_help
|
assert "pyro workspace sync push WORKSPACE_ID ./repo --dest src" in workspace_help
|
||||||
assert "pyro workspace exec WORKSPACE_ID" in workspace_help
|
assert "pyro workspace exec WORKSPACE_ID" in workspace_help
|
||||||
|
|
@ -2569,7 +2574,7 @@ def test_cli_workspace_exec_json_error_exits_nonzero(
|
||||||
def test_print_env_helpers_render_human_output(capsys: pytest.CaptureFixture[str]) -> None:
|
def test_print_env_helpers_render_human_output(capsys: pytest.CaptureFixture[str]) -> None:
|
||||||
cli._print_env_list_human(
|
cli._print_env_list_human(
|
||||||
{
|
{
|
||||||
"catalog_version": "2.0.0",
|
"catalog_version": "3.0.0",
|
||||||
"environments": [
|
"environments": [
|
||||||
{"name": "debian:12", "installed": True, "description": "Git environment"},
|
{"name": "debian:12", "installed": True, "description": "Git environment"},
|
||||||
"ignored",
|
"ignored",
|
||||||
|
|
@ -2615,7 +2620,7 @@ def test_print_env_helpers_render_human_output(capsys: pytest.CaptureFixture[str
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
captured = capsys.readouterr().out
|
captured = capsys.readouterr().out
|
||||||
assert "Catalog version: 2.0.0" in captured
|
assert "Catalog version: 3.0.0" in captured
|
||||||
assert "debian:12 [installed] Git environment" in captured
|
assert "debian:12 [installed] Git environment" in captured
|
||||||
assert "Install manifest: /cache/linux-x86_64/debian_12-1.0.0/environment.json" in captured
|
assert "Install manifest: /cache/linux-x86_64/debian_12-1.0.0/environment.json" in captured
|
||||||
assert "Deleted 2 cached environment entries." in captured
|
assert "Deleted 2 cached environment entries." in captured
|
||||||
|
|
@ -2624,7 +2629,7 @@ def test_print_env_helpers_render_human_output(capsys: pytest.CaptureFixture[str
|
||||||
|
|
||||||
|
|
||||||
def test_print_env_list_human_handles_empty(capsys: pytest.CaptureFixture[str]) -> None:
|
def test_print_env_list_human_handles_empty(capsys: pytest.CaptureFixture[str]) -> None:
|
||||||
cli._print_env_list_human({"catalog_version": "2.0.0", "environments": []})
|
cli._print_env_list_human({"catalog_version": "3.0.0", "environments": []})
|
||||||
output = capsys.readouterr().out
|
output = capsys.readouterr().out
|
||||||
assert "No environments found." in output
|
assert "No environments found." in output
|
||||||
|
|
||||||
|
|
|
||||||
2
uv.lock
generated
2
uv.lock
generated
|
|
@ -706,7 +706,7 @@ crypto = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyro-mcp"
|
name = "pyro-mcp"
|
||||||
version = "2.10.0"
|
version = "3.0.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "mcp" },
|
{ name = "mcp" },
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue