# Install ## Support Matrix Supported today: - Linux x86_64 - Python 3.12+ - `uv` - `/dev/kvm` Optional for outbound guest networking: - `ip` - `nft` or `iptables` - privilege to create TAP devices and configure NAT Not supported today: - macOS - Windows - Linux hosts without working KVM at `/dev/kvm` If you do not already have `uv`, install it first: ```bash python -m pip install uv ``` Use these command forms consistently: - published package without install: `uvx --from pyro-mcp pyro ...` - installed package: `pyro ...` - source checkout: `uv run pyro ...` ## Fastest Evaluation Path Use either of these equivalent evaluator paths: ```bash # Package without install uvx --from pyro-mcp pyro doctor uvx --from pyro-mcp pyro env list uvx --from pyro-mcp pyro env pull debian:12 uvx --from pyro-mcp pyro run debian:12 -- git --version ``` ```bash # Already installed pyro doctor pyro env list pyro env pull debian:12 pyro run debian:12 -- git --version ``` 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 ```bash uvx --from pyro-mcp pyro doctor ``` Expected success signals: ```bash 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 ``` If `Runtime: FAIL`, stop here and use [troubleshooting.md](troubleshooting.md). ### 2. Inspect the catalog ```bash uvx --from pyro-mcp pyro env list ``` Expected output: ```bash Catalog version: 3.5.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 ```bash uvx --from pyro-mcp pyro env pull debian:12 ``` 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. Expected success signals: ```bash [pull] phase=install environment=debian:12 [pull] phase=ready environment=debian:12 Pulled: debian:12 ... ``` ### 4. Run one command in a guest ```bash uvx --from pyro-mcp pyro run debian:12 -- git --version ``` Expected success signals: ```bash [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. If guest execution is unavailable, the command fails unless you explicitly pass `--allow-host-compat`. ## 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 --name repro-fix --label issue=123 --json | python -c 'import json,sys; print(json.load(sys.stdin)["workspace_id"])')" pyro workspace list pyro workspace update "$WORKSPACE_ID" --label owner=codex pyro workspace sync push "$WORKSPACE_ID" ./changes pyro workspace file read "$WORKSPACE_ID" note.txt pyro workspace patch apply "$WORKSPACE_ID" --patch "$(cat fix.patch)" 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 create --name/--label`, `workspace list`, and `workspace update` make workspaces discoverable - `workspace sync push` imports later host-side changes - `workspace file *` and `workspace patch apply` cover model-native text inspection and edits - `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 - `workspace stop|start` and `workspace disk *` add secondary stopped-workspace inspection and raw ext4 export ## 6. Optional demo proof point ```bash uvx --from pyro-mcp pyro demo ``` `pyro demo` proves the one-shot create/start/exec/delete VM lifecycle works end to end. 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" } ``` For a fuller copy-pasteable transcript, see [first-run.md](first-run.md). When you are done evaluating and want to remove stale cached environments, run `pyro env prune`. ## Installed CLI If you already installed the package, the same evaluator path works with plain `pyro ...`: ```bash uv tool install pyro-mcp pyro --version pyro doctor pyro env list pyro env pull debian:12 pyro run debian:12 -- git --version ``` After the CLI path works, you can move on to: - persistent workspaces: `pyro workspace create debian:12 --seed-path ./repo` - workspace discovery metadata: `pyro workspace create debian:12 --name repro-fix --label issue=123` - workspace discovery commands: `pyro workspace list` and `pyro workspace update WORKSPACE_ID --label owner=codex` - live workspace updates: `pyro workspace sync push WORKSPACE_ID ./changes` - guest networking policy: `pyro workspace create debian:12 --network-policy egress` - workspace secrets: `pyro workspace create debian:12 --secret API_TOKEN=expected --secret-file PIP_TOKEN=./token.txt` - model-native file editing: `pyro workspace file read WORKSPACE_ID src/app.py`, `pyro workspace file write WORKSPACE_ID src/app.py --text 'print("hi")'`, and `pyro workspace patch apply WORKSPACE_ID --patch "$(cat fix.patch)"` - 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` - stopped-workspace inspection: `pyro workspace stop WORKSPACE_ID`, `pyro workspace disk list WORKSPACE_ID`, `pyro workspace disk read WORKSPACE_ID note.txt`, and `pyro workspace disk export WORKSPACE_ID --output ./workspace.ext4` - 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'` - localhost-published ports: `pyro workspace create debian:12 --network-policy egress+published-ports` and `pyro workspace service start WORKSPACE_ID app --ready-http http://127.0.0.1:8080/ --publish 18080:8080 -- ./start-app` - MCP: `pyro mcp serve --profile workspace-core` - Python SDK: `from pyro_mcp import Pyro` - Demos: `pyro demo` or `pyro demo --network` ## Stable Workspace Use `pyro workspace ...` when you need repeated commands in one sandbox instead of one-shot `pyro run`. ```bash pyro workspace create debian:12 --seed-path ./repo pyro workspace create debian:12 --network-policy egress pyro workspace create debian:12 --seed-path ./repo --secret API_TOKEN=expected pyro workspace create debian:12 --network-policy egress+published-ports pyro workspace sync push WORKSPACE_ID ./changes --dest src pyro workspace file list WORKSPACE_ID src --recursive pyro workspace file read WORKSPACE_ID src/note.txt pyro workspace file write WORKSPACE_ID src/app.py --text 'print("hi")' pyro workspace patch apply WORKSPACE_ID --patch "$(cat fix.patch)" pyro workspace exec WORKSPACE_ID -- cat src/note.txt pyro workspace exec WORKSPACE_ID --secret-env API_TOKEN -- sh -lc 'test "$API_TOKEN" = "expected"' 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 --secret-env API_TOKEN pyro workspace shell write WORKSPACE_ID SHELL_ID --input 'pwd' pyro workspace shell read WORKSPACE_ID SHELL_ID --plain --wait-for-idle-ms 300 pyro workspace shell close WORKSPACE_ID SHELL_ID 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' pyro workspace service start WORKSPACE_ID worker --ready-file .worker-ready -- sh -lc 'touch .worker-ready && while true; do sleep 60; done' pyro workspace service start WORKSPACE_ID app --ready-http http://127.0.0.1:8080/ --publish 18080:8080 -- ./start-app pyro workspace service list WORKSPACE_ID pyro workspace service status WORKSPACE_ID web pyro workspace service logs WORKSPACE_ID web --tail-lines 50 pyro workspace service stop WORKSPACE_ID web pyro workspace service stop WORKSPACE_ID worker pyro workspace stop WORKSPACE_ID pyro workspace disk list WORKSPACE_ID pyro workspace disk read WORKSPACE_ID src/note.txt pyro workspace disk export WORKSPACE_ID --output ./workspace.ext4 pyro workspace start WORKSPACE_ID pyro workspace logs WORKSPACE_ID pyro workspace delete WORKSPACE_ID ``` Workspace commands default to the persistent `/workspace` directory inside the guest. If you need 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 `3.5.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. Prefer `pyro workspace shell read --plain --wait-for-idle-ms 300` for chat-facing shell loops. Use `pyro workspace service *` when the workspace needs long-running background processes with typed readiness probes. Service metadata and logs stay outside `/workspace`, so the service runtime itself does not show up in workspace diff or export results. Use `--network-policy egress` when the workspace needs 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, and `--secret-env SECRET_NAME[=ENV_VAR]` when one exec, shell, or service call needs that secret as an environment variable. Persisted secret files are available in the guest at `/run/pyro-secrets/`. 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 it. ## Contributor Clone ```bash git lfs install git clone cd pyro git lfs pull make setup ```