Reframe pyro around the chat-host path

Make the docs and help text unapologetically teach  as the product path for Claude Code, Codex, and OpenCode on Linux KVM.

Rewrite the README, install/first-run/integration guides, public contract, vision, and use-case docs around the zero-to-hero chat flow, and explicitly note that there are no users yet so breaking changes are acceptable while the interface is still being shaped.

Update package metadata, CLI help, and the docs/help expectation tests to match the new positioning. Validate the reframe with usage: pyro [-h] [--version] COMMAND ...

Validate the host and serve disposable MCP workspaces for chat-based coding agents on supported Linux x86_64 KVM hosts.

positional arguments:
  COMMAND
    env        Inspect and manage curated environments.
    mcp        Run the MCP server.
    run        Run one command inside an ephemeral VM.
    workspace  Manage persistent workspaces.
    doctor     Inspect runtime and host diagnostics.
    demo       Run built-in demos.

options:
  -h, --help   show this help message and exit
  --version    show program's version number and exit

Suggested zero-to-hero path:
  pyro doctor
  pyro env list
  pyro env pull debian:12
  pyro run debian:12 -- git --version
  pyro mcp serve

Connect a chat host after that:
  claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve
  codex mcp add pyro -- uvx --from pyro-mcp pyro mcp serve

If you want terminal-level visibility into the workspace model:
  pyro workspace create debian:12 --seed-path ./repo --id-only
  pyro workspace sync push WORKSPACE_ID ./changes
  pyro workspace exec WORKSPACE_ID -- cat note.txt
  pyro workspace diff WORKSPACE_ID
  pyro workspace snapshot create WORKSPACE_ID checkpoint
  pyro workspace reset WORKSPACE_ID --snapshot checkpoint
  pyro workspace shell open WORKSPACE_ID --id-only
  pyro workspace service start WORKSPACE_ID app --ready-file .ready --                 sh -lc 'touch .ready && while true; do sleep 60; done'
  pyro workspace export WORKSPACE_ID note.txt --output ./note.txt, usage: pyro mcp serve [-h] [--profile {vm-run,workspace-core,workspace-full}]

Expose pyro tools over stdio for an MCP client. Bare `pyro mcp serve` now starts `workspace-core`, the recommended first profile for most chat hosts.

options:
  -h, --help            show this help message and exit
  --profile {vm-run,workspace-core,workspace-full}
                        Expose only one model-facing tool profile. `workspace-
                        core` is the default and recommended first profile for
                        most chat hosts; `workspace-full` is the larger opt-in
                        profile. (default: workspace-core)

Default and recommended first start:
  pyro mcp serve

Profiles:
  workspace-core: default for normal persistent chat editing
  vm-run: smallest one-shot-only surface
  workspace-full: larger opt-in surface for shells, services,
    snapshots, secrets, network policy, and disk tools

Use --profile workspace-full only when the host truly needs those
extra workspace capabilities., and uv run ruff check .
All checks passed!
uv run mypy
Success: no issues found in 61 source files
uv run pytest -n auto
============================= test session starts ==============================
platform linux -- Python 3.12.10, pytest-9.0.2, pluggy-1.6.0
rootdir: /home/thales/projects/personal/pyro
configfile: pyproject.toml
testpaths: tests
plugins: anyio-4.12.1, xdist-3.8.0, cov-7.0.0
created: 32/32 workers
32 workers [393 items]

........................................................................ [ 18%]
........................................................................ [ 36%]
........................................................................ [ 54%]
........................................................................ [ 73%]
........................................................................ [ 91%]
.................................                                        [100%]
=============================== warnings summary ===============================
../../../.local/share/uv/python/cpython-3.12.10-linux-x86_64-gnu/lib/python3.12/importlib/metadata/__init__.py:467: 32 warnings
  /home/thales/.local/share/uv/python/cpython-3.12.10-linux-x86_64-gnu/lib/python3.12/importlib/metadata/__init__.py:467: DeprecationWarning: Implicit None on return values is deprecated and will raise KeyErrors.
    return self.metadata['Version']

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.12.10-final-0 _______________

Name                                        Stmts   Miss  Cover   Missing
-------------------------------------------------------------------------
src/pyro_mcp/__init__.py                       25      0   100%
src/pyro_mcp/api.py                           307      7    98%   37-38, 63, 69, 72, 75, 548
src/pyro_mcp/cli.py                          1132    141    88%   288-289, 332-333, 336, 344, 367-368, 394-395, 398, 406, 450, 460-461, 464, 477, 483-484, 498-499, 502, 566-575, 592-593, 596, 635, 2180, 2182, 2226, 2236, 2280, 2284-2285, 2295, 2302, 2344-2351, 2392, 2409-2414, 2459-2461, 2470-2472, 2483-2485, 2494-2496, 2503-2505, 2510-2512, 2523-2528, 2530, 2541-2546, 2567-2572, 2574, 2589-2594, 2596, 2608, 2623, 2637, 2655-2660, 2669-2674, 2676, 2683-2688, 2690, 2701-2706, 2708, 2719-2724, 2726, 2737-2742, 2764, 2787, 2806, 2824, 2841, 2899, 3017
src/pyro_mcp/contract.py                       52      0   100%
src/pyro_mcp/demo.py                           16      0   100%
src/pyro_mcp/doctor.py                         12      0   100%
src/pyro_mcp/ollama_demo.py                   245      6    98%   289, 294, 299, 318, 439, 550
src/pyro_mcp/runtime.py                       142     14    90%   80, 84, 88, 92, 120, 130, 144, 173, 182, 194, 230-232, 262
src/pyro_mcp/runtime_boot_check.py             33      0   100%
src/pyro_mcp/runtime_build.py                 546     47    91%   92, 127, 181, 189, 238-240, 263-265, 300, 325, 331, 340-341, 343, 392, 396, 413, 416, 492-494, 497-499, 522, 525, 578, 615, 620, 646-647, 649, 686, 688, 694, 697, 725, 765, 779, 791, 805, 808, 1002, 1009, 1198
src/pyro_mcp/runtime_bundle/__init__.py         0      0   100%
src/pyro_mcp/runtime_network_check.py          15      0   100%
src/pyro_mcp/server.py                          8      0   100%
src/pyro_mcp/vm_environments.py               386     55    86%   128, 131, 267, 274, 281, 304-306, 329-331, 352-353, 355, 380, 382, 392-394, 415, 418, 421, 429, 431, 436-437, 446-448, 488, 495-496, 502, 515, 526, 539, 546, 549, 570, 596, 599, 608-609, 613, 617, 626, 629, 636, 644, 647, 659, 667, 676, 682, 685
src/pyro_mcp/vm_firecracker.py                 47      0   100%
src/pyro_mcp/vm_guest.py                      206     22    89%   139, 142, 173, 176, 202, 205, 208, 211, 217, 239, 262-279, 291, 313, 633-634, 643
src/pyro_mcp/vm_manager.py                   2846    355    88%   625, 642, 650-657, 677, 684, 688, 712-715, 795-796, 818, 828, 830, 845, 853-855, 858, 870, 872, 881, 889, 892, 901-902, 910, 913, 919, 926, 929, 933, 951-955, 1010-1011, 1050, 1096, 1102, 1114, 1150, 1156, 1159, 1168, 1170, 1173-1177, 1230, 1236, 1239, 1248, 1250, 1253-1257, 1268, 1277, 1280, 1284-1290, 1319, 1322-1324, 1326, 1333, 1335, 1345, 1347, 1349, 1352-1353, 1361, 1377, 1379, 1381, 1391, 1403-1404, 1408, 1424, 1440-1441, 1443, 1447, 1450-1451, 1469, 1476, 1488, 1505, 1508-1509, 1511, 1582-1583, 1586-1588, 1599, 1602, 1605, 1617, 1638, 1649-1650, 1657-1658, 1669-1671, 1781, 1792-1798, 1808, 1860, 1870, 1891, 1894-1895, 1901-1904, 1910, 1922-1962, 1991-1993, 2034, 2046-2047, 2077, 2146, 2175, 2524-2528, 2598-2602, 2614, 2720, 3563, 3577, 3580, 3583, 3648-3653, 3720, 3802, 3842-3843, 3846-3847, 3862-3863, 3914, 4194, 4229, 4232, 4237, 4250, 4254, 4263, 4277, 4316, 4349, 4444, 4472-4473, 4477-4478, 4504, 4530-4531, 4576, 4578, 4600-4601, 4629, 4631, 4661-4662, 4681-4682, 4734, 4738, 4741-4743, 4745, 4747, 4776-4777, 4809-4845, 4863-4864, 4903, 4905, 4934, 4954-4955, 4977, 4988-4990, 5036, 5049-5050, 5059-5061, 5104-5105, 5171-5178, 5189-5192, 5203, 5208, 5216-5230, 5240, 5473-5476, 5485-5490, 5498-5503, 5513, 5557, 5577, 5601-5602, 5678-5680, 5706-5725, 5784, 5789, 5804, 5832, 5836, 5848, 5884-5886, 5946, 5950, 6079, 6111, 6155, 6170, 6189, 6201, 6242, 6251, 6256, 6269, 6274, 6296, 6394, 6422-6423
src/pyro_mcp/vm_network.py                    134     22    84%   65-66, 139, 201, 203, 205, 226, 317-331, 350-351, 360, 362, 372-384
src/pyro_mcp/workspace_disk.py                164      0   100%
src/pyro_mcp/workspace_files.py               293      0   100%
src/pyro_mcp/workspace_ports.py                79      1    99%   116
src/pyro_mcp/workspace_shell_output.py         88      2    98%   16, 61
src/pyro_mcp/workspace_shells.py              235     26    89%   105-118, 193-194, 226-227, 230-235, 251, 257-259, 263, 270-271, 299, 301, 303, 306-307
src/pyro_mcp/workspace_use_case_smokes.py     216      8    96%   131, 134-135, 423-426, 490
-------------------------------------------------------------------------
TOTAL                                        7227    706    90%
Required test coverage of 90% reached. Total coverage: 90.23%
======================= 393 passed, 32 warnings in 5.60s =======================.
This commit is contained in:
Thales Maciel 2026-03-13 15:03:20 -03:00
parent 6433847185
commit 999fe1b23a
No known key found for this signature in database
GPG key ID: 33112E6833C34679
15 changed files with 608 additions and 1613 deletions

