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:
parent
6433847185
commit
999fe1b23a
15 changed files with 608 additions and 1613 deletions
615
README.md
615
README.md
|
|
@ -1,34 +1,50 @@
|
|||
# pyro-mcp
|
||||
|
||||
`pyro-mcp` is a stable agent workspace product for one-shot commands and persistent work inside ephemeral Firecracker microVMs using curated Linux environments such as `debian:12`.
|
||||
`pyro-mcp` is a disposable MCP workspace for chat-based coding agents such as
|
||||
Claude Code, Codex, and OpenCode.
|
||||
|
||||
It is built for Linux `x86_64` hosts with working KVM. The product path is:
|
||||
|
||||
1. prove the host works
|
||||
2. connect a chat host over MCP
|
||||
3. let the agent work inside a disposable workspace
|
||||
4. validate the workflow with the recipe-backed smoke pack
|
||||
|
||||
`pyro-mcp` currently has no users. Expect breaking changes while this chat-host
|
||||
path is still being shaped.
|
||||
|
||||
This repo is not trying to be a generic VM toolkit, a CI runner, or an
|
||||
SDK-first platform.
|
||||
|
||||
[](https://pypi.org/project/pyro-mcp/)
|
||||
|
||||
This is for coding agents, MCP clients, and developers who want isolated command execution and stable disposable workspaces in ephemeral microVMs.
|
||||
|
||||
It exposes the same runtime in three public forms:
|
||||
|
||||
- the `pyro` CLI
|
||||
- the Python SDK via `from pyro_mcp import Pyro`
|
||||
- an MCP server so LLM clients can call VM tools directly
|
||||
|
||||
## Start Here
|
||||
|
||||
- Install: [docs/install.md](docs/install.md)
|
||||
- Vision: [docs/vision.md](docs/vision.md)
|
||||
- Workspace GA roadmap: [docs/roadmap/task-workspace-ga.md](docs/roadmap/task-workspace-ga.md)
|
||||
- LLM chat roadmap: [docs/roadmap/llm-chat-ergonomics.md](docs/roadmap/llm-chat-ergonomics.md)
|
||||
- Use-case recipes: [docs/use-cases/README.md](docs/use-cases/README.md)
|
||||
- Install and zero-to-hero path: [docs/install.md](docs/install.md)
|
||||
- First run transcript: [docs/first-run.md](docs/first-run.md)
|
||||
- Chat host integrations: [docs/integrations.md](docs/integrations.md)
|
||||
- Use-case recipes: [docs/use-cases/README.md](docs/use-cases/README.md)
|
||||
- Vision: [docs/vision.md](docs/vision.md)
|
||||
- Public contract: [docs/public-contract.md](docs/public-contract.md)
|
||||
- Host requirements: [docs/host-requirements.md](docs/host-requirements.md)
|
||||
- Troubleshooting: [docs/troubleshooting.md](docs/troubleshooting.md)
|
||||
- Stable workspace walkthrough GIF: [docs/assets/workspace-first-run.gif](docs/assets/workspace-first-run.gif)
|
||||
- Terminal walkthrough GIF: [docs/assets/first-run.gif](docs/assets/first-run.gif)
|
||||
- PyPI package: [pypi.org/project/pyro-mcp](https://pypi.org/project/pyro-mcp/)
|
||||
- What's new in 4.0.0: [CHANGELOG.md#400](CHANGELOG.md#400)
|
||||
- Host requirements: [docs/host-requirements.md](docs/host-requirements.md)
|
||||
- Integration targets: [docs/integrations.md](docs/integrations.md)
|
||||
- Public contract: [docs/public-contract.md](docs/public-contract.md)
|
||||
- Troubleshooting: [docs/troubleshooting.md](docs/troubleshooting.md)
|
||||
- Changelog: [CHANGELOG.md](CHANGELOG.md)
|
||||
- PyPI package: [pypi.org/project/pyro-mcp](https://pypi.org/project/pyro-mcp/)
|
||||
|
||||
## Who It's For
|
||||
|
||||
- Claude Code users who want disposable workspaces instead of running directly
|
||||
on the host
|
||||
- Codex users who want an MCP-backed sandbox for repo setup, bug fixing, and
|
||||
evaluation loops
|
||||
- OpenCode users who want the same disposable workspace model
|
||||
- people evaluating repo setup, test, and app-start workflows from a chat
|
||||
interface on a clean machine
|
||||
|
||||
If you want a general VM platform, a queueing system, or a broad SDK product,
|
||||
this repo is intentionally biased away from that story.
|
||||
|
||||
## Quickstart
|
||||
|
||||
|
|
@ -73,84 +89,15 @@ Pulled: debian:12
|
|||
git version ...
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## Stable Workspace Path
|
||||
|
||||
`pyro run` is the stable one-shot entrypoint. `pyro workspace ...` is the stable path when an
|
||||
agent needs one sandbox to stay alive across repeated commands, shells, services, checkpoints,
|
||||
diffs, exports, and reset.
|
||||
|
||||
After that stable walkthrough works, continue with the recipe set in
|
||||
[docs/use-cases/README.md](docs/use-cases/README.md). It packages the five core workspace stories
|
||||
into documented flows plus real guest-backed smoke targets such as `make smoke-use-cases` and
|
||||
`make smoke-repro-fix-loop`. At this point `make smoke-use-cases` is the
|
||||
trustworthy guest-backed release-gate path for the advertised workspace workflows.
|
||||
|
||||
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"
|
||||
```
|
||||
|
||||

|
||||
|
||||
That stable workspace path gives you:
|
||||
|
||||
- initial host-in seeding with `--seed-path`
|
||||
- discovery metadata with `--name`, `--label`, `workspace list`, and `workspace update`
|
||||
- later host-in updates with `workspace sync push`
|
||||
- model-native file inspection and text edits with `workspace file *` and `workspace patch apply`
|
||||
- one-shot commands with `workspace exec` and persistent PTYs with `workspace shell *`
|
||||
- long-running processes with `workspace service *`
|
||||
- explicit checkpoints with `workspace snapshot *`
|
||||
- full-sandbox recovery with `workspace reset`
|
||||
- baseline comparison with `workspace diff`
|
||||
- explicit host-out export with `workspace export`
|
||||
- secondary stopped-workspace disk inspection with `workspace stop|start` and `workspace disk *`
|
||||
|
||||
After the quickstart works:
|
||||
|
||||
- prove the full one-shot lifecycle with `uvx --from pyro-mcp pyro demo`
|
||||
- start most chat hosts with `uvx --from pyro-mcp pyro mcp serve`
|
||||
- create a persistent workspace with `uvx --from pyro-mcp pyro workspace create debian:12 --seed-path ./repo`
|
||||
- add a human-friendly workspace name with `uvx --from pyro-mcp pyro workspace create debian:12 --name repro-fix --label issue=123`
|
||||
- rediscover or retag workspaces with `uvx --from pyro-mcp pyro workspace list` and `uvx --from pyro-mcp pyro workspace update WORKSPACE_ID --label owner=codex`
|
||||
- update a live workspace from the host with `uvx --from pyro-mcp pyro workspace sync push WORKSPACE_ID ./changes`
|
||||
- enable outbound guest networking for one workspace with `uvx --from pyro-mcp pyro workspace create debian:12 --network-policy egress`
|
||||
- add literal or file-backed secrets with `uvx --from pyro-mcp pyro workspace create debian:12 --secret API_TOKEN=expected --secret-file PIP_TOKEN=./token.txt`
|
||||
- map one persisted secret into one exec, shell, or service call with `--secret-env API_TOKEN`
|
||||
- inspect and edit files without shell quoting with `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`, and `uvx --from pyro-mcp pyro workspace patch apply WORKSPACE_ID --patch-file fix.patch`
|
||||
- diff the live workspace against its create-time baseline with `uvx --from pyro-mcp pyro workspace diff WORKSPACE_ID`
|
||||
- capture a checkpoint with `uvx --from pyro-mcp pyro workspace snapshot create WORKSPACE_ID checkpoint`
|
||||
- reset a broken workspace with `uvx --from pyro-mcp pyro workspace reset WORKSPACE_ID --snapshot checkpoint`
|
||||
- export a changed file or directory with `uvx --from pyro-mcp pyro workspace export WORKSPACE_ID note.txt --output ./note.txt`
|
||||
- open a persistent interactive shell with `uvx --from pyro-mcp pyro workspace shell open WORKSPACE_ID --id-only`
|
||||
- start long-running workspace services with `uvx --from pyro-mcp pyro workspace service start WORKSPACE_ID app --ready-file .ready -- sh -lc 'touch .ready && while true; do sleep 60; done'`
|
||||
- publish one guest service port to the host with `uvx --from pyro-mcp pyro workspace create debian:12 --network-policy egress+published-ports` and `uvx --from pyro-mcp pyro workspace service start WORKSPACE_ID app --ready-http http://127.0.0.1:8080/ --publish 18080:8080 -- ./start-app`
|
||||
- stop a workspace for offline inspection with `uvx --from pyro-mcp pyro workspace stop WORKSPACE_ID`
|
||||
- inspect or export one stopped guest rootfs with `uvx --from pyro-mcp pyro workspace disk list WORKSPACE_ID`, `uvx --from pyro-mcp pyro workspace disk read WORKSPACE_ID note.txt --content-only`, and `uvx --from pyro-mcp pyro workspace disk export WORKSPACE_ID --output ./workspace.ext4`
|
||||
- move to Python or MCP via [docs/integrations.md](docs/integrations.md)
|
||||
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.
|
||||
|
||||
## Chat Host Quickstart
|
||||
|
||||
For most MCP chat hosts, bare `pyro mcp serve` now starts `workspace-core`. It exposes the practical
|
||||
persistent editing loop without shells, services, snapshots, secrets, network
|
||||
policy, or disk tools.
|
||||
After the quickstart works, the intended next step is to connect a chat host.
|
||||
Bare `pyro mcp serve` starts `workspace-core`, which is the default product
|
||||
path.
|
||||
|
||||
```bash
|
||||
uvx --from pyro-mcp pyro mcp serve
|
||||
|
|
@ -189,223 +136,55 @@ OpenCode `opencode.json` snippet:
|
|||
}
|
||||
```
|
||||
|
||||
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 workspace-full` only when the host truly needs the full advanced
|
||||
workspace surface.
|
||||
If `pyro-mcp` is already installed, replace `uvx --from pyro-mcp pyro` with
|
||||
`pyro` in the same command or config shape.
|
||||
|
||||
Profile progression:
|
||||
Use `--profile workspace-full` only when the chat truly needs shells, services,
|
||||
snapshots, secrets, network policy, or disk tools.
|
||||
|
||||
- `workspace-core`: default and recommended first profile for normal persistent chat editing
|
||||
- `vm-run`: smallest one-shot-only surface
|
||||
- `workspace-full`: explicit advanced opt-in when the chat truly needs shells, services, snapshots, secrets, network policy, or disk tools
|
||||
## Zero To Hero
|
||||
|
||||
## Supported Hosts
|
||||
1. Validate the host with `pyro doctor`.
|
||||
2. Pull `debian:12` and prove guest execution with `pyro run debian:12 -- git --version`.
|
||||
3. Connect Claude Code, Codex, or OpenCode with `pyro mcp serve`.
|
||||
4. Start with one recipe from [docs/use-cases/README.md](docs/use-cases/README.md).
|
||||
`repro-fix-loop` is the shortest chat-first story.
|
||||
5. Use `make smoke-use-cases` as the trustworthy guest-backed verification path
|
||||
for the advertised workflows.
|
||||
|
||||
Supported today:
|
||||
That is the intended user journey. The terminal commands exist to validate and
|
||||
debug that chat-host path, not to replace it as the main product story.
|
||||
|
||||
- Linux x86_64
|
||||
- Python 3.12+
|
||||
- `uv`
|
||||
- `/dev/kvm`
|
||||
## Manual Terminal Workspace Flow
|
||||
|
||||
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`
|
||||
|
||||
## Detailed Walkthrough
|
||||
|
||||
If you want the expanded version of the canonical quickstart, use the step-by-step flow below.
|
||||
|
||||
### 1. Check the host
|
||||
If you want to understand what the agent gets inside the sandbox, or debug a
|
||||
recipe outside the chat host, use the terminal companion flow below:
|
||||
|
||||
```bash
|
||||
uvx --from pyro-mcp pyro doctor
|
||||
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 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 reset "$WORKSPACE_ID" --snapshot checkpoint
|
||||
pyro workspace export "$WORKSPACE_ID" note.txt --output ./note.txt
|
||||
pyro workspace delete "$WORKSPACE_ID"
|
||||
```
|
||||
|
||||
Expected success signals:
|
||||
Add `workspace-full` only when the chat or your manual debugging loop really
|
||||
needs:
|
||||
|
||||
```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
|
||||
```
|
||||
- persistent PTY shells
|
||||
- long-running services and readiness probes
|
||||
- guest networking and published ports
|
||||
- secrets
|
||||
- stopped-workspace disk inspection
|
||||
|
||||
### 2. Inspect the catalog
|
||||
|
||||
```bash
|
||||
uvx --from pyro-mcp pyro env list
|
||||
```
|
||||
|
||||
Expected output:
|
||||
|
||||
```bash
|
||||
Catalog version: 4.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
|
||||
|
||||
```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 [docs/host-requirements.md](docs/host-requirements.md) for the full host requirements.
|
||||
|
||||
### 4. Run one command in a guest
|
||||
|
||||
```bash
|
||||
uvx --from pyro-mcp pyro run debian:12 -- git --version
|
||||
```
|
||||
|
||||
Expected success signals:
|
||||
|
||||
```bash
|
||||
[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. Optional demos
|
||||
|
||||
```bash
|
||||
uvx --from pyro-mcp pyro demo
|
||||
uvx --from pyro-mcp pyro demo --network
|
||||
```
|
||||
|
||||
`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"
|
||||
}
|
||||
```
|
||||
|
||||
When you are done evaluating and want to remove stale cached environments, run `pyro env prune`.
|
||||
|
||||
If you prefer a fuller copy-pasteable transcript, see [docs/first-run.md](docs/first-run.md).
|
||||
The walkthrough GIF above was rendered from [docs/assets/first-run.tape](docs/assets/first-run.tape) using [scripts/render_tape.sh](scripts/render_tape.sh).
|
||||
|
||||
## Stable Workspaces
|
||||
|
||||
Use `pyro run` for one-shot commands. Use `pyro workspace ...` when you need repeated commands in one
|
||||
workspace without recreating the sandbox every time.
|
||||
|
||||
The project direction is an agent workspace, not a CI job runner. Persistent
|
||||
workspaces are meant to let an agent stay inside one bounded sandbox across multiple
|
||||
steps. See [docs/vision.md](docs/vision.md) for the product thesis and the
|
||||
longer-term interaction model.
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Persistent workspaces start in `/workspace` and keep command history until you delete them. For
|
||||
machine consumption, use `--id-only` for only the identifier or `--json` for the full
|
||||
workspace payload. Use `--seed-path` when
|
||||
you want the workspace to 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 want 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 live `/workspace` tree to its immutable create-time
|
||||
baseline, and `pyro workspace export` to copy one changed file or directory back to the host. Use
|
||||
`pyro workspace snapshot *` and `pyro workspace reset` when you want explicit checkpoints and
|
||||
full-sandbox recovery. Use `pyro workspace exec` for one-shot
|
||||
non-interactive commands inside a live workspace, and `pyro workspace shell *` when you need a
|
||||
persistent PTY session that keeps interactive shell state between calls. Prefer
|
||||
`pyro workspace shell read --plain --wait-for-idle-ms 300` for chat-facing shell reads. Use
|
||||
`pyro workspace service *` when the workspace needs one or more long-running background processes.
|
||||
Typed readiness checks prefer `--ready-file`, `--ready-tcp`, or `--ready-http`; keep
|
||||
`--ready-command` as the escape hatch. Service metadata and logs live outside `/workspace`, so the
|
||||
internal service state does not appear in `pyro workspace diff` or `pyro workspace export`.
|
||||
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 probed 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 secrets are materialized inside the guest at `/run/pyro-secrets/<name>`, and
|
||||
`--secret-env SECRET_NAME[=ENV_VAR]` maps one secret into one exec, shell, or service call without
|
||||
exposing the raw value in workspace status, logs, diffs, or exports. 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.
|
||||
|
||||
## Public Interfaces
|
||||
|
||||
The public user-facing interface is `pyro` and `Pyro`. After the CLI validation path works, you can choose one of three surfaces:
|
||||
|
||||
- `pyro` for direct CLI usage, including one-shot `run` and persistent `workspace` workflows
|
||||
- `from pyro_mcp import Pyro` for Python orchestration
|
||||
- `pyro mcp serve` for MCP clients
|
||||
|
||||
Command forms:
|
||||
|
||||
- published package without install: `uvx --from pyro-mcp pyro ...`
|
||||
- installed package: `pyro ...`
|
||||
- source checkout: `uv run pyro ...`
|
||||
|
||||
`Makefile` targets are contributor conveniences for this repository and are not the primary product UX.
|
||||
The five recipe docs show when those capabilities are justified:
|
||||
[docs/use-cases/README.md](docs/use-cases/README.md)
|
||||
|
||||
## Official Environments
|
||||
|
||||
|
|
@ -415,216 +194,10 @@ Current official environments in the shipped catalog:
|
|||
- `debian:12-base`
|
||||
- `debian:12-build`
|
||||
|
||||
The package ships the embedded Firecracker runtime and a package-controlled environment catalog.
|
||||
Official environments are pulled as OCI artifacts from public Docker Hub repositories into a local
|
||||
cache on first use or through `pyro env pull`.
|
||||
End users do not need registry credentials to pull or run official environments.
|
||||
The default cache location is `~/.cache/pyro-mcp/environments`; override it with
|
||||
`PYRO_ENVIRONMENT_CACHE_DIR`.
|
||||
|
||||
## CLI
|
||||
|
||||
List available environments:
|
||||
|
||||
```bash
|
||||
pyro env list
|
||||
```
|
||||
|
||||
Prefetch one environment:
|
||||
|
||||
```bash
|
||||
pyro env pull debian:12
|
||||
```
|
||||
|
||||
Run one command in an ephemeral VM:
|
||||
|
||||
```bash
|
||||
pyro run debian:12 -- git --version
|
||||
```
|
||||
|
||||
Run with outbound internet enabled:
|
||||
|
||||
```bash
|
||||
pyro run debian:12 --network -- \
|
||||
'python3 -c "import urllib.request; print(urllib.request.urlopen(\"https://example.com\", timeout=10).status)"'
|
||||
```
|
||||
|
||||
Show runtime and host diagnostics:
|
||||
|
||||
```bash
|
||||
pyro doctor
|
||||
pyro doctor --json
|
||||
```
|
||||
|
||||
`pyro run` defaults to `1 vCPU / 1024 MiB`.
|
||||
It fails closed when guest boot or guest exec is unavailable.
|
||||
Use `--allow-host-compat` only if you explicitly want host execution.
|
||||
|
||||
Run the MCP server after the CLI path above works. Start most chat hosts with
|
||||
`workspace-core`:
|
||||
|
||||
```bash
|
||||
pyro mcp serve
|
||||
```
|
||||
|
||||
Profile progression for chat hosts:
|
||||
|
||||
- `workspace-core`: recommended first profile for normal persistent chat editing
|
||||
- `vm-run`: expose only `vm_run` for one-shot-only hosts
|
||||
- `workspace-full`: expose shells, services, snapshots, secrets, network policy, and disk tools when the chat truly needs the full stable surface
|
||||
|
||||
Run the deterministic demo:
|
||||
|
||||
```bash
|
||||
pyro demo
|
||||
pyro demo --network
|
||||
```
|
||||
|
||||
Run the Ollama demo:
|
||||
|
||||
```bash
|
||||
ollama serve
|
||||
ollama pull llama3.2:3b
|
||||
pyro demo ollama
|
||||
```
|
||||
|
||||
## Python SDK
|
||||
|
||||
```python
|
||||
from pyro_mcp import Pyro
|
||||
|
||||
pyro = Pyro()
|
||||
result = pyro.run_in_vm(
|
||||
environment="debian:12",
|
||||
command="git --version",
|
||||
timeout_seconds=30,
|
||||
network=False,
|
||||
)
|
||||
print(result["stdout"])
|
||||
```
|
||||
|
||||
Lower-level lifecycle control remains available:
|
||||
|
||||
```python
|
||||
from pyro_mcp import Pyro
|
||||
|
||||
pyro = Pyro()
|
||||
created = pyro.create_vm(
|
||||
environment="debian:12",
|
||||
ttl_seconds=600,
|
||||
network=True,
|
||||
)
|
||||
vm_id = created["vm_id"]
|
||||
pyro.start_vm(vm_id)
|
||||
result = pyro.exec_vm(vm_id, command="git --version", timeout_seconds=30)
|
||||
print(result["stdout"])
|
||||
```
|
||||
|
||||
`exec_vm()` is a one-command auto-cleaning call. After it returns, the VM is already deleted.
|
||||
|
||||
Environment management is also available through the SDK:
|
||||
|
||||
```python
|
||||
from pyro_mcp import Pyro
|
||||
|
||||
pyro = Pyro()
|
||||
print(pyro.list_environments())
|
||||
print(pyro.inspect_environment("debian:12"))
|
||||
```
|
||||
|
||||
For repeated commands in one workspace:
|
||||
|
||||
```python
|
||||
from pyro_mcp import Pyro
|
||||
|
||||
pyro = Pyro()
|
||||
workspace = pyro.create_workspace(environment="debian:12", seed_path="./repo")
|
||||
workspace_id = workspace["workspace_id"]
|
||||
try:
|
||||
pyro.push_workspace_sync(workspace_id, "./changes", dest="src")
|
||||
result = pyro.exec_workspace(workspace_id, command="cat src/note.txt")
|
||||
print(result["stdout"], end="")
|
||||
finally:
|
||||
pyro.delete_workspace(workspace_id)
|
||||
```
|
||||
|
||||
## MCP Tools
|
||||
|
||||
Primary agent-facing tool:
|
||||
|
||||
- `vm_run(environment, command, vcpu_count=1, mem_mib=1024, timeout_seconds=30, ttl_seconds=600, network=false, allow_host_compat=false)`
|
||||
|
||||
Advanced lifecycle tools:
|
||||
|
||||
- `vm_list_environments()`
|
||||
- `vm_create(environment, vcpu_count=1, mem_mib=1024, ttl_seconds=600, network=false, allow_host_compat=false)`
|
||||
- `vm_start(vm_id)`
|
||||
- `vm_exec(vm_id, command, timeout_seconds=30)` auto-cleans the VM after that command
|
||||
- `vm_stop(vm_id)`
|
||||
- `vm_delete(vm_id)`
|
||||
- `vm_status(vm_id)`
|
||||
- `vm_network_info(vm_id)`
|
||||
- `vm_reap_expired()`
|
||||
|
||||
Persistent workspace tools:
|
||||
|
||||
- `workspace_create(environment, vcpu_count=1, mem_mib=1024, ttl_seconds=600, network_policy="off", allow_host_compat=false, seed_path=null, secrets=null)`
|
||||
- `workspace_sync_push(workspace_id, source_path, dest="/workspace")`
|
||||
- `workspace_exec(workspace_id, command, timeout_seconds=30, secret_env=null)`
|
||||
- `workspace_export(workspace_id, path, output_path)`
|
||||
- `workspace_diff(workspace_id)`
|
||||
- `snapshot_create(workspace_id, snapshot_name)`
|
||||
- `snapshot_list(workspace_id)`
|
||||
- `snapshot_delete(workspace_id, snapshot_name)`
|
||||
- `workspace_reset(workspace_id, snapshot="baseline")`
|
||||
- `service_start(workspace_id, service_name, command, cwd="/workspace", readiness=null, ready_timeout_seconds=30, ready_interval_ms=500, secret_env=null, published_ports=null)`
|
||||
- `service_list(workspace_id)`
|
||||
- `service_status(workspace_id, service_name)`
|
||||
- `service_logs(workspace_id, service_name, tail_lines=200)`
|
||||
- `service_stop(workspace_id, service_name)`
|
||||
- `shell_open(workspace_id, cwd="/workspace", cols=120, rows=30, secret_env=null)`
|
||||
- `shell_read(workspace_id, shell_id, cursor=0, max_chars=65536, plain=False, wait_for_idle_ms=None)`
|
||||
- `shell_write(workspace_id, shell_id, input, append_newline=true)`
|
||||
- `shell_signal(workspace_id, shell_id, signal_name="INT")`
|
||||
- `shell_close(workspace_id, shell_id)`
|
||||
- `workspace_status(workspace_id)`
|
||||
- `workspace_logs(workspace_id)`
|
||||
- `workspace_delete(workspace_id)`
|
||||
|
||||
Recommended MCP tool profiles:
|
||||
|
||||
- `vm-run`: `vm_run` only
|
||||
- `workspace-core`: `vm_run`, `workspace_create`, `workspace_list`, `workspace_update`, `workspace_status`, `workspace_sync_push`, `workspace_exec`, `workspace_logs`, `workspace_file_list`, `workspace_file_read`, `workspace_file_write`, `workspace_patch_apply`, `workspace_diff`, `workspace_export`, `workspace_reset`, `workspace_delete`
|
||||
- `workspace-full`: the complete stable MCP surface above
|
||||
|
||||
## Integration Examples
|
||||
|
||||
- Python one-shot SDK example: [examples/python_run.py](examples/python_run.py)
|
||||
- Python lifecycle example: [examples/python_lifecycle.py](examples/python_lifecycle.py)
|
||||
- Python workspace example: [examples/python_workspace.py](examples/python_workspace.py)
|
||||
- Claude Code MCP setup: [examples/claude_code_mcp.md](examples/claude_code_mcp.md)
|
||||
- Codex MCP setup: [examples/codex_mcp.md](examples/codex_mcp.md)
|
||||
- OpenCode MCP config: [examples/opencode_mcp_config.json](examples/opencode_mcp_config.json)
|
||||
- Generic MCP client config: [examples/mcp_client_config.md](examples/mcp_client_config.md)
|
||||
- Claude Desktop MCP config: [examples/claude_desktop_mcp_config.json](examples/claude_desktop_mcp_config.json)
|
||||
- Cursor MCP config: [examples/cursor_mcp_config.json](examples/cursor_mcp_config.json)
|
||||
- OpenAI Responses API example: [examples/openai_responses_vm_run.py](examples/openai_responses_vm_run.py)
|
||||
- OpenAI Responses `workspace-core` example: [examples/openai_responses_workspace_core.py](examples/openai_responses_workspace_core.py)
|
||||
- LangChain wrapper example: [examples/langchain_vm_run.py](examples/langchain_vm_run.py)
|
||||
- Agent-ready `vm_run` example: [examples/agent_vm_run.py](examples/agent_vm_run.py)
|
||||
|
||||
## Runtime
|
||||
|
||||
The package ships an embedded Linux x86_64 runtime payload with:
|
||||
|
||||
- Firecracker
|
||||
- Jailer
|
||||
- guest agent
|
||||
- runtime manifest and diagnostics
|
||||
|
||||
No system Firecracker installation is required.
|
||||
`pyro` installs curated environments into a local cache and reports their status through `pyro env inspect` and `pyro doctor`.
|
||||
The public CLI is human-readable by default; add `--json` for structured output.
|
||||
The embedded Firecracker runtime ships with the package. Official environments
|
||||
are pulled as OCI artifacts from public Docker Hub into a local cache on first
|
||||
use or through `pyro env pull`. End users do not need registry credentials to
|
||||
pull or run the official environments.
|
||||
|
||||
## Contributor Workflow
|
||||
|
||||
|
|
@ -637,11 +210,12 @@ make check
|
|||
make dist-check
|
||||
```
|
||||
|
||||
Contributor runtime sources live under `runtime_sources/`. The packaged runtime bundle under
|
||||
`src/pyro_mcp/runtime_bundle/` contains the embedded boot/runtime assets plus manifest metadata;
|
||||
end-user environment installs pull OCI-published environments by default. Use
|
||||
`PYRO_RUNTIME_BUNDLE_DIR=build/runtime_bundle` only when you are explicitly validating a locally
|
||||
built contributor runtime bundle.
|
||||
Contributor runtime sources live under `runtime_sources/`. The packaged runtime
|
||||
bundle under `src/pyro_mcp/runtime_bundle/` contains the embedded boot/runtime
|
||||
assets plus manifest metadata. End-user environment installs pull
|
||||
OCI-published environments by default. Use
|
||||
`PYRO_RUNTIME_BUNDLE_DIR=build/runtime_bundle` only when you are explicitly
|
||||
validating a locally built contributor runtime bundle.
|
||||
|
||||
Official environment publication is performed locally against Docker Hub:
|
||||
|
||||
|
|
@ -652,20 +226,9 @@ make runtime-materialize
|
|||
make runtime-publish-official-environments-oci
|
||||
```
|
||||
|
||||
`make runtime-publish-environment-oci` auto-exports the OCI layout for the selected
|
||||
environment if it is missing.
|
||||
The publisher accepts either `DOCKERHUB_USERNAME` and `DOCKERHUB_TOKEN` or
|
||||
`OCI_REGISTRY_USERNAME` and `OCI_REGISTRY_PASSWORD`.
|
||||
Docker Hub uploads are chunked by default for large rootfs layers; if you need to tune a slow
|
||||
link, use `PYRO_OCI_UPLOAD_TIMEOUT_SECONDS`, `PYRO_OCI_UPLOAD_CHUNK_SIZE_BYTES`, and
|
||||
`PYRO_OCI_REQUEST_TIMEOUT_SECONDS`.
|
||||
|
||||
For a local PyPI publish:
|
||||
|
||||
```bash
|
||||
export TWINE_PASSWORD='pypi-...'
|
||||
make pypi-publish
|
||||
```
|
||||
|
||||
`make pypi-publish` defaults `TWINE_USERNAME` to `__token__`.
|
||||
Set `PYPI_REPOSITORY_URL=https://test.pypi.org/legacy/` to publish to TestPyPI instead.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue