# 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 ```bash $ 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 ```bash $ uvx --from pyro-mcp pyro env list Catalog version: 3.2.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](host-requirements.md) for the full host requirements. ```bash $ 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 ```bash $ 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. ```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 file read "$WORKSPACE_ID" note.txt $ uvx --from pyro-mcp pyro workspace patch apply "$WORKSPACE_ID" --patch "$(cat fix.patch)" $ 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 stop "$WORKSPACE_ID" $ uvx --from pyro-mcp pyro workspace disk list "$WORKSPACE_ID" $ uvx --from pyro-mcp pyro workspace disk read "$WORKSPACE_ID" note.txt $ uvx --from pyro-mcp pyro workspace disk export "$WORKSPACE_ID" --output ./workspace.ext4 $ uvx --from pyro-mcp pyro workspace start "$WORKSPACE_ID" $ uvx --from pyro-mcp pyro workspace delete "$WORKSPACE_ID" ``` ## 6. Optional one-shot demo and expanded workspace flow ```bash $ 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 file list WORKSPACE_ID src --recursive $ uvx --from pyro-mcp pyro workspace file read WORKSPACE_ID src/app.py $ uvx --from pyro-mcp pyro workspace file write WORKSPACE_ID src/app.py --text 'print("hi")' $ uvx --from pyro-mcp pyro workspace patch apply WORKSPACE_ID --patch "$(cat fix.patch)" $ 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 ...`: ```bash $ 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 file list WORKSPACE_ID src --recursive Workspace file path: /workspace/src - /workspace/src/note.txt [file] bytes=... $ uvx --from pyro-mcp pyro workspace file read WORKSPACE_ID src/note.txt hello from synced workspace [workspace-file-read] workspace_id=... path=/workspace/src/note.txt size_bytes=... truncated=False execution_mode=guest_vsock $ uvx --from pyro-mcp pyro workspace patch apply WORKSPACE_ID --patch "$(cat fix.patch)" [workspace-patch] workspace_id=... total=... added=... modified=... deleted=... 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 $ uvx --from pyro-mcp pyro workspace stop WORKSPACE_ID Workspace ID: ... State: stopped $ uvx --from pyro-mcp pyro workspace disk list WORKSPACE_ID src --recursive Workspace: ... Path: /workspace/src - /workspace/src [directory] - /workspace/src/note.txt [file] bytes=... $ uvx --from pyro-mcp pyro workspace disk read WORKSPACE_ID src/note.txt hello from synced workspace [workspace-disk-read] workspace_id=... path=/workspace/src/note.txt size_bytes=... truncated=False $ uvx --from pyro-mcp pyro workspace disk export WORKSPACE_ID --output ./workspace.ext4 [workspace-disk-export] workspace_id=... output_path=... disk_format=ext4 bytes_written=... $ uvx --from pyro-mcp pyro workspace start WORKSPACE_ID Workspace ID: ... State: started ``` 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.2.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 file *` and `pyro workspace patch apply` for model-native text edits, `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/`, 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. Use `pyro workspace stop` plus `pyro workspace disk list|read|export` when you need offline inspection or one raw ext4 copy from a stopped guest-backed workspace, then `pyro workspace start` to resume the same workspace. 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: ```json { "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](troubleshooting.md).