# Public Contract This document defines the stable public interface for `pyro-mcp` `3.x`. ## 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` Stable product framing: - `pyro run` is the stable one-shot entrypoint. - `pyro workspace ...` is the stable persistent workspace contract. ## CLI Contract Top-level commands: - `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: - positional environment name - `--vcpu-count` - `--mem-mib` - `--timeout-seconds` - `--ttl-seconds` - `--network` - `--allow-host-compat` - `--json` Behavioral guarantees: - `pyro run -- ` 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. ## Python SDK Contract Primary facade: - `Pyro` Supported public entrypoints: - `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(...)` Stable public method names: - `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(...)` 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: - `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_sync_push` - `workspace_stop` - `workspace_start` - `workspace_exec` - `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_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: - `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. ## Versioning Rule - `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.