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
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue