Replace the workspace-level boolean network toggle with explicit network policies and attach localhost TCP publication to workspace services. Persist network_policy in workspace records, validate --publish requests, and run host-side proxy helpers that follow the service lifecycle so published ports are cleaned up on failure, stop, reset, and delete. Update the CLI, SDK, MCP contract, docs, roadmap, and examples for the new policy model, add coverage for the proxy and manager edge cases, and validate with uv lock, UV_CACHE_DIR=.uv-cache make check, UV_CACHE_DIR=.uv-cache make dist-check, and a real guest-backed published-port probe smoke.
271 lines
14 KiB
Markdown
271 lines
14 KiB
Markdown
# Public Contract
|
|
|
|
This document defines the supported public interface for `pyro-mcp` `2.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`
|
|
|
|
## 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 sync push`
|
|
- `pyro workspace exec`
|
|
- `pyro workspace export`
|
|
- `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 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 <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 --network-policy {off,egress,egress+published-ports}` controls workspace guest networking and whether services may publish localhost ports.
|
|
- `pyro workspace create --secret NAME=VALUE` and `--secret-file NAME=PATH` persist guest-only UTF-8 secrets outside `/workspace`.
|
|
- `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 export WORKSPACE_ID PATH --output HOST_PATH` exports one file or directory from `/workspace` back to the host.
|
|
- `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 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 logs` returns persisted command history for that workspace until `pyro workspace delete`.
|
|
- Workspace create/status results expose `workspace_seed` metadata describing how `/workspace` was initialized.
|
|
- 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 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(..., network_policy="off", secrets=None)`
|
|
- `Pyro.push_workspace_sync(workspace_id, source_path, *, dest="/workspace")`
|
|
- `Pyro.export_workspace(workspace_id, path, *, output_path)`
|
|
- `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)`
|
|
- `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.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(..., network_policy="off", secrets=None)`
|
|
- `push_workspace_sync(workspace_id, source_path, *, dest="/workspace")`
|
|
- `export_workspace(workspace_id, path, *, output_path)`
|
|
- `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)`
|
|
- `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)`
|
|
- `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(..., 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.push_workspace_sync(...)` imports later host-side directory or archive content into a started workspace.
|
|
- `Pyro.export_workspace(...)` exports one file or directory from `/workspace` to an explicit host path.
|
|
- `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.
|
|
- `Pyro.write_shell(...)`, `Pyro.signal_shell(...)`, and `Pyro.close_shell(...)` operate on that persistent shell session.
|
|
|
|
## MCP Contract
|
|
|
|
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_sync_push`
|
|
- `workspace_exec`
|
|
- `workspace_export`
|
|
- `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_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 `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_sync_push` imports later host-side directory or archive content into a started workspace, with an optional `dest` under `/workspace`.
|
|
- `workspace_export` exports one file or directory from `/workspace` to an explicit host path.
|
|
- `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.
|
|
- `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.
|