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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
296
docs/install.md
296
docs/install.md
|
|
@ -1,11 +1,17 @@
|
|||
# Install
|
||||
|
||||
`pyro-mcp` is built for chat-based coding agents on Linux `x86_64` with KVM.
|
||||
This document is intentionally biased toward that path.
|
||||
|
||||
`pyro-mcp` currently has no users. Expect breaking changes while the chat-host
|
||||
flow is still being shaped.
|
||||
|
||||
## Support Matrix
|
||||
|
||||
Supported today:
|
||||
|
||||
- Linux x86_64
|
||||
- Python 3.12+
|
||||
- Linux `x86_64`
|
||||
- Python `3.12+`
|
||||
- `uv`
|
||||
- `/dev/kvm`
|
||||
|
||||
|
|
@ -53,11 +59,12 @@ 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`.
|
||||
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 ...`.
|
||||
After that one-shot proof works, the intended next step is `pyro mcp serve`.
|
||||
|
||||
### 1. Check the host first
|
||||
## 1. Check the host
|
||||
|
||||
```bash
|
||||
uvx --from pyro-mcp pyro doctor
|
||||
|
|
@ -76,7 +83,7 @@ Networking: tun=yes ip_forward=yes
|
|||
|
||||
If `Runtime: FAIL`, stop here and use [troubleshooting.md](troubleshooting.md).
|
||||
|
||||
### 2. Inspect the catalog
|
||||
## 2. Inspect the catalog
|
||||
|
||||
```bash
|
||||
uvx --from pyro-mcp pyro env list
|
||||
|
|
@ -91,15 +98,16 @@ debian:12-base [installed|not installed] Minimal Debian 12 environment for shell
|
|||
debian:12-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled.
|
||||
```
|
||||
|
||||
### 3. Pull the default environment
|
||||
## 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.
|
||||
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:
|
||||
|
||||
|
|
@ -110,7 +118,7 @@ Pulled: debian:12
|
|||
...
|
||||
```
|
||||
|
||||
### 4. Run one command in a guest
|
||||
## 4. Run one command in a guest
|
||||
|
||||
```bash
|
||||
uvx --from pyro-mcp pyro run debian:12 -- git --version
|
||||
|
|
@ -126,119 +134,14 @@ Expected success signals:
|
|||
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
|
||||
The guest command output and the `[run] ...` summary are written to different
|
||||
streams, so they may appear in either order. 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. Connect a chat host
|
||||
|
||||
## 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`
|
||||
- Python SDK: `from pyro_mcp import Pyro`
|
||||
- Demos: `pyro demo` or `pyro demo --network`
|
||||
|
||||
## Chat Host Quickstart
|
||||
|
||||
For most chat-host integrations, bare `pyro mcp serve` now starts
|
||||
`workspace-core`:
|
||||
Bare `pyro mcp serve` now starts `workspace-core`, which is the default
|
||||
product path.
|
||||
|
||||
```bash
|
||||
uvx --from pyro-mcp pyro mcp serve
|
||||
|
|
@ -246,10 +149,10 @@ uvx --from pyro-mcp pyro mcp serve
|
|||
|
||||
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 setup: [claude_code_mcp.md](../examples/claude_code_mcp.md)
|
||||
- Codex setup: [codex_mcp.md](../examples/codex_mcp.md)
|
||||
- OpenCode config: [opencode_mcp_config.json](../examples/opencode_mcp_config.json)
|
||||
- Generic MCP fallback: [mcp_client_config.md](../examples/mcp_client_config.md)
|
||||
|
||||
Claude Code:
|
||||
|
||||
|
|
@ -263,86 +166,87 @@ Codex:
|
|||
codex mcp add pyro -- uvx --from pyro-mcp pyro mcp serve
|
||||
```
|
||||
|
||||
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 workspace-full` only when the host truly needs the full advanced
|
||||
workspace surface.
|
||||
OpenCode uses the `mcp` / `type: "local"` config shape shown in
|
||||
[opencode_mcp_config.json](../examples/opencode_mcp_config.json).
|
||||
|
||||
Use profile progression like this:
|
||||
If `pyro-mcp` is already installed, replace `uvx --from pyro-mcp pyro` with
|
||||
`pyro` in the same command or config shape.
|
||||
|
||||
- `workspace-core`: default and recommended first profile for normal persistent chat editing
|
||||
- `vm-run`: one-shot-only integrations
|
||||
- `workspace-full`: explicit advanced opt-in when the host truly needs shells, services, snapshots, secrets, network policy, or disk tools
|
||||
Use `--profile workspace-full` only when the chat truly needs shells, services,
|
||||
snapshots, secrets, network policy, or disk tools.
|
||||
|
||||
## Stable Workspace
|
||||
## 6. Go from zero to hero
|
||||
|
||||
Use `pyro workspace ...` when you need repeated commands in one sandbox instead of one-shot `pyro run`.
|
||||
The intended user journey is:
|
||||
|
||||
1. validate the host with `pyro doctor`
|
||||
2. pull `debian:12`
|
||||
3. prove guest execution with `pyro run debian:12 -- git --version`
|
||||
4. connect Claude Code, Codex, or OpenCode with `pyro mcp serve`
|
||||
5. start with one use-case recipe from [use-cases/README.md](use-cases/README.md)
|
||||
6. trust but verify with `make smoke-use-cases`
|
||||
|
||||
If you want the shortest chat-first story, start with
|
||||
[use-cases/repro-fix-loop.md](use-cases/repro-fix-loop.md).
|
||||
|
||||
## 7. Manual terminal workspace flow
|
||||
|
||||
If you want to inspect the workspace model directly from the terminal, use the
|
||||
companion flow below. This is for understanding and debugging the chat-host
|
||||
product, not the primary story.
|
||||
|
||||
```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
|
||||
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 reset "$WORKSPACE_ID" --snapshot checkpoint
|
||||
pyro workspace export "$WORKSPACE_ID" note.txt --output ./note.txt
|
||||
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 `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 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.
|
||||
When you need deeper debugging or richer recipes, add:
|
||||
|
||||
## Contributor Clone
|
||||
- `pyro workspace shell *` for interactive PTY state
|
||||
- `pyro workspace service *` for long-running processes and readiness probes
|
||||
- `pyro workspace create --network-policy egress+published-ports` plus
|
||||
`workspace service start --publish` for host-probed services
|
||||
- `pyro workspace create --secret` and `--secret-file` when the sandbox needs
|
||||
private tokens
|
||||
- `pyro workspace stop` plus `workspace disk *` for offline inspection
|
||||
|
||||
## 8. Trustworthy verification path
|
||||
|
||||
The five recipe docs in [use-cases/README.md](use-cases/README.md) are backed
|
||||
by a real Firecracker smoke pack:
|
||||
|
||||
```bash
|
||||
make smoke-use-cases
|
||||
```
|
||||
|
||||
Treat that smoke pack as the trustworthy guest-backed verification path for the
|
||||
advertised chat-host workflows.
|
||||
|
||||
## Installed CLI
|
||||
|
||||
If you already installed the package, the same 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
|
||||
pyro mcp serve
|
||||
```
|
||||
|
||||
## Contributor clone
|
||||
|
||||
```bash
|
||||
git lfs install
|
||||
|
|
|
|||
|
|
@ -1,164 +1,141 @@
|
|||
# Integration Targets
|
||||
# Chat Host Integrations
|
||||
|
||||
These are the main ways to integrate `pyro-mcp` into an LLM application.
|
||||
This page documents the intended product path for `pyro-mcp`:
|
||||
|
||||
Use this page after you have already validated the host and guest execution through the
|
||||
CLI path in [install.md](install.md) or [first-run.md](first-run.md).
|
||||
- validate the host with the CLI
|
||||
- run `pyro mcp serve`
|
||||
- connect a chat host
|
||||
- let the agent work inside disposable workspaces
|
||||
|
||||
`pyro-mcp` currently has no users. Expect breaking changes while this chat-host
|
||||
path is still being shaped.
|
||||
|
||||
Use this page after you have already validated the host and guest execution
|
||||
through [install.md](install.md) or [first-run.md](first-run.md).
|
||||
|
||||
## Recommended Default
|
||||
|
||||
Bare `pyro mcp serve` now starts `workspace-core`. Use `vm_run` only for one-shot
|
||||
integrations, and promote the chat surface to `workspace-full` only when it
|
||||
truly needs shells, services, snapshots, secrets, network policy, or disk
|
||||
tools.
|
||||
Bare `pyro mcp serve` starts `workspace-core`. That is the product path.
|
||||
|
||||
That keeps the model-facing contract small:
|
||||
```bash
|
||||
pyro mcp serve
|
||||
```
|
||||
|
||||
- one tool
|
||||
- one command
|
||||
- one ephemeral VM
|
||||
- automatic cleanup
|
||||
Use `--profile workspace-full` only when the chat truly needs shells, services,
|
||||
snapshots, secrets, network policy, or disk tools.
|
||||
|
||||
Profile progression:
|
||||
## Claude Code
|
||||
|
||||
- `workspace-core`: default and recommended first profile for persistent chat editing
|
||||
- `vm-run`: one-shot only
|
||||
- `workspace-full`: the full stable workspace surface, including shells, services, snapshots, secrets, network policy, and disk tools
|
||||
Package without install:
|
||||
|
||||
## OpenAI Responses API
|
||||
```bash
|
||||
claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve
|
||||
claude mcp list
|
||||
```
|
||||
|
||||
Best when:
|
||||
Already installed:
|
||||
|
||||
- your agent already uses OpenAI models directly
|
||||
- you want a normal tool-calling loop instead of MCP transport
|
||||
- you want the smallest amount of integration code
|
||||
```bash
|
||||
claude mcp add pyro -- pyro mcp serve
|
||||
claude mcp list
|
||||
```
|
||||
|
||||
Recommended surface:
|
||||
Reference:
|
||||
|
||||
- `vm_run` for one-shot loops
|
||||
- the `workspace-core` tool set for the normal persistent chat loop
|
||||
- the `workspace-full` tool set only when the host explicitly needs advanced workspace capabilities
|
||||
- [claude_code_mcp.md](../examples/claude_code_mcp.md)
|
||||
|
||||
Canonical example:
|
||||
## Codex
|
||||
|
||||
- [examples/openai_responses_vm_run.py](../examples/openai_responses_vm_run.py)
|
||||
- [examples/openai_responses_workspace_core.py](../examples/openai_responses_workspace_core.py)
|
||||
- [docs/use-cases/repro-fix-loop.md](use-cases/repro-fix-loop.md)
|
||||
Package without install:
|
||||
|
||||
## MCP Clients
|
||||
```bash
|
||||
codex mcp add pyro -- uvx --from pyro-mcp pyro mcp serve
|
||||
codex mcp list
|
||||
```
|
||||
|
||||
Best when:
|
||||
Already installed:
|
||||
|
||||
- your host application already supports MCP
|
||||
- you want `pyro` to run as an external stdio server
|
||||
- you want tool schemas to be discovered directly from the server
|
||||
```bash
|
||||
codex mcp add pyro -- pyro mcp serve
|
||||
codex mcp list
|
||||
```
|
||||
|
||||
Recommended entrypoint:
|
||||
Reference:
|
||||
|
||||
- `pyro mcp serve`
|
||||
- [codex_mcp.md](../examples/codex_mcp.md)
|
||||
|
||||
Profile progression:
|
||||
## OpenCode
|
||||
|
||||
- `pyro mcp serve --profile vm-run` for the smallest one-shot surface
|
||||
- `pyro mcp serve` for the normal persistent chat loop
|
||||
- `pyro mcp serve --profile workspace-full` only when the model truly needs advanced workspace tools
|
||||
Use the local MCP config shape from:
|
||||
|
||||
Host-specific onramps:
|
||||
- [opencode_mcp_config.json](../examples/opencode_mcp_config.json)
|
||||
|
||||
- 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 Desktop fallback: [examples/claude_desktop_mcp_config.json](../examples/claude_desktop_mcp_config.json)
|
||||
- Cursor fallback: [examples/cursor_mcp_config.json](../examples/cursor_mcp_config.json)
|
||||
- Use-case recipes: [docs/use-cases/README.md](use-cases/README.md)
|
||||
Minimal `opencode.json` snippet:
|
||||
|
||||
## Direct Python SDK
|
||||
```json
|
||||
{
|
||||
"mcp": {
|
||||
"pyro": {
|
||||
"type": "local",
|
||||
"enabled": true,
|
||||
"command": ["uvx", "--from", "pyro-mcp", "pyro", "mcp", "serve"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Best when:
|
||||
If `pyro-mcp` is already installed, replace `uvx --from pyro-mcp pyro` with
|
||||
`pyro` in the same config shape.
|
||||
|
||||
- your application owns orchestration itself
|
||||
- you do not need MCP transport
|
||||
- you want direct access to `Pyro`
|
||||
## Generic MCP Fallback
|
||||
|
||||
Recommended default:
|
||||
Use this only when the host expects a plain `mcpServers` JSON config and does
|
||||
not already have a dedicated example in the repo:
|
||||
|
||||
- `Pyro.run_in_vm(...)`
|
||||
- `Pyro.create_server()` for most chat hosts now that `workspace-core` is the default profile
|
||||
- `Pyro.create_workspace(name=..., labels=...)` + `Pyro.list_workspaces()` + `Pyro.update_workspace(...)` when repeated workspaces need human-friendly discovery metadata
|
||||
- `Pyro.create_workspace(seed_path=...)` + `Pyro.push_workspace_sync(...)` + `Pyro.exec_workspace(...)` when repeated workspace commands are required
|
||||
- `Pyro.list_workspace_files(...)` / `Pyro.read_workspace_file(...)` / `Pyro.write_workspace_file(...)` / `Pyro.apply_workspace_patch(...)` when the agent needs model-native file inspection and text edits inside one live workspace
|
||||
- `Pyro.create_workspace(..., secrets=...)` + `Pyro.exec_workspace(..., secret_env=...)` when the workspace needs private tokens or authenticated setup
|
||||
- `Pyro.create_workspace(..., network_policy="egress+published-ports")` + `Pyro.start_service(..., published_ports=[...])` when the host must probe one workspace service
|
||||
- `Pyro.diff_workspace(...)` + `Pyro.export_workspace(...)` when the agent needs baseline comparison or host-out file transfer
|
||||
- `Pyro.start_service(..., secret_env=...)` + `Pyro.list_services(...)` + `Pyro.logs_service(...)` when the agent needs long-running background processes in one workspace
|
||||
- `Pyro.open_shell(..., secret_env=...)` + `Pyro.write_shell(...)` + `Pyro.read_shell(..., plain=True, wait_for_idle_ms=300)` when the agent needs an interactive PTY inside the workspace
|
||||
- [mcp_client_config.md](../examples/mcp_client_config.md)
|
||||
|
||||
Lifecycle note:
|
||||
Generic `mcpServers` shape:
|
||||
|
||||
- `Pyro.exec_vm(...)` runs one command and auto-cleans the VM afterward
|
||||
- use `create_vm(...)` + `start_vm(...)` only when you need pre-exec inspection or status before
|
||||
that final exec
|
||||
- use `create_workspace(seed_path=...)` when the agent needs repeated commands in one persistent
|
||||
`/workspace` that starts from host content
|
||||
- use `create_workspace(name=..., labels=...)`, `list_workspaces()`, and `update_workspace(...)`
|
||||
when the agent or operator needs to rediscover the right workspace later without external notes
|
||||
- use `push_workspace_sync(...)` when later host-side changes need to be imported into that
|
||||
running workspace without recreating it
|
||||
- use `list_workspace_files(...)`, `read_workspace_file(...)`, `write_workspace_file(...)`, and
|
||||
`apply_workspace_patch(...)` when the agent should inspect or edit workspace files without shell
|
||||
quoting tricks
|
||||
- use `create_workspace(..., secrets=...)` plus `secret_env` on exec, shell, or service start when
|
||||
the agent needs private tokens or authenticated startup inside that workspace
|
||||
- use `create_workspace(..., network_policy="egress+published-ports")` plus
|
||||
`start_service(..., published_ports=[...])` when the host must probe one service from that
|
||||
workspace
|
||||
- use `diff_workspace(...)` when the agent needs a structured comparison against the immutable
|
||||
create-time baseline
|
||||
- use `export_workspace(...)` when the agent needs one file or directory copied back to the host
|
||||
- use `stop_workspace(...)` plus `list_workspace_disk(...)`, `read_workspace_disk(...)`, or
|
||||
`export_workspace_disk(...)` when the agent needs offline inspection or one raw ext4 copy from
|
||||
a stopped guest-backed workspace
|
||||
- use `start_service(...)` when the agent needs long-running processes and typed readiness inside
|
||||
one workspace
|
||||
- use `open_shell(...)` when the agent needs interactive shell state instead of one-shot execs
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"pyro": {
|
||||
"command": "uvx",
|
||||
"args": ["--from", "pyro-mcp", "pyro", "mcp", "serve"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Examples:
|
||||
## When To Use `workspace-full`
|
||||
|
||||
- [examples/python_run.py](../examples/python_run.py)
|
||||
- [examples/python_lifecycle.py](../examples/python_lifecycle.py)
|
||||
- [examples/python_workspace.py](../examples/python_workspace.py)
|
||||
- [examples/python_shell.py](../examples/python_shell.py)
|
||||
- [docs/use-cases/README.md](use-cases/README.md)
|
||||
Stay on bare `pyro mcp serve` unless the chat host truly needs:
|
||||
|
||||
## Agent Framework Wrappers
|
||||
- persistent PTY shell sessions
|
||||
- long-running services and readiness probes
|
||||
- secrets
|
||||
- guest networking and published ports
|
||||
- stopped-workspace disk inspection or raw ext4 export
|
||||
|
||||
Examples:
|
||||
When that is necessary:
|
||||
|
||||
- LangChain tools
|
||||
- PydanticAI tools
|
||||
- custom in-house orchestration layers
|
||||
```bash
|
||||
pyro mcp serve --profile workspace-full
|
||||
```
|
||||
|
||||
Best when:
|
||||
## Recipe-Backed Workflows
|
||||
|
||||
- you already have an application framework that expects a Python callable tool
|
||||
- you want to wrap `vm_run` behind framework-specific abstractions
|
||||
Once the host is connected, move to the five real workflows in
|
||||
[use-cases/README.md](use-cases/README.md):
|
||||
|
||||
Recommended pattern:
|
||||
- cold-start repo validation
|
||||
- repro plus fix loops
|
||||
- parallel isolated workspaces
|
||||
- unsafe or untrusted code inspection
|
||||
- review and evaluation workflows
|
||||
|
||||
- keep the framework wrapper thin
|
||||
- map one-shot framework tool input directly onto `vm_run`
|
||||
- expose `workspace_*` only when the framework truly needs repeated commands in one workspace
|
||||
Validate the whole story with:
|
||||
|
||||
Concrete example:
|
||||
|
||||
- [examples/langchain_vm_run.py](../examples/langchain_vm_run.py)
|
||||
|
||||
## Selection Rule
|
||||
|
||||
Choose the narrowest integration that matches the host environment:
|
||||
|
||||
1. OpenAI Responses API if you want a direct provider tool loop.
|
||||
2. MCP if your host already speaks MCP.
|
||||
3. Python SDK if you own orchestration and do not need transport.
|
||||
4. Framework wrappers only as thin adapters over the same `vm_run` contract.
|
||||
```bash
|
||||
make smoke-use-cases
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,375 +1,150 @@
|
|||
# Public Contract
|
||||
|
||||
This document defines the stable public interface for `pyro-mcp` `3.x`.
|
||||
This document describes the chat way to use `pyro-mcp` in `4.x`.
|
||||
|
||||
`pyro-mcp` currently has no users. Expect breaking changes while this chat-host
|
||||
path is still being shaped.
|
||||
|
||||
This document is intentionally biased. It describes the path users are meant to
|
||||
follow today:
|
||||
|
||||
- prove the host with the terminal companion commands
|
||||
- serve disposable workspaces over MCP
|
||||
- connect Claude Code, Codex, or OpenCode
|
||||
- use the recipe-backed workflows
|
||||
|
||||
This page does not try to document every building block in the repo. It
|
||||
documents the chat-host path the project is actively shaping.
|
||||
|
||||
## Package Identity
|
||||
|
||||
- Distribution name: `pyro-mcp`
|
||||
- Public executable: `pyro`
|
||||
- Public Python import: `from pyro_mcp import Pyro`
|
||||
- Public package-level factory: `from pyro_mcp import create_server`
|
||||
- distribution name: `pyro-mcp`
|
||||
- public executable: `pyro`
|
||||
- primary product entrypoint: `pyro mcp serve`
|
||||
|
||||
Stable product framing:
|
||||
`pyro-mcp` is a disposable MCP workspace for chat-based coding agents on Linux
|
||||
`x86_64` KVM hosts.
|
||||
|
||||
- `pyro run` is the stable one-shot entrypoint.
|
||||
- `pyro workspace ...` is the stable persistent workspace contract.
|
||||
## Supported Product Path
|
||||
|
||||
## CLI Contract
|
||||
The intended user journey is:
|
||||
|
||||
Top-level commands:
|
||||
1. `pyro doctor`
|
||||
2. `pyro env list`
|
||||
3. `pyro env pull debian:12`
|
||||
4. `pyro run debian:12 -- git --version`
|
||||
5. `pyro mcp serve`
|
||||
6. connect Claude Code, Codex, or OpenCode
|
||||
7. run one of the documented recipe-backed workflows
|
||||
8. validate the whole story with `make smoke-use-cases`
|
||||
|
||||
## Evaluator CLI
|
||||
|
||||
These terminal commands are the documented companion path for the chat-host
|
||||
product:
|
||||
|
||||
- `pyro doctor`
|
||||
- `pyro env list`
|
||||
- `pyro env pull`
|
||||
- `pyro env inspect`
|
||||
- `pyro env prune`
|
||||
- `pyro mcp serve`
|
||||
- `pyro run`
|
||||
- `pyro workspace create`
|
||||
- `pyro workspace list`
|
||||
- `pyro workspace sync push`
|
||||
- `pyro workspace stop`
|
||||
- `pyro workspace start`
|
||||
- `pyro workspace exec`
|
||||
- `pyro workspace file list`
|
||||
- `pyro workspace file read`
|
||||
- `pyro workspace file write`
|
||||
- `pyro workspace export`
|
||||
- `pyro workspace patch apply`
|
||||
- `pyro workspace disk export`
|
||||
- `pyro workspace disk list`
|
||||
- `pyro workspace disk read`
|
||||
- `pyro workspace diff`
|
||||
- `pyro workspace snapshot create`
|
||||
- `pyro workspace snapshot list`
|
||||
- `pyro workspace snapshot delete`
|
||||
- `pyro workspace reset`
|
||||
- `pyro workspace service start`
|
||||
- `pyro workspace service list`
|
||||
- `pyro workspace service status`
|
||||
- `pyro workspace service logs`
|
||||
- `pyro workspace service stop`
|
||||
- `pyro workspace shell open`
|
||||
- `pyro workspace shell read`
|
||||
- `pyro workspace shell write`
|
||||
- `pyro workspace shell signal`
|
||||
- `pyro workspace shell close`
|
||||
- `pyro workspace status`
|
||||
- `pyro workspace update`
|
||||
- `pyro workspace logs`
|
||||
- `pyro workspace delete`
|
||||
- `pyro doctor`
|
||||
- `pyro demo`
|
||||
- `pyro demo ollama`
|
||||
|
||||
Stable `pyro run` interface:
|
||||
What to expect from that path:
|
||||
|
||||
- positional environment name
|
||||
- `--vcpu-count`
|
||||
- `--mem-mib`
|
||||
- `--timeout-seconds`
|
||||
- `--ttl-seconds`
|
||||
- `--network`
|
||||
- `--allow-host-compat`
|
||||
- `--json`
|
||||
- `pyro run <environment> -- <command>` defaults to `1 vCPU / 1024 MiB`
|
||||
- `pyro run` fails if guest boot or guest exec is unavailable unless
|
||||
`--allow-host-compat` is set
|
||||
- `pyro run`, `pyro env list`, `pyro env pull`, `pyro env inspect`,
|
||||
`pyro env prune`, and `pyro doctor` are human-readable by default and return
|
||||
structured JSON with `--json`
|
||||
- the first official environment pull downloads from public Docker Hub into the
|
||||
local environment cache
|
||||
- `pyro demo` proves the one-shot create/start/exec/delete VM lifecycle end to
|
||||
end
|
||||
|
||||
Behavioral guarantees:
|
||||
These commands exist to validate and debug the chat-host path. They are not the
|
||||
main product destination.
|
||||
|
||||
- `pyro run <environment> -- <command>` defaults to `1 vCPU / 1024 MiB`.
|
||||
- `pyro run` fails if guest boot or guest exec is unavailable unless `--allow-host-compat` is set.
|
||||
- `pyro run`, `pyro env list`, `pyro env pull`, `pyro env inspect`, `pyro env prune`, and `pyro doctor` are human-readable by default and return structured JSON with `--json`.
|
||||
- `pyro demo ollama` prints log lines plus a final summary line.
|
||||
- `pyro workspace create` auto-starts a persistent workspace.
|
||||
- `pyro workspace create --seed-path PATH` seeds `/workspace` from a host directory or a local `.tar` / `.tar.gz` / `.tgz` archive before the workspace is returned.
|
||||
- `pyro workspace create --id-only` prints only the new `workspace_id` plus a trailing newline.
|
||||
- `pyro workspace create --name NAME --label KEY=VALUE` attaches human-oriented discovery metadata without changing the stable `workspace_id`.
|
||||
- `pyro workspace create --network-policy {off,egress,egress+published-ports}` controls workspace guest networking and whether services may publish localhost ports.
|
||||
- `pyro mcp serve --profile {vm-run,workspace-core,workspace-full}` narrows the model-facing MCP surface without changing runtime behavior; `workspace-core` is the recommended first profile for most chat hosts.
|
||||
- `pyro workspace create --secret NAME=VALUE` and `--secret-file NAME=PATH` persist guest-only UTF-8 secrets outside `/workspace`.
|
||||
- `pyro workspace list` returns persisted workspaces sorted by most recent `last_activity_at`.
|
||||
- `pyro workspace sync push WORKSPACE_ID SOURCE_PATH [--dest WORKSPACE_PATH]` imports later host-side directory or archive content into a started workspace.
|
||||
- `pyro workspace stop WORKSPACE_ID` stops one persistent workspace without deleting its `/workspace`, snapshots, or command history.
|
||||
- `pyro workspace start WORKSPACE_ID` restarts one stopped workspace without resetting `/workspace`.
|
||||
- `pyro workspace file list WORKSPACE_ID [PATH] [--recursive]` returns metadata for one live path under `/workspace`.
|
||||
- `pyro workspace file read WORKSPACE_ID PATH [--max-bytes N] [--content-only]` reads one regular text file under `/workspace`.
|
||||
- `pyro workspace file write WORKSPACE_ID PATH --text TEXT` and `--text-file PATH` create or replace one regular text file under `/workspace`, creating missing parent directories automatically.
|
||||
- `pyro workspace export WORKSPACE_ID PATH --output HOST_PATH` exports one file or directory from `/workspace` back to the host.
|
||||
- `pyro workspace disk export WORKSPACE_ID --output HOST_PATH` copies the stopped guest-backed workspace rootfs as raw ext4 to the host.
|
||||
- `pyro workspace disk list WORKSPACE_ID [PATH] [--recursive]` inspects a stopped guest-backed workspace rootfs offline without booting the guest.
|
||||
- `pyro workspace disk read WORKSPACE_ID PATH [--max-bytes N] [--content-only]` reads one regular file from a stopped guest-backed workspace rootfs offline.
|
||||
- `pyro workspace disk *` requires `state=stopped` and a guest-backed workspace; it fails on `host_compat`.
|
||||
- `pyro workspace diff WORKSPACE_ID` compares the current `/workspace` tree to the immutable create-time baseline.
|
||||
- `pyro workspace snapshot *` manages explicit named snapshots in addition to the implicit `baseline`.
|
||||
- `pyro workspace reset WORKSPACE_ID [--snapshot SNAPSHOT_NAME|baseline]` recreates the full sandbox and restores `/workspace` from the chosen snapshot.
|
||||
- `pyro workspace service *` manages long-running named services inside one started workspace with typed readiness probes.
|
||||
- `pyro workspace service start --publish GUEST_PORT` or `--publish HOST_PORT:GUEST_PORT` publishes one guest TCP port to `127.0.0.1` on the host.
|
||||
- `pyro workspace exec --secret-env SECRET_NAME[=ENV_VAR]` maps one persisted secret into one exec call.
|
||||
- `pyro workspace service start --secret-env SECRET_NAME[=ENV_VAR]` maps one persisted secret into one service start call.
|
||||
- `pyro workspace exec` runs in the persistent `/workspace` for that workspace and does not auto-clean.
|
||||
- `pyro workspace patch apply WORKSPACE_ID --patch TEXT` and `--patch-file PATH` apply one unified text patch with add/modify/delete operations under `/workspace`.
|
||||
- `pyro workspace shell open --id-only` prints only the new `shell_id` plus a trailing newline.
|
||||
- `pyro workspace shell open --secret-env SECRET_NAME[=ENV_VAR]` maps one persisted secret into the opened shell environment.
|
||||
- `pyro workspace shell *` manages persistent PTY sessions inside a started workspace.
|
||||
- `pyro workspace shell read --plain --wait-for-idle-ms 300` is the recommended chat-facing read mode; raw shell reads remain available without `--plain`.
|
||||
- `pyro workspace logs` returns persisted command history for that workspace until `pyro workspace delete`.
|
||||
- `pyro workspace update` changes only discovery metadata such as `name` and key/value `labels`.
|
||||
- Workspace create/status results expose `workspace_seed` metadata describing how `/workspace` was initialized.
|
||||
- Workspace create/status/reset/update results expose `name`, `labels`, and `last_activity_at`.
|
||||
- Workspace create/status/reset results expose `network_policy`.
|
||||
- Workspace create/status/reset results expose `reset_count` and `last_reset_at`.
|
||||
- Workspace create/status/reset results expose safe `secrets` metadata with each secret name and source kind, but never the secret values.
|
||||
- `pyro workspace status` includes aggregate `service_count` and `running_service_count` fields.
|
||||
- `pyro workspace list` returns one summary row per persisted workspace with `workspace_id`, `name`, `labels`, `environment`, `state`, `created_at`, `last_activity_at`, `expires_at`, `command_count`, `service_count`, and `running_service_count`.
|
||||
- `pyro workspace service start`, `pyro workspace service list`, and `pyro workspace service status` expose published-port metadata when present.
|
||||
## MCP Entry Point
|
||||
|
||||
## Python SDK Contract
|
||||
The product entrypoint is:
|
||||
|
||||
Primary facade:
|
||||
```bash
|
||||
pyro mcp serve
|
||||
```
|
||||
|
||||
- `Pyro`
|
||||
What to expect:
|
||||
|
||||
Supported public entrypoints:
|
||||
- bare `pyro mcp serve` starts `workspace-core`
|
||||
- `workspace-core` is the default product path for chat hosts
|
||||
- `pyro mcp serve --profile workspace-full` explicitly opts into the larger
|
||||
tool surface
|
||||
- `pyro mcp serve --profile vm-run` exposes the smallest one-shot-only surface
|
||||
|
||||
- `create_server()`
|
||||
- `Pyro.create_server()`
|
||||
- `Pyro.list_environments()`
|
||||
- `Pyro.pull_environment(environment)`
|
||||
- `Pyro.inspect_environment(environment)`
|
||||
- `Pyro.prune_environments()`
|
||||
- `Pyro.create_vm(...)`
|
||||
- `Pyro.create_workspace(..., name=None, labels=None, network_policy="off", secrets=None)`
|
||||
- `Pyro.list_workspaces()`
|
||||
- `Pyro.push_workspace_sync(workspace_id, source_path, *, dest="/workspace")`
|
||||
- `Pyro.stop_workspace(workspace_id)`
|
||||
- `Pyro.start_workspace(workspace_id)`
|
||||
- `Pyro.list_workspace_files(workspace_id, path="/workspace", recursive=False)`
|
||||
- `Pyro.read_workspace_file(workspace_id, path, *, max_bytes=65536)`
|
||||
- `Pyro.write_workspace_file(workspace_id, path, *, text)`
|
||||
- `Pyro.export_workspace(workspace_id, path, *, output_path)`
|
||||
- `Pyro.apply_workspace_patch(workspace_id, *, patch)`
|
||||
- `Pyro.export_workspace_disk(workspace_id, *, output_path)`
|
||||
- `Pyro.list_workspace_disk(workspace_id, path="/workspace", recursive=False)`
|
||||
- `Pyro.read_workspace_disk(workspace_id, path, *, max_bytes=65536)`
|
||||
- `Pyro.diff_workspace(workspace_id)`
|
||||
- `Pyro.create_snapshot(workspace_id, snapshot_name)`
|
||||
- `Pyro.list_snapshots(workspace_id)`
|
||||
- `Pyro.delete_snapshot(workspace_id, snapshot_name)`
|
||||
- `Pyro.reset_workspace(workspace_id, *, snapshot="baseline")`
|
||||
- `Pyro.start_service(workspace_id, service_name, *, command, cwd="/workspace", readiness=None, ready_timeout_seconds=30, ready_interval_ms=500, secret_env=None, published_ports=None)`
|
||||
- `Pyro.list_services(workspace_id)`
|
||||
- `Pyro.status_service(workspace_id, service_name)`
|
||||
- `Pyro.logs_service(workspace_id, service_name, *, tail_lines=200, all=False)`
|
||||
- `Pyro.stop_service(workspace_id, service_name)`
|
||||
- `Pyro.open_shell(workspace_id, *, cwd="/workspace", cols=120, rows=30, secret_env=None)`
|
||||
- `Pyro.read_shell(workspace_id, shell_id, *, cursor=0, max_chars=65536, plain=False, wait_for_idle_ms=None)`
|
||||
- `Pyro.write_shell(workspace_id, shell_id, *, input, append_newline=True)`
|
||||
- `Pyro.signal_shell(workspace_id, shell_id, *, signal_name="INT")`
|
||||
- `Pyro.close_shell(workspace_id, shell_id)`
|
||||
- `Pyro.start_vm(vm_id)`
|
||||
- `Pyro.exec_vm(vm_id, *, command, timeout_seconds=30)`
|
||||
- `Pyro.exec_workspace(workspace_id, *, command, timeout_seconds=30, secret_env=None)`
|
||||
- `Pyro.stop_vm(vm_id)`
|
||||
- `Pyro.delete_vm(vm_id)`
|
||||
- `Pyro.delete_workspace(workspace_id)`
|
||||
- `Pyro.status_vm(vm_id)`
|
||||
- `Pyro.status_workspace(workspace_id)`
|
||||
- `Pyro.update_workspace(workspace_id, *, name=None, clear_name=False, labels=None, clear_labels=None)`
|
||||
- `Pyro.logs_workspace(workspace_id)`
|
||||
- `Pyro.network_info_vm(vm_id)`
|
||||
- `Pyro.reap_expired()`
|
||||
- `Pyro.run_in_vm(...)`
|
||||
Host-specific setup docs:
|
||||
|
||||
Stable public method names:
|
||||
- [claude_code_mcp.md](../examples/claude_code_mcp.md)
|
||||
- [codex_mcp.md](../examples/codex_mcp.md)
|
||||
- [opencode_mcp_config.json](../examples/opencode_mcp_config.json)
|
||||
- [mcp_client_config.md](../examples/mcp_client_config.md)
|
||||
|
||||
- `create_server()`
|
||||
- `list_environments()`
|
||||
- `pull_environment(environment)`
|
||||
- `inspect_environment(environment)`
|
||||
- `prune_environments()`
|
||||
- `create_vm(...)`
|
||||
- `create_workspace(..., name=None, labels=None, network_policy="off", secrets=None)`
|
||||
- `list_workspaces()`
|
||||
- `push_workspace_sync(workspace_id, source_path, *, dest="/workspace")`
|
||||
- `stop_workspace(workspace_id)`
|
||||
- `start_workspace(workspace_id)`
|
||||
- `list_workspace_files(workspace_id, path="/workspace", recursive=False)`
|
||||
- `read_workspace_file(workspace_id, path, *, max_bytes=65536)`
|
||||
- `write_workspace_file(workspace_id, path, *, text)`
|
||||
- `export_workspace(workspace_id, path, *, output_path)`
|
||||
- `apply_workspace_patch(workspace_id, *, patch)`
|
||||
- `export_workspace_disk(workspace_id, *, output_path)`
|
||||
- `list_workspace_disk(workspace_id, path="/workspace", recursive=False)`
|
||||
- `read_workspace_disk(workspace_id, path, *, max_bytes=65536)`
|
||||
- `diff_workspace(workspace_id)`
|
||||
- `create_snapshot(workspace_id, snapshot_name)`
|
||||
- `list_snapshots(workspace_id)`
|
||||
- `delete_snapshot(workspace_id, snapshot_name)`
|
||||
- `reset_workspace(workspace_id, *, snapshot="baseline")`
|
||||
- `start_service(workspace_id, service_name, *, command, cwd="/workspace", readiness=None, ready_timeout_seconds=30, ready_interval_ms=500, secret_env=None, published_ports=None)`
|
||||
- `list_services(workspace_id)`
|
||||
- `status_service(workspace_id, service_name)`
|
||||
- `logs_service(workspace_id, service_name, *, tail_lines=200, all=False)`
|
||||
- `stop_service(workspace_id, service_name)`
|
||||
- `open_shell(workspace_id, *, cwd="/workspace", cols=120, rows=30, secret_env=None)`
|
||||
- `read_shell(workspace_id, shell_id, *, cursor=0, max_chars=65536, plain=False, wait_for_idle_ms=None)`
|
||||
- `write_shell(workspace_id, shell_id, *, input, append_newline=True)`
|
||||
- `signal_shell(workspace_id, shell_id, *, signal_name="INT")`
|
||||
- `close_shell(workspace_id, shell_id)`
|
||||
- `start_vm(vm_id)`
|
||||
- `exec_vm(vm_id, *, command, timeout_seconds=30)`
|
||||
- `exec_workspace(workspace_id, *, command, timeout_seconds=30, secret_env=None)`
|
||||
- `stop_vm(vm_id)`
|
||||
- `delete_vm(vm_id)`
|
||||
- `delete_workspace(workspace_id)`
|
||||
- `status_vm(vm_id)`
|
||||
- `status_workspace(workspace_id)`
|
||||
- `update_workspace(workspace_id, *, name=None, clear_name=False, labels=None, clear_labels=None)`
|
||||
- `logs_workspace(workspace_id)`
|
||||
- `network_info_vm(vm_id)`
|
||||
- `reap_expired()`
|
||||
- `run_in_vm(...)`
|
||||
## Chat-Facing Workspace Contract
|
||||
|
||||
Behavioral defaults:
|
||||
|
||||
- `Pyro.create_vm(...)` and `Pyro.run_in_vm(...)` default to `vcpu_count=1` and `mem_mib=1024`.
|
||||
- `Pyro.create_workspace(...)` defaults to `vcpu_count=1` and `mem_mib=1024`.
|
||||
- `allow_host_compat` defaults to `False` on `create_vm(...)` and `run_in_vm(...)`.
|
||||
- `allow_host_compat` defaults to `False` on `create_workspace(...)`.
|
||||
- `Pyro.create_workspace(..., seed_path=...)` seeds `/workspace` from a host directory or a local `.tar` / `.tar.gz` / `.tgz` archive before the workspace is returned.
|
||||
- `Pyro.create_workspace(..., name=..., labels=...)` attaches human-oriented discovery metadata without changing the stable `workspace_id`.
|
||||
- `Pyro.create_workspace(..., network_policy="off"|"egress"|"egress+published-ports")` controls workspace guest networking and whether services may publish host ports.
|
||||
- `Pyro.create_workspace(..., secrets=...)` persists guest-only UTF-8 secrets outside `/workspace`.
|
||||
- `Pyro.list_workspaces()` returns persisted workspace summaries sorted by most recent `last_activity_at`.
|
||||
- `Pyro.push_workspace_sync(...)` imports later host-side directory or archive content into a started workspace.
|
||||
- `Pyro.stop_workspace(...)` stops one persistent workspace without deleting its `/workspace`, snapshots, or command history.
|
||||
- `Pyro.start_workspace(...)` restarts one stopped workspace without resetting `/workspace`.
|
||||
- `Pyro.list_workspace_files(...)`, `Pyro.read_workspace_file(...)`, and `Pyro.write_workspace_file(...)` provide structured live `/workspace` inspection and text edits without shell quoting.
|
||||
- `Pyro.export_workspace(...)` exports one file or directory from `/workspace` to an explicit host path.
|
||||
- `Pyro.apply_workspace_patch(...)` applies unified text patches for add/modify/delete operations under `/workspace`.
|
||||
- `Pyro.export_workspace_disk(...)` copies the stopped guest-backed workspace rootfs as raw ext4 to an explicit host path.
|
||||
- `Pyro.list_workspace_disk(...)` inspects a stopped guest-backed workspace rootfs offline without booting the guest.
|
||||
- `Pyro.read_workspace_disk(...)` reads one regular file from a stopped guest-backed workspace rootfs offline.
|
||||
- stopped-workspace disk helpers require `state=stopped` and a guest-backed workspace; they fail on `host_compat`.
|
||||
- `Pyro.diff_workspace(...)` compares the current `/workspace` tree to the immutable create-time baseline.
|
||||
- `Pyro.create_snapshot(...)` captures one named `/workspace` checkpoint.
|
||||
- `Pyro.list_snapshots(...)` lists the implicit `baseline` plus any named snapshots.
|
||||
- `Pyro.delete_snapshot(...)` deletes one named snapshot while leaving `baseline` intact.
|
||||
- `Pyro.reset_workspace(...)` recreates the full sandbox from `baseline` or one named snapshot and clears command, shell, and service history.
|
||||
- `Pyro.start_service(..., secret_env=...)` maps persisted workspace secrets into that service process as environment variables for that start call only.
|
||||
- `Pyro.start_service(...)` starts one named long-running process in a started workspace and waits for its typed readiness probe when configured.
|
||||
- `Pyro.start_service(..., published_ports=[...])` publishes one or more guest TCP ports to `127.0.0.1` on the host when the workspace network policy is `egress+published-ports`.
|
||||
- `Pyro.list_services(...)`, `Pyro.status_service(...)`, `Pyro.logs_service(...)`, and `Pyro.stop_service(...)` manage those persisted workspace services.
|
||||
- `Pyro.exec_vm(...)` runs one command and auto-cleans that VM after the exec completes.
|
||||
- `Pyro.exec_workspace(..., secret_env=...)` maps persisted workspace secrets into that exec call as environment variables for that call only.
|
||||
- `Pyro.exec_workspace(...)` runs one command in the persistent workspace and leaves it alive.
|
||||
- `Pyro.open_shell(..., secret_env=...)` maps persisted workspace secrets into the shell environment when that shell opens.
|
||||
- `Pyro.open_shell(...)` opens a persistent PTY shell attached to one started workspace.
|
||||
- `Pyro.read_shell(...)` reads merged text output from that shell by cursor, with optional plain rendering and idle batching for chat-facing consumers.
|
||||
- `Pyro.write_shell(...)`, `Pyro.signal_shell(...)`, and `Pyro.close_shell(...)` operate on that persistent shell session.
|
||||
- `Pyro.update_workspace(...)` changes only discovery metadata such as `name` and key/value `labels`.
|
||||
|
||||
## MCP Contract
|
||||
|
||||
Stable MCP profiles:
|
||||
|
||||
- `vm-run`: exposes only `vm_run`
|
||||
- `workspace-core`: exposes `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`, and `workspace_delete`
|
||||
- `workspace-full`: exposes the complete stable MCP surface below
|
||||
|
||||
Behavioral defaults:
|
||||
|
||||
- `pyro mcp serve`, `create_server()`, and `Pyro.create_server()` default to `workspace-core`.
|
||||
- `workspace-core` is the default and recommended first profile for most new chat-host integrations.
|
||||
- `create_server(profile="workspace-full")` and `Pyro.create_server(profile="workspace-full")` opt into the full advanced workspace surface explicitly.
|
||||
- `workspace-core` narrows `workspace_create` by omitting `network_policy` and `secrets`.
|
||||
- `workspace-core` narrows `workspace_exec` by omitting `secret_env`.
|
||||
|
||||
Primary tool:
|
||||
`workspace-core` is the normal chat path. It exposes:
|
||||
|
||||
- `vm_run`
|
||||
|
||||
Advanced lifecycle tools:
|
||||
|
||||
- `vm_list_environments`
|
||||
- `vm_create`
|
||||
- `vm_start`
|
||||
- `vm_exec`
|
||||
- `vm_stop`
|
||||
- `vm_delete`
|
||||
- `vm_status`
|
||||
- `vm_network_info`
|
||||
- `vm_reap_expired`
|
||||
|
||||
Persistent workspace tools:
|
||||
|
||||
- `workspace_create`
|
||||
- `workspace_list`
|
||||
- `workspace_update`
|
||||
- `workspace_status`
|
||||
- `workspace_sync_push`
|
||||
- `workspace_stop`
|
||||
- `workspace_start`
|
||||
- `workspace_exec`
|
||||
- `workspace_logs`
|
||||
- `workspace_file_list`
|
||||
- `workspace_file_read`
|
||||
- `workspace_file_write`
|
||||
- `workspace_export`
|
||||
- `workspace_patch_apply`
|
||||
- `workspace_disk_export`
|
||||
- `workspace_disk_list`
|
||||
- `workspace_disk_read`
|
||||
- `workspace_diff`
|
||||
- `snapshot_create`
|
||||
- `snapshot_list`
|
||||
- `snapshot_delete`
|
||||
- `workspace_export`
|
||||
- `workspace_reset`
|
||||
- `service_start`
|
||||
- `service_list`
|
||||
- `service_status`
|
||||
- `service_logs`
|
||||
- `service_stop`
|
||||
- `shell_open`
|
||||
- `shell_read`
|
||||
- `shell_write`
|
||||
- `shell_signal`
|
||||
- `shell_close`
|
||||
- `workspace_status`
|
||||
- `workspace_update`
|
||||
- `workspace_logs`
|
||||
- `workspace_delete`
|
||||
|
||||
Behavioral defaults:
|
||||
That is enough for the normal persistent editing loop:
|
||||
|
||||
- `vm_run` and `vm_create` default to `vcpu_count=1` and `mem_mib=1024`.
|
||||
- `workspace_create` defaults to `vcpu_count=1` and `mem_mib=1024`.
|
||||
- `vm_run` and `vm_create` expose `allow_host_compat`, which defaults to `false`.
|
||||
- `workspace_create` exposes `allow_host_compat`, which defaults to `false`.
|
||||
- `workspace_create` accepts optional `seed_path` and seeds `/workspace` from a host directory or a local `.tar` / `.tar.gz` / `.tgz` archive before the workspace is returned.
|
||||
- `workspace_create` accepts optional `name` and `labels` metadata for human discovery without changing the stable `workspace_id`.
|
||||
- `workspace_create` accepts `network_policy` with `off`, `egress`, or `egress+published-ports` to control workspace guest networking and service port publication.
|
||||
- `workspace_create` accepts optional `secrets` and persists guest-only UTF-8 secret material outside `/workspace`.
|
||||
- `workspace_list` returns persisted workspace summaries sorted by most recent `last_activity_at`.
|
||||
- `workspace_sync_push` imports later host-side directory or archive content into a started workspace, with an optional `dest` under `/workspace`.
|
||||
- `workspace_stop` stops one persistent workspace without deleting its `/workspace`, snapshots, or command history.
|
||||
- `workspace_start` restarts one stopped workspace without resetting `/workspace`.
|
||||
- `workspace_file_list`, `workspace_file_read`, and `workspace_file_write` provide structured live `/workspace` inspection and text edits without shell quoting.
|
||||
- `workspace_export` exports one file or directory from `/workspace` to an explicit host path.
|
||||
- `workspace_patch_apply` applies unified text patches for add/modify/delete operations under `/workspace`.
|
||||
- `workspace_disk_export` copies the stopped guest-backed workspace rootfs as raw ext4 to an explicit host path.
|
||||
- `workspace_disk_list` inspects a stopped guest-backed workspace rootfs offline without booting the guest.
|
||||
- `workspace_disk_read` reads one regular file from a stopped guest-backed workspace rootfs offline.
|
||||
- stopped-workspace disk tools require `state=stopped` and a guest-backed workspace; they fail on `host_compat`.
|
||||
- `workspace_diff` compares the current `/workspace` tree to the immutable create-time baseline.
|
||||
- `snapshot_create`, `snapshot_list`, and `snapshot_delete` manage explicit named snapshots in addition to the implicit `baseline`.
|
||||
- `workspace_reset` recreates the full sandbox and restores `/workspace` from `baseline` or one named snapshot.
|
||||
- `service_start`, `service_list`, `service_status`, `service_logs`, and `service_stop` manage persistent named services inside a started workspace.
|
||||
- `service_start` accepts optional `published_ports` to expose guest TCP ports on `127.0.0.1` when the workspace network policy is `egress+published-ports`.
|
||||
- `vm_exec` runs one command and auto-cleans that VM after the exec completes.
|
||||
- `workspace_exec` accepts optional `secret_env` mappings for one exec call and leaves the workspace alive.
|
||||
- `workspace_update` changes only discovery metadata such as `name` and key/value `labels`.
|
||||
- `service_start` accepts optional `secret_env` mappings for one service start call.
|
||||
- `shell_open` accepts optional `secret_env` mappings for the opened shell session.
|
||||
- `shell_open`, `shell_read`, `shell_write`, `shell_signal`, and `shell_close` manage persistent PTY shells inside a started workspace.
|
||||
- create one workspace
|
||||
- sync or seed repo content
|
||||
- inspect and edit files without shell quoting
|
||||
- run commands repeatedly in one sandbox
|
||||
- diff and export results
|
||||
- reset and retry
|
||||
- delete the workspace when the task is done
|
||||
|
||||
## Versioning Rule
|
||||
Move to `workspace-full` only when the chat truly needs:
|
||||
|
||||
- `pyro-mcp` uses SemVer.
|
||||
- Environment names are stable identifiers in the shipped catalog.
|
||||
- Changing a public command name, public flag, public method name, public MCP tool name, or required request field is a breaking change.
|
||||
- persistent PTY shell sessions
|
||||
- long-running services and readiness probes
|
||||
- secrets
|
||||
- guest networking and published ports
|
||||
- stopped-workspace disk inspection
|
||||
|
||||
## Recipe-Backed Workflows
|
||||
|
||||
The documented product workflows are:
|
||||
|
||||
| Workflow | Recommended profile | Doc |
|
||||
| --- | --- | --- |
|
||||
| Cold-start repo validation | `workspace-full` | [use-cases/cold-start-repo-validation.md](use-cases/cold-start-repo-validation.md) |
|
||||
| Repro plus fix loop | `workspace-core` | [use-cases/repro-fix-loop.md](use-cases/repro-fix-loop.md) |
|
||||
| Parallel isolated workspaces | `workspace-core` | [use-cases/parallel-workspaces.md](use-cases/parallel-workspaces.md) |
|
||||
| Unsafe or untrusted code inspection | `workspace-core` | [use-cases/untrusted-inspection.md](use-cases/untrusted-inspection.md) |
|
||||
| Review and evaluation workflows | `workspace-full` | [use-cases/review-eval-workflows.md](use-cases/review-eval-workflows.md) |
|
||||
|
||||
Treat this smoke pack as the trustworthy guest-backed verification path for the
|
||||
advertised product:
|
||||
|
||||
```bash
|
||||
make smoke-use-cases
|
||||
```
|
||||
|
||||
The chat-host MCP path above is the thing the docs are intentionally shaping
|
||||
around.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Workspace Use-Case Recipes
|
||||
|
||||
These recipes turn the stable workspace surface into five concrete agent flows.
|
||||
These recipes turn the chat-host workspace path into five concrete agent flows.
|
||||
They are the canonical next step after the quickstart in [install.md](../install.md)
|
||||
or [first-run.md](../first-run.md).
|
||||
|
||||
|
|
|
|||
|
|
@ -12,26 +12,14 @@ Use this flow when an agent needs to treat a fresh repo like a new user would:
|
|||
seed it into a workspace, run the validation script, keep one long-running
|
||||
process alive, probe it from another command, and export a validation report.
|
||||
|
||||
Canonical SDK flow:
|
||||
Chat-host recipe:
|
||||
|
||||
```python
|
||||
from pyro_mcp import Pyro
|
||||
|
||||
pyro = Pyro()
|
||||
created = pyro.create_workspace(environment="debian:12", seed_path="./repo")
|
||||
workspace_id = str(created["workspace_id"])
|
||||
|
||||
pyro.exec_workspace(workspace_id, command="sh validate.sh")
|
||||
pyro.start_service(
|
||||
workspace_id,
|
||||
"app",
|
||||
command="sh serve.sh",
|
||||
readiness={"type": "file", "path": ".app-ready"},
|
||||
)
|
||||
pyro.exec_workspace(workspace_id, command="sh -lc 'test -f .app-ready && cat service-state.txt'")
|
||||
pyro.export_workspace(workspace_id, "validation-report.txt", output_path="./validation-report.txt")
|
||||
pyro.delete_workspace(workspace_id)
|
||||
```
|
||||
1. Create one workspace from the repo seed.
|
||||
2. Run the validation command inside that workspace.
|
||||
3. Start the app as a long-running service with readiness configured.
|
||||
4. Probe the ready service from another command in the same workspace.
|
||||
5. Export the validation report back to the host.
|
||||
6. Delete the workspace when the evaluation is done.
|
||||
|
||||
This recipe is intentionally guest-local and deterministic. It proves startup,
|
||||
service readiness, validation, and host-out report capture without depending on
|
||||
|
|
|
|||
|
|
@ -11,32 +11,14 @@ make smoke-parallel-workspaces
|
|||
Use this flow when the agent needs one isolated workspace per issue, branch, or
|
||||
review thread and must rediscover the right one later.
|
||||
|
||||
Canonical SDK flow:
|
||||
Chat-host recipe:
|
||||
|
||||
```python
|
||||
from pyro_mcp import Pyro
|
||||
|
||||
pyro = Pyro()
|
||||
alpha = pyro.create_workspace(
|
||||
environment="debian:12",
|
||||
seed_path="./repo",
|
||||
name="parallel-alpha",
|
||||
labels={"branch": "alpha", "issue": "123"},
|
||||
)
|
||||
beta = pyro.create_workspace(
|
||||
environment="debian:12",
|
||||
seed_path="./repo",
|
||||
name="parallel-beta",
|
||||
labels={"branch": "beta", "issue": "456"},
|
||||
)
|
||||
|
||||
pyro.write_workspace_file(alpha["workspace_id"], "branch.txt", text="alpha\n")
|
||||
pyro.write_workspace_file(beta["workspace_id"], "branch.txt", text="beta\n")
|
||||
pyro.update_workspace(alpha["workspace_id"], labels={"branch": "alpha", "owner": "alice"})
|
||||
pyro.list_workspaces()
|
||||
pyro.delete_workspace(alpha["workspace_id"])
|
||||
pyro.delete_workspace(beta["workspace_id"])
|
||||
```
|
||||
1. Create one workspace per issue or branch with a human-friendly name and
|
||||
labels.
|
||||
2. Mutate each workspace independently.
|
||||
3. Rediscover the right workspace later with `workspace_list`.
|
||||
4. Update metadata when ownership or issue mapping changes.
|
||||
5. Delete each workspace independently when its task is done.
|
||||
|
||||
The important proof here is operational, not syntactic: names, labels, list
|
||||
ordering, and file contents stay isolated even when multiple workspaces are
|
||||
|
|
|
|||
|
|
@ -12,31 +12,15 @@ Use this flow when the agent has to reproduce a bug, patch files without shell
|
|||
quoting tricks, rerun the failing command, diff the result, export the fix, and
|
||||
reset back to baseline.
|
||||
|
||||
Canonical SDK flow:
|
||||
Chat-host recipe:
|
||||
|
||||
```python
|
||||
from pyro_mcp import Pyro
|
||||
|
||||
pyro = Pyro()
|
||||
created = pyro.create_workspace(environment="debian:12", seed_path="./broken-repro")
|
||||
workspace_id = str(created["workspace_id"])
|
||||
|
||||
pyro.exec_workspace(workspace_id, command="sh check.sh")
|
||||
pyro.read_workspace_file(workspace_id, "message.txt")
|
||||
pyro.apply_workspace_patch(
|
||||
workspace_id,
|
||||
patch="--- a/message.txt\n+++ b/message.txt\n@@ -1 +1 @@\n-broken\n+fixed\n",
|
||||
)
|
||||
pyro.exec_workspace(workspace_id, command="sh check.sh")
|
||||
pyro.diff_workspace(workspace_id)
|
||||
pyro.export_workspace(workspace_id, "message.txt", output_path="./message.txt")
|
||||
pyro.reset_workspace(workspace_id)
|
||||
pyro.delete_workspace(workspace_id)
|
||||
```
|
||||
|
||||
Canonical MCP/chat example:
|
||||
|
||||
- [examples/openai_responses_workspace_core.py](../../examples/openai_responses_workspace_core.py)
|
||||
1. Create one workspace from the broken repro seed.
|
||||
2. Run the failing command.
|
||||
3. Inspect the broken file with structured file reads.
|
||||
4. Apply the fix with `workspace_patch_apply`.
|
||||
5. Rerun the failing command in the same workspace.
|
||||
6. Diff and export the changed result.
|
||||
7. Reset to baseline and delete the workspace.
|
||||
|
||||
This is the main `workspace-core` story: model-native file ops, repeatable exec,
|
||||
structured diff, explicit export, and reset-over-repair.
|
||||
|
|
|
|||
|
|
@ -11,30 +11,14 @@ make smoke-review-eval
|
|||
Use this flow when an agent needs to read a checklist interactively, run an
|
||||
evaluation script, checkpoint or reset its changes, and export the final report.
|
||||
|
||||
Canonical SDK flow:
|
||||
Chat-host recipe:
|
||||
|
||||
```python
|
||||
from pyro_mcp import Pyro
|
||||
|
||||
pyro = Pyro()
|
||||
created = pyro.create_workspace(environment="debian:12", seed_path="./review-fixture")
|
||||
workspace_id = str(created["workspace_id"])
|
||||
|
||||
pyro.create_snapshot(workspace_id, "pre-review")
|
||||
shell = pyro.open_shell(workspace_id)
|
||||
pyro.write_shell(workspace_id, shell["shell_id"], input="cat CHECKLIST.md")
|
||||
pyro.read_shell(
|
||||
workspace_id,
|
||||
shell["shell_id"],
|
||||
plain=True,
|
||||
wait_for_idle_ms=300,
|
||||
)
|
||||
pyro.close_shell(workspace_id, shell["shell_id"])
|
||||
pyro.exec_workspace(workspace_id, command="sh review.sh")
|
||||
pyro.export_workspace(workspace_id, "review-report.txt", output_path="./review-report.txt")
|
||||
pyro.reset_workspace(workspace_id, snapshot="pre-review")
|
||||
pyro.delete_workspace(workspace_id)
|
||||
```
|
||||
1. Create a named snapshot before the review starts.
|
||||
2. Open a readable PTY shell and inspect the checklist interactively.
|
||||
3. Run the review or evaluation script in the same workspace.
|
||||
4. Export the final report.
|
||||
5. Reset back to the snapshot if the review branch goes sideways.
|
||||
6. Delete the workspace when the evaluation is done.
|
||||
|
||||
This is the stable shell-facing story: readable PTY output for chat loops,
|
||||
checkpointed evaluation, explicit export, and reset when a review branch goes
|
||||
|
|
|
|||
|
|
@ -11,24 +11,13 @@ make smoke-untrusted-inspection
|
|||
Use this flow when the agent needs to inspect suspicious code or an unfamiliar
|
||||
repo without granting more capabilities than necessary.
|
||||
|
||||
Canonical SDK flow:
|
||||
Chat-host recipe:
|
||||
|
||||
```python
|
||||
from pyro_mcp import Pyro
|
||||
|
||||
pyro = Pyro()
|
||||
created = pyro.create_workspace(environment="debian:12", seed_path="./suspicious-repo")
|
||||
workspace_id = str(created["workspace_id"])
|
||||
|
||||
pyro.list_workspace_files(workspace_id, path="/workspace", recursive=True)
|
||||
pyro.read_workspace_file(workspace_id, "suspicious.sh")
|
||||
pyro.exec_workspace(
|
||||
workspace_id,
|
||||
command="sh -lc \"grep -n 'curl' suspicious.sh > inspection-report.txt\"",
|
||||
)
|
||||
pyro.export_workspace(workspace_id, "inspection-report.txt", output_path="./inspection-report.txt")
|
||||
pyro.delete_workspace(workspace_id)
|
||||
```
|
||||
1. Create one workspace from the suspicious repo seed.
|
||||
2. Inspect the tree with structured file listing and file reads.
|
||||
3. Run the smallest possible command that produces the inspection report.
|
||||
4. Export only the report the agent chose to materialize.
|
||||
5. Delete the workspace when inspection is complete.
|
||||
|
||||
This recipe stays offline-by-default, uses only explicit file reads and execs,
|
||||
and exports only the inspection report the agent chose to materialize.
|
||||
|
|
|
|||
135
docs/vision.md
135
docs/vision.md
|
|
@ -1,16 +1,19 @@
|
|||
# Vision
|
||||
|
||||
`pyro-mcp` should become the disposable sandbox where an agent can do real
|
||||
development work safely, repeatedly, and reproducibly.
|
||||
`pyro-mcp` should become the disposable MCP workspace for chat-based coding
|
||||
agents.
|
||||
|
||||
That is a different product from a generic VM wrapper, a secure CI runner, or a
|
||||
task queue with better isolation.
|
||||
That is a different product from a generic VM wrapper, a secure CI runner, or
|
||||
an SDK-first platform.
|
||||
|
||||
`pyro-mcp` currently has no users. That means we can still make breaking
|
||||
changes freely while we shape the chat-host path into the right product.
|
||||
|
||||
## Core Thesis
|
||||
|
||||
The goal is not just to run one command in a microVM.
|
||||
|
||||
The goal is to give an LLM or coding agent a bounded workspace where it can:
|
||||
The goal is to give a chat-hosted coding agent a bounded workspace where it can:
|
||||
|
||||
- inspect a repo
|
||||
- install dependencies
|
||||
|
|
@ -23,6 +26,25 @@ The goal is to give an LLM or coding agent a bounded workspace where it can:
|
|||
|
||||
The sandbox is the execution boundary for agentic software work.
|
||||
|
||||
## Current Product Focus
|
||||
|
||||
The product path should be obvious and narrow:
|
||||
|
||||
- Claude Code
|
||||
- Codex
|
||||
- OpenCode
|
||||
- Linux `x86_64` with KVM
|
||||
|
||||
The happy path is:
|
||||
|
||||
1. prove the host with the terminal companion commands
|
||||
2. run `pyro mcp serve`
|
||||
3. connect a chat host
|
||||
4. work through one disposable workspace per task
|
||||
|
||||
The repo can contain lower-level building blocks, but they should not drive the
|
||||
product story.
|
||||
|
||||
## What This Is Not
|
||||
|
||||
`pyro-mcp` should not drift into:
|
||||
|
|
@ -32,9 +54,10 @@ The sandbox is the execution boundary for agentic software work.
|
|||
- a generic CI job runner
|
||||
- a scheduler or queueing platform
|
||||
- a broad VM orchestration product
|
||||
- an SDK product that happens to have an MCP server on the side
|
||||
|
||||
Those products optimize for queued work, throughput, retries, matrix builds, and
|
||||
shared infrastructure.
|
||||
Those products optimize for queued work, throughput, retries, matrix builds, or
|
||||
library ergonomics.
|
||||
|
||||
`pyro-mcp` should optimize for agent loops:
|
||||
|
||||
|
|
@ -57,10 +80,15 @@ Any sandbox product starts to look like CI if the main abstraction is:
|
|||
That shape is useful, but it is not the center of the vision.
|
||||
|
||||
To stay aligned, the primary abstraction should be a workspace the agent
|
||||
inhabits, not a job the agent submits.
|
||||
inhabits from a chat host, not a job the agent submits to a runner.
|
||||
|
||||
## Product Principles
|
||||
|
||||
### Chat Hosts First
|
||||
|
||||
The product should be shaped around the MCP path used from chat interfaces.
|
||||
Everything else is there to support, debug, or build that path.
|
||||
|
||||
### Workspace-First
|
||||
|
||||
The default mental model should be "open a disposable workspace" rather than
|
||||
|
|
@ -85,11 +113,6 @@ Anything that crosses the host boundary should be intentional and visible:
|
|||
Agents should be able to checkpoint, reset, and retry cheaply. Disposable state
|
||||
is a feature, not a limitation.
|
||||
|
||||
### Same Contract Across Surfaces
|
||||
|
||||
CLI, Python, and MCP should expose the same underlying workspace model so the
|
||||
product feels coherent no matter how it is consumed.
|
||||
|
||||
### Agent-Native Observability
|
||||
|
||||
The sandbox should expose the things an agent actually needs to reason about:
|
||||
|
|
@ -101,10 +124,16 @@ The sandbox should expose the things an agent actually needs to reason about:
|
|||
- readiness
|
||||
- exported results
|
||||
|
||||
## The Shape Of An LLM-First Sandbox
|
||||
## The Shape Of The Product
|
||||
|
||||
The strongest future direction is a small, agent-native contract built around
|
||||
workspaces, shells, files, services, and reset.
|
||||
The strongest direction is a small chat-facing contract built around:
|
||||
|
||||
- one MCP server
|
||||
- one disposable workspace model
|
||||
- structured file inspection and edits
|
||||
- repeated commands in the same sandbox
|
||||
- service lifecycle when the workflow needs it
|
||||
- reset as a first-class workflow primitive
|
||||
|
||||
Representative primitives:
|
||||
|
||||
|
|
@ -114,95 +143,57 @@ Representative primitives:
|
|||
- `workspace.sync_push`
|
||||
- `workspace.export`
|
||||
- `workspace.diff`
|
||||
- `workspace.snapshot`
|
||||
- `workspace.reset`
|
||||
- `workspace.exec`
|
||||
- `shell.open`
|
||||
- `shell.read`
|
||||
- `shell.write`
|
||||
- `shell.signal`
|
||||
- `shell.close`
|
||||
- `workspace.exec`
|
||||
- `service.start`
|
||||
- `service.status`
|
||||
- `service.logs`
|
||||
- `service.stop`
|
||||
|
||||
These names are illustrative, not a committed public API.
|
||||
|
||||
The important point is the interaction model:
|
||||
|
||||
- a shell session is interactive state inside the sandbox
|
||||
- a workspace is durable for the life of the task
|
||||
- services are first-class, not accidental background jobs
|
||||
- reset is a core workflow primitive
|
||||
These names are illustrative, not a promise that every lower-level repo surface
|
||||
should be treated as equally stable or equally important.
|
||||
|
||||
## Interactive Shells And Disk Operations
|
||||
|
||||
Interactive shells are aligned with the vision because they make the agent feel
|
||||
present inside the sandbox rather than reduced to one-shot job submission.
|
||||
|
||||
That does not mean `pyro-mcp` should become a raw SSH replacement. The shell
|
||||
should sit inside a higher-level workspace model with structured file, service,
|
||||
diff, and reset operations around it.
|
||||
They should remain subordinate to the workspace model, not replace it with a
|
||||
raw SSH story.
|
||||
|
||||
Disk-level operations are also useful, but they should remain supporting tools.
|
||||
They are good for:
|
||||
Disk-level operations are useful for:
|
||||
|
||||
- fast workspace seeding
|
||||
- snapshotting
|
||||
- offline inspection
|
||||
- diffing
|
||||
- export/import without a full boot
|
||||
|
||||
They should not become the primary product identity. If the center of the
|
||||
product becomes "operate on VM disks", it will read as image tooling rather
|
||||
than an agent workspace.
|
||||
They should remain supporting tools rather than the product identity.
|
||||
|
||||
## What To Build Next
|
||||
|
||||
Features should be prioritized in this order:
|
||||
Features should keep reinforcing the chat-host path in this order:
|
||||
|
||||
1. Repeated commands in one persistent workspace
|
||||
2. Interactive shell sessions with PTY semantics
|
||||
3. Structured workspace sync and export
|
||||
4. Service lifecycle and readiness checks
|
||||
5. Snapshot and reset workflows
|
||||
6. Explicit secrets and network policy
|
||||
7. Secondary disk-level import/export and inspection tools
|
||||
1. make the first chat-host setup painfully obvious
|
||||
2. make the recipe-backed workflows feel trivial from chat
|
||||
3. keep the smoke pack trustworthy enough to gate the advertised stories
|
||||
4. keep the terminal companion path good enough to debug what the chat sees
|
||||
5. let lower-level repo surfaces move freely when the chat-host product needs it
|
||||
|
||||
The completed workspace GA roadmap lives in
|
||||
[roadmap/task-workspace-ga.md](roadmap/task-workspace-ga.md).
|
||||
|
||||
The next implementation milestones that make those workflows feel natural from
|
||||
chat-driven LLM interfaces live in
|
||||
The follow-on milestones that make the chat-host path clearer live in
|
||||
[roadmap/llm-chat-ergonomics.md](roadmap/llm-chat-ergonomics.md).
|
||||
|
||||
## Naming Guidance
|
||||
|
||||
Prefer language that reinforces the workspace model:
|
||||
|
||||
- `workspace`
|
||||
- `sandbox`
|
||||
- `shell`
|
||||
- `service`
|
||||
- `snapshot`
|
||||
- `reset`
|
||||
|
||||
Avoid centering language that makes the product feel like CI infrastructure:
|
||||
|
||||
- `job`
|
||||
- `runner`
|
||||
- `pipeline`
|
||||
- `worker`
|
||||
- `queue`
|
||||
- `build matrix`
|
||||
|
||||
## Litmus Test
|
||||
|
||||
When evaluating a new feature, ask:
|
||||
|
||||
"Does this help an agent inhabit a safe disposable workspace and do real
|
||||
software work inside it?"
|
||||
"Does this make Claude Code, Codex, or OpenCode feel more natural and powerful
|
||||
when they work inside a disposable sandbox?"
|
||||
|
||||
If the better description is "it helps submit, schedule, and report jobs", the
|
||||
feature is probably pushing the product in the wrong direction.
|
||||
If the better description is "it helps build a broader VM toolkit or SDK", it
|
||||
is probably pushing the product in the wrong direction.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[project]
|
||||
name = "pyro-mcp"
|
||||
version = "4.0.0"
|
||||
description = "Stable Firecracker workspaces, one-shot sandboxes, and MCP tools for coding agents."
|
||||
description = "Disposable MCP workspaces for chat-based coding agents on Linux KVM."
|
||||
readme = "README.md"
|
||||
license = { file = "LICENSE" }
|
||||
authors = [
|
||||
|
|
@ -9,7 +9,7 @@ authors = [
|
|||
]
|
||||
requires-python = ">=3.12"
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Development Status :: 4 - Beta",
|
||||
"Environment :: Console",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
|
|
|
|||
|
|
@ -639,18 +639,23 @@ class _HelpFormatter(
|
|||
def _build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
"Run stable one-shot and persistent workspace workflows on supported "
|
||||
"Linux x86_64 KVM hosts."
|
||||
"Validate the host and serve disposable MCP workspaces for chat-based "
|
||||
"coding agents on supported Linux x86_64 KVM hosts."
|
||||
),
|
||||
epilog=dedent(
|
||||
"""
|
||||
Suggested first run:
|
||||
Suggested zero-to-hero path:
|
||||
pyro doctor
|
||||
pyro env list
|
||||
pyro env pull debian:12
|
||||
pyro run debian:12 -- git --version
|
||||
pyro mcp serve
|
||||
|
||||
Continue into the stable workspace path after that:
|
||||
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
|
||||
|
|
@ -661,8 +666,6 @@ def _build_parser() -> argparse.ArgumentParser:
|
|||
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
|
||||
|
||||
Use `pyro mcp serve` only after the CLI validation path works.
|
||||
"""
|
||||
),
|
||||
formatter_class=_HelpFormatter,
|
||||
|
|
@ -760,8 +763,8 @@ def _build_parser() -> argparse.ArgumentParser:
|
|||
help="Run the MCP server.",
|
||||
description=(
|
||||
"Run the MCP server after you have already validated the host and "
|
||||
"guest execution with `pyro doctor` and `pyro run`. Bare `pyro "
|
||||
"mcp serve` now starts the recommended `workspace-core` profile."
|
||||
"guest execution with `pyro doctor` and `pyro run`. This is the "
|
||||
"main product path for Claude Code, Codex, and OpenCode."
|
||||
),
|
||||
epilog=dedent(
|
||||
"""
|
||||
|
|
@ -790,11 +793,11 @@ def _build_parser() -> argparse.ArgumentParser:
|
|||
Profiles:
|
||||
workspace-core: default for normal persistent chat editing
|
||||
vm-run: smallest one-shot-only surface
|
||||
workspace-full: advanced 4.x opt-in surface for shells, services,
|
||||
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 the full
|
||||
advanced workspace surface.
|
||||
Use --profile workspace-full only when the host truly needs those
|
||||
extra workspace capabilities.
|
||||
"""
|
||||
),
|
||||
formatter_class=_HelpFormatter,
|
||||
|
|
@ -806,7 +809,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|||
help=(
|
||||
"Expose only one model-facing tool profile. `workspace-core` is "
|
||||
"the default and recommended first profile for most chat hosts; "
|
||||
"`workspace-full` is the explicit advanced opt-in surface."
|
||||
"`workspace-full` is the larger opt-in profile."
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -888,7 +891,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|||
"workspace",
|
||||
help="Manage persistent workspaces.",
|
||||
description=(
|
||||
"Use the stable workspace contract when you need one sandbox to stay alive "
|
||||
"Use the workspace model when you need one sandbox to stay alive "
|
||||
"across repeated exec, shell, service, diff, export, snapshot, and reset calls."
|
||||
),
|
||||
epilog=dedent(
|
||||
|
|
@ -1818,7 +1821,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|||
pyro workspace service stop WORKSPACE_ID app
|
||||
|
||||
Use `--ready-file` by default in the curated Debian environments. `--ready-command`
|
||||
remains available as an escape hatch.
|
||||
remains available when the workflow needs a custom readiness check.
|
||||
"""
|
||||
),
|
||||
formatter_class=_HelpFormatter,
|
||||
|
|
|
|||
|
|
@ -26,17 +26,20 @@ def test_cli_help_guides_first_run() -> None:
|
|||
parser = cli._build_parser()
|
||||
help_text = parser.format_help()
|
||||
|
||||
assert "Suggested first run:" in help_text
|
||||
assert "Suggested zero-to-hero path:" in help_text
|
||||
assert "pyro doctor" in help_text
|
||||
assert "pyro env list" in help_text
|
||||
assert "pyro env pull debian:12" in help_text
|
||||
assert "pyro run debian:12 -- git --version" in help_text
|
||||
assert "Continue into the stable workspace path after that:" in help_text
|
||||
assert "pyro mcp serve" in help_text
|
||||
assert "Connect a chat host after that:" in help_text
|
||||
assert "claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve" in help_text
|
||||
assert "codex mcp add pyro -- uvx --from pyro-mcp pyro mcp serve" in help_text
|
||||
assert "If you want terminal-level visibility into the workspace model:" in help_text
|
||||
assert "pyro workspace exec WORKSPACE_ID -- cat note.txt" in help_text
|
||||
assert "pyro workspace snapshot create WORKSPACE_ID checkpoint" in help_text
|
||||
assert "pyro workspace reset WORKSPACE_ID --snapshot checkpoint" in help_text
|
||||
assert "pyro workspace sync push WORKSPACE_ID ./changes" in help_text
|
||||
assert "Use `pyro mcp serve` only after the CLI validation path works." in help_text
|
||||
|
||||
|
||||
def test_cli_subcommand_help_includes_examples_and_guidance() -> None:
|
||||
|
|
@ -69,10 +72,10 @@ def test_cli_subcommand_help_includes_examples_and_guidance() -> None:
|
|||
assert "vm-run" in mcp_help
|
||||
assert "recommended first profile for most chat hosts" in mcp_help
|
||||
assert "workspace-core: default for normal persistent chat editing" in mcp_help
|
||||
assert "workspace-full: advanced 4.x opt-in surface" in mcp_help
|
||||
assert "workspace-full: larger opt-in surface" in mcp_help
|
||||
|
||||
workspace_help = _subparser_choice(parser, "workspace").format_help()
|
||||
assert "stable workspace contract" in workspace_help
|
||||
assert "Use the workspace model when you need one sandbox to stay alive" in workspace_help
|
||||
assert "pyro workspace create debian:12 --seed-path ./repo" in workspace_help
|
||||
assert "--id-only" in workspace_help
|
||||
assert "pyro workspace create debian:12 --name repro-fix --label issue=123" in workspace_help
|
||||
|
|
@ -2822,9 +2825,9 @@ def test_chat_host_docs_and_examples_recommend_workspace_core() -> None:
|
|||
assert claude_cmd in readme
|
||||
assert codex_cmd in readme
|
||||
assert "examples/opencode_mcp_config.json" in readme
|
||||
assert "recommended first profile for normal persistent chat editing" in readme
|
||||
assert "Bare `pyro mcp serve` starts `workspace-core`" in readme
|
||||
|
||||
assert "## Chat Host Quickstart" in install
|
||||
assert "## 5. Connect a chat host" in install
|
||||
assert "uvx --from pyro-mcp pyro mcp serve" in install
|
||||
assert claude_cmd in install
|
||||
assert codex_cmd in install
|
||||
|
|
@ -2833,14 +2836,14 @@ def test_chat_host_docs_and_examples_recommend_workspace_core() -> None:
|
|||
assert claude_cmd in first_run
|
||||
assert codex_cmd in first_run
|
||||
|
||||
assert "Bare `pyro mcp serve` now starts `workspace-core`." in integrations
|
||||
assert (
|
||||
"Bare `pyro mcp serve` starts `workspace-core`. That is the product path."
|
||||
in integrations
|
||||
)
|
||||
assert "examples/claude_code_mcp.md" in integrations
|
||||
assert "examples/codex_mcp.md" in integrations
|
||||
assert "examples/opencode_mcp_config.json" in integrations
|
||||
assert (
|
||||
'`Pyro.create_server()` for most chat hosts now that `workspace-core` '
|
||||
"is the default profile" in integrations
|
||||
)
|
||||
assert "That is the product path." in integrations
|
||||
|
||||
assert "Default for most chat hosts in `4.x`: `workspace-core`." in mcp_config
|
||||
assert "Use the host-specific examples first when they apply:" in mcp_config
|
||||
|
|
@ -2882,7 +2885,7 @@ def test_content_only_read_docs_are_aligned() -> None:
|
|||
assert 'workspace file read "$WORKSPACE_ID" note.txt --content-only' in readme
|
||||
assert 'workspace file read "$WORKSPACE_ID" note.txt --content-only' in install
|
||||
assert 'workspace file read "$WORKSPACE_ID" note.txt --content-only' in first_run
|
||||
assert 'workspace disk read "$WORKSPACE_ID" note.txt --content-only' in first_run
|
||||
assert 'workspace patch apply "$WORKSPACE_ID" --patch-file fix.patch' in first_run
|
||||
|
||||
|
||||
def test_cli_workspace_shell_write_signal_close_json(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue