pyro-mcp/docs/first-run.md
Thales Maciel f2d20ef30a 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.
2026-03-12 18:59:09 -03:00

12 KiB

First Run Transcript

This is the intended evaluator path for a first successful run on a supported host. Copy the commands as-is. Paths and timing values will differ on your machine. The same sequence works with an installed pyro binary by dropping the uvx --from pyro-mcp prefix. If you are running from a source checkout instead of the published package, replace pyro with uv run pyro.

1. Verify the host

$ uvx --from pyro-mcp pyro doctor
Platform: linux-x86_64
Runtime: PASS
KVM: exists=yes readable=yes writable=yes
Environment cache: /home/you/.cache/pyro-mcp/environments
Capabilities: vm_boot=yes guest_exec=yes guest_network=yes
Networking: tun=yes ip_forward=yes

2. Inspect the catalog

$ uvx --from pyro-mcp pyro env list
Catalog version: 3.0.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.

3. Pull the default environment

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. See host-requirements.md for the full host requirements.

$ uvx --from pyro-mcp pyro env pull debian:12
[pull] phase=install environment=debian:12
[pull] phase=ready environment=debian:12
Pulled: debian:12
Version: 1.0.0
Distribution: debian 12
Installed: yes
Cache dir: /home/you/.cache/pyro-mcp/environments
Default packages: bash, coreutils, git
Install dir: /home/you/.cache/pyro-mcp/environments/linux-x86_64/debian_12-1.0.0
Install manifest: /home/you/.cache/pyro-mcp/environments/linux-x86_64/debian_12-1.0.0/environment.json
Kernel image: /home/you/.cache/pyro-mcp/environments/linux-x86_64/debian_12-1.0.0/vmlinux
Rootfs image: /home/you/.cache/pyro-mcp/environments/linux-x86_64/debian_12-1.0.0/rootfs.ext4
OCI source: registry-1.docker.io/thalesmaciel/pyro-environment-debian-12:1.0.0

4. Run one command in a guest

$ uvx --from pyro-mcp pyro run debian:12 -- git --version
[run] phase=create environment=debian:12
[run] phase=start vm_id=...
[run] phase=execute vm_id=...
[run] environment=debian:12 execution_mode=guest_vsock exit_code=0 duration_ms=...
git version ...

The guest command output and the [run] ... summary are written to different streams, so they may appear in either order in terminals or capture tools. Use --json if you need a deterministic structured result.

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.

$ 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

$ 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 create debian:12 --network-policy egress
$ uvx --from pyro-mcp pyro workspace create debian:12 --secret API_TOKEN=expected --secret-file PIP_TOKEN=./token.txt
$ uvx --from pyro-mcp pyro workspace exec WORKSPACE_ID --secret-env API_TOKEN -- sh -lc 'test "$API_TOKEN" = "expected"'
$ 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 --secret-env API_TOKEN
$ uvx --from pyro-mcp pyro workspace service start WORKSPACE_ID app --secret-env API_TOKEN --ready-file .ready -- sh -lc 'touch .ready && while true; do sleep 60; done'
$ uvx --from pyro-mcp pyro workspace create debian:12 --network-policy egress+published-ports
$ uvx --from pyro-mcp pyro workspace service start WORKSPACE_ID app --ready-http http://127.0.0.1:8080/ --publish 18080:8080 -- ./start-app
$ uvx --from pyro-mcp pyro mcp serve

pyro demo proves the one-shot create/start/exec/delete VM lifecycle works end to end.

When you need repeated commands in one sandbox, switch to pyro workspace ...:

$ uvx --from pyro-mcp pyro workspace create debian:12 --seed-path ./repo
Workspace ID: ...
Environment: debian:12
State: started
Workspace: /workspace
Workspace seed: directory from ...
Network policy: off
Execution mode: guest_vsock
Resources: 1 vCPU / 1024 MiB
Command count: 0

$ uvx --from pyro-mcp pyro workspace sync push WORKSPACE_ID ./changes --dest src
[workspace-sync] workspace_id=... mode=directory source=... destination=/workspace/src entry_count=... bytes_written=... execution_mode=guest_vsock

$ uvx --from pyro-mcp pyro workspace exec WORKSPACE_ID -- cat src/note.txt
hello from synced workspace
[workspace-exec] workspace_id=... sequence=1 cwd=/workspace execution_mode=guest_vsock exit_code=0 duration_ms=...

$ uvx --from pyro-mcp pyro workspace exec WORKSPACE_ID --secret-env API_TOKEN -- sh -lc 'test "$API_TOKEN" = "expected"'
[workspace-exec] workspace_id=... sequence=2 cwd=/workspace execution_mode=guest_vsock exit_code=0 duration_ms=...

