Finish the 3.1.0 secondary disk-tools milestone so stable workspaces can be stopped, inspected offline, exported as raw ext4 images, and started again without changing the primary workspace-first interaction model. Add workspace stop/start plus workspace disk export/list/read across the CLI, SDK, and MCP, backed by a new offline debugfs inspection helper and guest-only validation. Scrub runtime-only guest state before disk inspection/export, and fix the real guest reliability gaps by flushing the filesystem on stop and removing stale Firecracker socket files before restart. Update the docs, examples, changelog, and roadmap to mark 3.1.0 done, and cover the new lifecycle/disk paths with API, CLI, manager, contract, and package-surface tests. Validation: uv lock; UV_CACHE_DIR=.uv-cache make check; UV_CACHE_DIR=.uv-cache make dist-check; real guest-backed smoke for create, shell/service activity, stop, workspace disk list/read/export, start, exec, and delete.
17 KiB
17 KiB
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 runis the stable one-shot entrypoint.pyro workspace ...is the stable persistent workspace contract.
CLI Contract
Top-level commands:
pyro env listpyro env pullpyro env inspectpyro env prunepyro mcp servepyro runpyro workspace createpyro workspace sync pushpyro workspace stoppyro workspace startpyro workspace execpyro workspace exportpyro workspace disk exportpyro workspace disk listpyro workspace disk readpyro workspace diffpyro workspace snapshot createpyro workspace snapshot listpyro workspace snapshot deletepyro workspace resetpyro workspace service startpyro workspace service listpyro workspace service statuspyro workspace service logspyro workspace service stoppyro workspace shell openpyro workspace shell readpyro workspace shell writepyro workspace shell signalpyro workspace shell closepyro workspace statuspyro workspace logspyro workspace deletepyro doctorpyro demopyro 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 to1 vCPU / 1024 MiB.pyro runfails if guest boot or guest exec is unavailable unless--allow-host-compatis set.pyro run,pyro env list,pyro env pull,pyro env inspect,pyro env prune, andpyro doctorare human-readable by default and return structured JSON with--json.pyro demo ollamaprints log lines plus a final summary line.pyro workspace createauto-starts a persistent workspace.pyro workspace create --seed-path PATHseeds/workspacefrom a host directory or a local.tar/.tar.gz/.tgzarchive 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=VALUEand--secret-file NAME=PATHpersist 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 stop WORKSPACE_IDstops one persistent workspace without deleting its/workspace, snapshots, or command history.pyro workspace start WORKSPACE_IDrestarts one stopped workspace without resetting/workspace.pyro workspace export WORKSPACE_ID PATH --output HOST_PATHexports one file or directory from/workspaceback to the host.pyro workspace disk export WORKSPACE_ID --output HOST_PATHcopies 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]reads one regular file from a stopped guest-backed workspace rootfs offline.pyro workspace disk *requiresstate=stoppedand a guest-backed workspace; it fails onhost_compat.pyro workspace diff WORKSPACE_IDcompares the current/workspacetree to the immutable create-time baseline.pyro workspace snapshot *manages explicit named snapshots in addition to the implicitbaseline.pyro workspace reset WORKSPACE_ID [--snapshot SNAPSHOT_NAME|baseline]recreates the full sandbox and restores/workspacefrom 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_PORTor--publish HOST_PORT:GUEST_PORTpublishes one guest TCP port to127.0.0.1on 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 execruns in the persistent/workspacefor 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 logsreturns persisted command history for that workspace untilpyro workspace delete.- Workspace create/status results expose
workspace_seedmetadata describing how/workspacewas initialized. - Workspace create/status/reset results expose
network_policy. - Workspace create/status/reset results expose
reset_countandlast_reset_at. - Workspace create/status/reset results expose safe
secretsmetadata with each secret name and source kind, but never the secret values. pyro workspace statusincludes aggregateservice_countandrunning_service_countfields.pyro workspace service start,pyro workspace service list, andpyro workspace service statusexpose 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.stop_workspace(workspace_id)Pyro.start_workspace(workspace_id)Pyro.export_workspace(workspace_id, path, *, output_path)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)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")stop_workspace(workspace_id)start_workspace(workspace_id)export_workspace(workspace_id, path, *, output_path)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)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(...)andPyro.run_in_vm(...)default tovcpu_count=1andmem_mib=1024.Pyro.create_workspace(...)defaults tovcpu_count=1andmem_mib=1024.allow_host_compatdefaults toFalseoncreate_vm(...)andrun_in_vm(...).allow_host_compatdefaults toFalseoncreate_workspace(...).Pyro.create_workspace(..., seed_path=...)seeds/workspacefrom a host directory or a local.tar/.tar.gz/.tgzarchive 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.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.export_workspace(...)exports one file or directory from/workspaceto an explicit host path.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=stoppedand a guest-backed workspace; they fail onhost_compat. Pyro.diff_workspace(...)compares the current/workspacetree to the immutable create-time baseline.Pyro.create_snapshot(...)captures one named/workspacecheckpoint.Pyro.list_snapshots(...)lists the implicitbaselineplus any named snapshots.Pyro.delete_snapshot(...)deletes one named snapshot while leavingbaselineintact.Pyro.reset_workspace(...)recreates the full sandbox frombaselineor 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 to127.0.0.1on the host when the workspace network policy isegress+published-ports.Pyro.list_services(...),Pyro.status_service(...),Pyro.logs_service(...), andPyro.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(...), andPyro.close_shell(...)operate on that persistent shell session.
MCP Contract
Primary tool:
vm_run
Advanced lifecycle tools:
vm_list_environmentsvm_createvm_startvm_execvm_stopvm_deletevm_statusvm_network_infovm_reap_expired
Persistent workspace tools:
workspace_createworkspace_sync_pushworkspace_stopworkspace_startworkspace_execworkspace_exportworkspace_disk_exportworkspace_disk_listworkspace_disk_readworkspace_diffsnapshot_createsnapshot_listsnapshot_deleteworkspace_resetservice_startservice_listservice_statusservice_logsservice_stopshell_openshell_readshell_writeshell_signalshell_closeworkspace_statusworkspace_logsworkspace_delete
Behavioral defaults:
vm_runandvm_createdefault tovcpu_count=1andmem_mib=1024.workspace_createdefaults tovcpu_count=1andmem_mib=1024.vm_runandvm_createexposeallow_host_compat, which defaults tofalse.workspace_createexposesallow_host_compat, which defaults tofalse.workspace_createaccepts optionalseed_pathand seeds/workspacefrom a host directory or a local.tar/.tar.gz/.tgzarchive before the workspace is returned.workspace_createacceptsnetwork_policywithoff,egress, oregress+published-portsto control workspace guest networking and service port publication.workspace_createaccepts optionalsecretsand persists guest-only UTF-8 secret material outside/workspace.workspace_sync_pushimports later host-side directory or archive content into a started workspace, with an optionaldestunder/workspace.workspace_stopstops one persistent workspace without deleting its/workspace, snapshots, or command history.workspace_startrestarts one stopped workspace without resetting/workspace.workspace_exportexports one file or directory from/workspaceto an explicit host path.workspace_disk_exportcopies the stopped guest-backed workspace rootfs as raw ext4 to an explicit host path.workspace_disk_listinspects a stopped guest-backed workspace rootfs offline without booting the guest.workspace_disk_readreads one regular file from a stopped guest-backed workspace rootfs offline.- stopped-workspace disk tools require
state=stoppedand a guest-backed workspace; they fail onhost_compat. workspace_diffcompares the current/workspacetree to the immutable create-time baseline.snapshot_create,snapshot_list, andsnapshot_deletemanage explicit named snapshots in addition to the implicitbaseline.workspace_resetrecreates the full sandbox and restores/workspacefrombaselineor one named snapshot.service_start,service_list,service_status,service_logs, andservice_stopmanage persistent named services inside a started workspace.service_startaccepts optionalpublished_portsto expose guest TCP ports on127.0.0.1when the workspace network policy isegress+published-ports.vm_execruns one command and auto-cleans that VM after the exec completes.workspace_execaccepts optionalsecret_envmappings for one exec call and leaves the workspace alive.service_startaccepts optionalsecret_envmappings for one service start call.shell_openaccepts optionalsecret_envmappings for the opened shell session.shell_open,shell_read,shell_write,shell_signal, andshell_closemanage persistent PTY shells inside a started workspace.
Versioning Rule
pyro-mcpuses 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.