# 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.11.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 --id-only)"
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 --content-only
pyro workspace patch apply "$WORKSPACE_ID" --patch-file 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

When that stable workspace path is working, continue with the recipe index at
[use-cases/README.md](use-cases/README.md). It groups the five core workspace stories and the
real smoke targets behind them, starting with `make smoke-use-cases` or one of the per-scenario
targets such as `make smoke-repro-fix-loop`.
Treat `make smoke-use-cases` as the trustworthy guest-backed verification path for the advertised
workspace workflows.

## 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 --content-only`, `pyro workspace file write WORKSPACE_ID src/app.py --text-file ./app.py`, and `pyro workspace patch apply WORKSPACE_ID --patch-file 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 --content-only`, and `pyro workspace disk export WORKSPACE_ID --output ./workspace.ext4`
- interactive shells: `pyro workspace shell open WORKSPACE_ID --id-only`
- 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`

## Chat Host Quickstart

For most chat-host integrations, start with `workspace-core`:

```bash
uvx --from pyro-mcp pyro mcp serve --profile workspace-core
```

Copy-paste host-specific starts:

- Claude Code: [examples/claude_code_mcp.md](../examples/claude_code_mcp.md)
- Codex: [examples/codex_mcp.md](../examples/codex_mcp.md)
- OpenCode: [examples/opencode_mcp_config.json](../examples/opencode_mcp_config.json)
- Generic MCP config: [examples/mcp_client_config.md](../examples/mcp_client_config.md)

Claude Code:

```bash
claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve --profile workspace-core
```

Codex:

```bash
codex mcp add pyro -- uvx --from pyro-mcp pyro mcp serve --profile workspace-core
```

OpenCode uses the `mcp`/`type: "local"` config shape shown in
[examples/opencode_mcp_config.json](../examples/opencode_mcp_config.json). If
`pyro-mcp` is already installed, replace the `uvx --from pyro-mcp pyro`
command with `pyro` in the same host-specific command or config shape.

Use profile progression like this:

- `workspace-core`: recommended first profile for normal persistent chat editing
- `vm-run`: one-shot-only integrations
- `workspace-full`: advanced 3.x compatibility surface when the host truly needs shells, services, snapshots, secrets, network policy, or disk tools

## 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 --content-only
pyro workspace file write WORKSPACE_ID src/app.py --text-file ./app.py
pyro workspace patch apply WORKSPACE_ID --patch-file 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 --id-only
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 --content-only
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 `--id-only` for only the identifier or `--json` for the full
workspace payload. 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.11.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/<name>`. 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 <repo>
cd pyro
git lfs pull
make setup
```