$ uvx --from pyro-mcp pyro workspace diff WORKSPACE_ID
[workspace-diff] workspace_id=... total=... added=... modified=... deleted=... type_changed=... text_patched=... non_text=...
--- a/src/note.txt
+++ 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

$ uvx --from pyro-mcp pyro workspace shell open WORKSPACE_ID --secret-env API_TOKEN
[workspace-shell-open] workspace_id=... shell_id=... state=running cwd=/workspace cols=120 rows=30 execution_mode=guest_vsock

$ uvx --from pyro-mcp pyro workspace shell write WORKSPACE_ID SHELL_ID --input 'pwd'
[workspace-shell-write] workspace_id=... shell_id=... state=running cwd=/workspace cols=120 rows=30 execution_mode=guest_vsock

$ uvx --from pyro-mcp pyro workspace shell read WORKSPACE_ID SHELL_ID
/workspace
[workspace-shell-read] workspace_id=... shell_id=... state=running cursor=0 next_cursor=... truncated=False execution_mode=guest_vsock

$ uvx --from pyro-mcp pyro workspace service start WORKSPACE_ID web --secret-env API_TOKEN --ready-file .web-ready -- sh -lc 'touch .web-ready && while true; do sleep 60; done'
[workspace-service-start] workspace_id=... service=web state=running cwd=/workspace ready_type=file execution_mode=guest_vsock

$ uvx --from pyro-mcp pyro workspace service start WORKSPACE_ID worker --ready-file .worker-ready -- sh -lc 'touch .worker-ready && while true; do sleep 60; done'
[workspace-service-start] workspace_id=... service=worker state=running cwd=/workspace ready_type=file execution_mode=guest_vsock

$ uvx --from pyro-mcp pyro workspace create debian:12 --network-policy egress+published-ports
Workspace ID: ...
Network policy: egress+published-ports
...

$ uvx --from pyro-mcp pyro workspace service start WORKSPACE_ID app --ready-http http://127.0.0.1:8080/ --publish 18080:8080 -- ./start-app
[workspace-service-start] workspace_id=... service=app state=running cwd=/workspace ready_type=http execution_mode=guest_vsock published=127.0.0.1:18080->8080/tcp

$ uvx --from pyro-mcp pyro workspace service list WORKSPACE_ID
Workspace: ...
Services: 2 total, 2 running
- web [running] cwd=/workspace readiness=file
- worker [running] cwd=/workspace readiness=file

$ uvx --from pyro-mcp pyro workspace service status WORKSPACE_ID web
Workspace: ...
Service: web
State: running
Command: sh -lc 'touch .web-ready && while true; do sleep 60; done'
Cwd: /workspace
Readiness: file /workspace/.web-ready
Execution mode: guest_vsock

$ uvx --from pyro-mcp pyro workspace service logs WORKSPACE_ID web --tail-lines 50
Workspace: ...
Service: web
State: running
...

$ uvx --from pyro-mcp pyro workspace service stop WORKSPACE_ID web
[workspace-service-stop] workspace_id=... service=web state=stopped execution_mode=guest_vsock

$ uvx --from pyro-mcp pyro workspace service stop WORKSPACE_ID worker
[workspace-service-stop] workspace_id=... service=worker state=stopped execution_mode=guest_vsock

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 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 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 workspace diff or export results. Use --network-policy egress for outbound guest networking, and --network-policy egress+published-ports plus workspace service start --publish when one service must be reachable from the host on 127.0.0.1. Use --secret and --secret-file at workspace creation when the sandbox needs private tokens or config. Persisted secret files are materialized at /run/pyro-secrets/<name>, and --secret-env SECRET_NAME[=ENV_VAR] maps one secret into one exec, shell, or service call without storing that environment mapping on the workspace itself.

The stable workspace walkthrough GIF in the README is rendered from docs/assets/workspace-first-run.tape with scripts/render_tape.sh.

Example output:

{
  "cleanup": {
    "deleted": true,
    "reason": "post_exec_cleanup",
    "vm_id": "..."
  },
  "command": "git --version",
  "environment": "debian:12",
  "execution_mode": "guest_vsock",
  "exit_code": 0,
  "stdout": "git version ...\n"
}

When you are done evaluating and want to remove stale cached environments, run pyro env prune.

If pyro doctor reports Runtime: FAIL, or if the pyro run summary does not show execution_mode=guest_vsock, stop and use troubleshooting.md.