View file

@ -1,10 +1,15 @@
# First Run Transcript
This is the intended evaluator path for a first successful run on a supported host.
This is the intended evaluator-to-chat-host 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`.
`uvx --from pyro-mcp` prefix. If you are running from a source checkout
instead of the published package, replace `pyro` with `uv run pyro`.
`pyro-mcp` currently has no users. Expect breaking changes while the chat-host
path is still being shaped.
## 1. Verify the host
@ -30,9 +35,10 @@ debian:12-build [installed|not installed] Debian 12 environment with Git and com
## 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.
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
@ -45,9 +51,6 @@ 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
```
@ -62,239 +65,90 @@ $ uvx --from pyro-mcp pyro run debian:12 -- git --version
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.
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
## 5. Start the MCP server
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.
Bare `pyro mcp serve` now starts `workspace-core`, which is the intended chat
path:
```bash
$ uvx --from pyro-mcp pyro mcp serve
```
## 6. Connect a chat host
Claude Code:
```bash
$ claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve
$ claude mcp list
```
Codex:
```bash
$ codex mcp add pyro -- uvx --from pyro-mcp pyro mcp serve
$ codex mcp list
```
OpenCode uses the local config shape shown in:
- [opencode_mcp_config.json](../examples/opencode_mcp_config.json)
Other host-specific references:
- [claude_code_mcp.md](../examples/claude_code_mcp.md)
- [codex_mcp.md](../examples/codex_mcp.md)
- [mcp_client_config.md](../examples/mcp_client_config.md)
## 7. Continue into a real workflow
Once the host is connected, move to one of the five recipe docs in
[use-cases/README.md](use-cases/README.md).
The shortest chat-first story is:
- [use-cases/repro-fix-loop.md](use-cases/repro-fix-loop.md)
If you want terminal-level visibility into what the agent gets, use the manual
workspace flow below:
```bash
$ export WORKSPACE_ID="$(uvx --from pyro-mcp pyro workspace create debian:12 --seed-path ./repo --name repro-fix --label issue=123 --id-only)"
$ uvx --from pyro-mcp pyro workspace list
$ uvx --from pyro-mcp pyro workspace update "$WORKSPACE_ID" --label owner=codex
$ uvx --from pyro-mcp pyro workspace sync push "$WORKSPACE_ID" ./changes
$ uvx --from pyro-mcp pyro workspace file read "$WORKSPACE_ID" note.txt --content-only
$ uvx --from pyro-mcp pyro workspace patch apply "$WORKSPACE_ID" --patch-file 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 --content-only
$ 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
Move to `--profile workspace-full` only when the chat really needs shells,
services, snapshots, secrets, network policy, or disk tools.
## 8. Trust the smoke pack
The repo now treats the full smoke pack as the trustworthy guest-backed
verification path for the advertised workflows:
```bash
$ make smoke-use-cases
```
That runner creates real guest-backed workspaces, exercises all five documented
stories, exports concrete results where relevant, and cleans up on both success
and failure.
## 9. Optional one-shot demo
```bash
$ uvx --from pyro-mcp pyro demo
$ uvx --from pyro-mcp pyro workspace create debian:12 --seed-path ./repo --name repro-fix --label issue=123
$ uvx --from pyro-mcp pyro workspace list
$ uvx --from pyro-mcp pyro workspace update WORKSPACE_ID --label owner=codex
$ 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 --content-only
$ uvx --from pyro-mcp pyro workspace file write WORKSPACE_ID src/app.py --text-file ./app.py
$ uvx --from pyro-mcp pyro workspace patch apply WORKSPACE_ID --patch-file 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 --id-only
$ 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
$ claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve
$ codex mcp add pyro -- uvx --from pyro-mcp pyro mcp serve
```
For most chat hosts, bare `pyro mcp serve` now starts `workspace-core`, the
recommended first MCP profile.
Move to `workspace-full` only when the host truly needs shells, services,
snapshots, secrets, network policy, or disk tools.
Host-specific MCP 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)
`pyro demo` proves the one-shot create/start/exec/delete VM lifecycle works end to end.
Once that stable workspace flow works, continue with the five recipe docs in
[use-cases/README.md](use-cases/README.md) or run the real guest-backed smoke packs directly with
`make smoke-use-cases`. Treat that smoke pack as the trustworthy guest-backed
verification path for the advertised workspace workflows.
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-file fix.patch
$ 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 --id-only
[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 --plain --wait-for-idle-ms 300
/workspace
[workspace-shell-read] workspace_id=... shell_id=... state=running cursor=0 next_cursor=... truncated=False plain=True wait_for_idle_ms=300 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 `4.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 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/<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. 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,
@ -309,7 +163,5 @@ Example output:
}
```
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).
`pyro demo` proves the one-shot create/start/exec/delete VM lifecycle works end
to end.