Refresh docs and examples for workspaces

Rewrite the user-facing persistent sandbox story around pyro workspace ..., including the install guide, first-run transcript, integrations notes, and public contract reference.

Rename the Python example to examples/python_workspace.py and update the docs to use the new workspace create, sync, exec, status, logs, and delete flows with seed_path/workspace_id terminology.

Mark the 2.4.0 workspace-contract pivot as done in the roadmap now that the shipped CLI, SDK, MCP, docs, and tests all use the workspace-first surface.
This commit is contained in:
Thales Maciel 2026-03-12 01:22:26 -03:00
parent 48b82d8386
commit 2de31306b6
9 changed files with 148 additions and 150 deletions

View file

@ -1,6 +1,6 @@
# pyro-mcp # pyro-mcp
`pyro-mcp` runs one-shot commands and repeated task workspaces inside ephemeral Firecracker microVMs using curated Linux environments such as `debian:12`. `pyro-mcp` runs one-shot commands and repeated workspaces inside ephemeral Firecracker microVMs using curated Linux environments such as `debian:12`.
[![PyPI version](https://img.shields.io/pypi/v/pyro-mcp.svg)](https://pypi.org/project/pyro-mcp/) [![PyPI version](https://img.shields.io/pypi/v/pyro-mcp.svg)](https://pypi.org/project/pyro-mcp/)
@ -16,10 +16,11 @@ It exposes the same runtime in three public forms:
- Install: [docs/install.md](docs/install.md) - Install: [docs/install.md](docs/install.md)
- Vision: [docs/vision.md](docs/vision.md) - Vision: [docs/vision.md](docs/vision.md)
- Workspace roadmap: [docs/roadmap/task-workspace-ga.md](docs/roadmap/task-workspace-ga.md)
- First run transcript: [docs/first-run.md](docs/first-run.md) - First run transcript: [docs/first-run.md](docs/first-run.md)
- Terminal walkthrough GIF: [docs/assets/first-run.gif](docs/assets/first-run.gif) - Terminal walkthrough GIF: [docs/assets/first-run.gif](docs/assets/first-run.gif)
- PyPI package: [pypi.org/project/pyro-mcp](https://pypi.org/project/pyro-mcp/) - PyPI package: [pypi.org/project/pyro-mcp](https://pypi.org/project/pyro-mcp/)
- What's new in 2.3.0: [CHANGELOG.md#230](CHANGELOG.md#230) - What's new in 2.4.0: [CHANGELOG.md#240](CHANGELOG.md#240)
- Host requirements: [docs/host-requirements.md](docs/host-requirements.md) - Host requirements: [docs/host-requirements.md](docs/host-requirements.md)
- Integration targets: [docs/integrations.md](docs/integrations.md) - Integration targets: [docs/integrations.md](docs/integrations.md)
- Public contract: [docs/public-contract.md](docs/public-contract.md) - Public contract: [docs/public-contract.md](docs/public-contract.md)
@ -56,7 +57,7 @@ What success looks like:
```bash ```bash
Platform: linux-x86_64 Platform: linux-x86_64
Runtime: PASS Runtime: PASS
Catalog version: 2.3.0 Catalog version: 2.4.0
... ...
[pull] phase=install environment=debian:12 [pull] phase=install environment=debian:12
[pull] phase=ready environment=debian:12 [pull] phase=ready environment=debian:12
@ -75,8 +76,8 @@ access to `registry-1.docker.io`, and needs local cache space for the guest imag
After the quickstart works: After the quickstart works:
- prove the full one-shot lifecycle with `uvx --from pyro-mcp pyro demo` - prove the full one-shot lifecycle with `uvx --from pyro-mcp pyro demo`
- create a persistent workspace with `uvx --from pyro-mcp pyro task create debian:12 --source-path ./repo` - create a persistent workspace with `uvx --from pyro-mcp pyro workspace create debian:12 --seed-path ./repo`
- update a live task from the host with `uvx --from pyro-mcp pyro task sync push TASK_ID ./changes` - update a live workspace from the host with `uvx --from pyro-mcp pyro workspace sync push WORKSPACE_ID ./changes`
- move to Python or MCP via [docs/integrations.md](docs/integrations.md) - move to Python or MCP via [docs/integrations.md](docs/integrations.md)
## Supported Hosts ## Supported Hosts
@ -130,7 +131,7 @@ uvx --from pyro-mcp pyro env list
Expected output: Expected output:
```bash ```bash
Catalog version: 2.2.0 Catalog version: 2.4.0
debian:12 [installed|not installed] Debian 12 environment with Git preinstalled for common agent workflows. debian:12 [installed|not installed] Debian 12 environment with Git preinstalled for common agent workflows.
debian:12-base [installed|not installed] Minimal Debian 12 environment for shell and core Unix tooling. debian:12-base [installed|not installed] Minimal Debian 12 environment for shell and core Unix tooling.
debian:12-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled. debian:12-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled.
@ -194,36 +195,36 @@ When you are done evaluating and want to remove stale cached environments, run `
If you prefer a fuller copy-pasteable transcript, see [docs/first-run.md](docs/first-run.md). If you prefer a fuller copy-pasteable transcript, see [docs/first-run.md](docs/first-run.md).
The walkthrough GIF above was rendered from [docs/assets/first-run.tape](docs/assets/first-run.tape) using [scripts/render_tape.sh](scripts/render_tape.sh). The walkthrough GIF above was rendered from [docs/assets/first-run.tape](docs/assets/first-run.tape) using [scripts/render_tape.sh](scripts/render_tape.sh).
## Persistent Tasks ## Persistent Workspaces
Use `pyro run` for one-shot commands. Use `pyro task ...` when you need repeated commands in one Use `pyro run` for one-shot commands. Use `pyro workspace ...` when you need repeated commands in one
workspace without recreating the sandbox every time. workspace without recreating the sandbox every time.
The project direction is an agent workspace, not a CI job runner. Persistent The project direction is an agent workspace, not a CI job runner. Persistent
tasks are meant to let an agent stay inside one bounded sandbox across multiple workspaces are meant to let an agent stay inside one bounded sandbox across multiple
steps. See [docs/vision.md](docs/vision.md) for the product thesis and the steps. See [docs/vision.md](docs/vision.md) for the product thesis and the
longer-term interaction model. longer-term interaction model.
```bash ```bash
pyro task create debian:12 --source-path ./repo pyro workspace create debian:12 --seed-path ./repo
pyro task sync push TASK_ID ./changes --dest src pyro workspace sync push WORKSPACE_ID ./changes --dest src
pyro task exec TASK_ID -- cat src/note.txt pyro workspace exec WORKSPACE_ID -- cat src/note.txt
pyro task logs TASK_ID pyro workspace logs WORKSPACE_ID
pyro task delete TASK_ID pyro workspace delete WORKSPACE_ID
``` ```
Task workspaces start in `/workspace` and keep command history until you delete them. For machine Persistent workspaces start in `/workspace` and keep command history until you delete them. For
consumption, add `--json` and read the returned `task_id`. Use `--source-path` when you want the machine consumption, add `--json` and read the returned `workspace_id`. Use `--seed-path` when
task to start from a host directory or a local `.tar` / `.tar.gz` / `.tgz` archive instead of an you want the workspace to start from a host directory or a local `.tar` / `.tar.gz` / `.tgz`
empty workspace. Use `pyro task sync push` when you want to import later host-side changes into a archive instead of an empty workspace. Use `pyro workspace sync push` when you want to import
started task. Sync is non-atomic in `2.3.0`; if it fails partway through, delete and recreate the later host-side changes into a started workspace. Sync is non-atomic in `2.4.0`; if it fails
task from its seed. partway through, delete and recreate the workspace from its seed.
## Public Interfaces ## Public Interfaces
The public user-facing interface is `pyro` and `Pyro`. After the CLI validation path works, you can choose one of three surfaces: The public user-facing interface is `pyro` and `Pyro`. After the CLI validation path works, you can choose one of three surfaces:
- `pyro` for direct CLI usage, including one-shot `run` and persistent `task` workflows - `pyro` for direct CLI usage, including one-shot `run` and persistent `workspace` workflows
- `from pyro_mcp import Pyro` for Python orchestration - `from pyro_mcp import Pyro` for Python orchestration
- `pyro mcp serve` for MCP clients - `pyro mcp serve` for MCP clients
@ -359,14 +360,14 @@ For repeated commands in one workspace:
from pyro_mcp import Pyro from pyro_mcp import Pyro
pyro = Pyro() pyro = Pyro()
task = pyro.create_task(environment="debian:12", source_path="./repo") workspace = pyro.create_workspace(environment="debian:12", seed_path="./repo")
task_id = task["task_id"] workspace_id = workspace["workspace_id"]
try: try:
pyro.push_task_sync(task_id, "./changes", dest="src") pyro.push_workspace_sync(workspace_id, "./changes", dest="src")
result = pyro.exec_task(task_id, command="cat src/note.txt") result = pyro.exec_workspace(workspace_id, command="cat src/note.txt")
print(result["stdout"], end="") print(result["stdout"], end="")
finally: finally:
pyro.delete_task(task_id) pyro.delete_workspace(workspace_id)
``` ```
## MCP Tools ## MCP Tools
@ -389,18 +390,18 @@ Advanced lifecycle tools:
Persistent workspace tools: Persistent workspace tools:
- `task_create(environment, vcpu_count=1, mem_mib=1024, ttl_seconds=600, network=false, allow_host_compat=false, source_path=null)` - `workspace_create(environment, vcpu_count=1, mem_mib=1024, ttl_seconds=600, network=false, allow_host_compat=false, seed_path=null)`
- `task_sync_push(task_id, source_path, dest="/workspace")` - `workspace_sync_push(workspace_id, source_path, dest="/workspace")`
- `task_exec(task_id, command, timeout_seconds=30)` - `workspace_exec(workspace_id, command, timeout_seconds=30)`
- `task_status(task_id)` - `workspace_status(workspace_id)`
- `task_logs(task_id)` - `workspace_logs(workspace_id)`
- `task_delete(task_id)` - `workspace_delete(workspace_id)`
## Integration Examples ## Integration Examples
- Python one-shot SDK example: [examples/python_run.py](examples/python_run.py) - Python one-shot SDK example: [examples/python_run.py](examples/python_run.py)
- Python lifecycle example: [examples/python_lifecycle.py](examples/python_lifecycle.py) - Python lifecycle example: [examples/python_lifecycle.py](examples/python_lifecycle.py)
- Python task workspace example: [examples/python_task.py](examples/python_task.py) - Python workspace example: [examples/python_workspace.py](examples/python_workspace.py)
- MCP client config example: [examples/mcp_client_config.md](examples/mcp_client_config.md) - MCP client config example: [examples/mcp_client_config.md](examples/mcp_client_config.md)
- Claude Desktop MCP config: [examples/claude_desktop_mcp_config.json](examples/claude_desktop_mcp_config.json) - Claude Desktop MCP config: [examples/claude_desktop_mcp_config.json](examples/claude_desktop_mcp_config.json)
- Cursor MCP config: [examples/cursor_mcp_config.json](examples/cursor_mcp_config.json) - Cursor MCP config: [examples/cursor_mcp_config.json](examples/cursor_mcp_config.json)

View file

@ -22,7 +22,7 @@ Networking: tun=yes ip_forward=yes
```bash ```bash
$ uvx --from pyro-mcp pyro env list $ uvx --from pyro-mcp pyro env list
Catalog version: 2.3.0 Catalog version: 2.4.0
debian:12 [installed|not installed] Debian 12 environment with Git preinstalled for common agent workflows. debian:12 [installed|not installed] Debian 12 environment with Git preinstalled for common agent workflows.
debian:12-base [installed|not installed] Minimal Debian 12 environment for shell and core Unix tooling. debian:12-base [installed|not installed] Minimal Debian 12 environment for shell and core Unix tooling.
debian:12-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled. debian:12-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled.
@ -70,18 +70,18 @@ deterministic structured result.
```bash ```bash
$ uvx --from pyro-mcp pyro demo $ uvx --from pyro-mcp pyro demo
$ uvx --from pyro-mcp pyro task create debian:12 --source-path ./repo $ uvx --from pyro-mcp pyro workspace create debian:12 --seed-path ./repo
$ uvx --from pyro-mcp pyro task sync push TASK_ID ./changes $ uvx --from pyro-mcp pyro workspace sync push WORKSPACE_ID ./changes
$ uvx --from pyro-mcp pyro mcp serve $ uvx --from pyro-mcp pyro mcp serve
``` ```
`pyro demo` proves the one-shot create/start/exec/delete VM lifecycle works end to end. `pyro demo` proves the one-shot create/start/exec/delete VM lifecycle works end to end.
When you need repeated commands in one sandbox, switch to `pyro task ...`: When you need repeated commands in one sandbox, switch to `pyro workspace ...`:
```bash ```bash
$ uvx --from pyro-mcp pyro task create debian:12 --source-path ./repo $ uvx --from pyro-mcp pyro workspace create debian:12 --seed-path ./repo
Task: ... Workspace ID: ...
Environment: debian:12 Environment: debian:12
State: started State: started
Workspace: /workspace Workspace: /workspace
@ -90,18 +90,19 @@ Execution mode: guest_vsock
Resources: 1 vCPU / 1024 MiB Resources: 1 vCPU / 1024 MiB
Command count: 0 Command count: 0
$ uvx --from pyro-mcp pyro task sync push TASK_ID ./changes --dest src $ uvx --from pyro-mcp pyro workspace sync push WORKSPACE_ID ./changes --dest src
[task-sync] task_id=... mode=directory source=... destination=/workspace/src entry_count=... bytes_written=... execution_mode=guest_vsock [workspace-sync] workspace_id=... mode=directory source=... destination=/workspace/src entry_count=... bytes_written=... execution_mode=guest_vsock
$ uvx --from pyro-mcp pyro task exec TASK_ID -- cat src/note.txt $ uvx --from pyro-mcp pyro workspace exec WORKSPACE_ID -- cat src/note.txt
hello from synced task hello from synced workspace
[task-exec] task_id=... sequence=1 cwd=/workspace execution_mode=guest_vsock exit_code=0 duration_ms=... [workspace-exec] workspace_id=... sequence=1 cwd=/workspace execution_mode=guest_vsock exit_code=0 duration_ms=...
``` ```
Use `--source-path` when the task should start from a host directory or a local Use `--seed-path` when the workspace should start from a host directory or a local
`.tar` / `.tar.gz` / `.tgz` archive instead of an empty `/workspace`. Use `.tar` / `.tar.gz` / `.tgz` archive instead of an empty `/workspace`. Use
`pyro task sync push` when you need to import later host-side changes into a started task. `pyro workspace sync push` when you need to import later host-side changes into a started
Sync is non-atomic in `2.3.0`; if it fails partway through, delete and recreate the task. workspace. Sync is non-atomic in `2.4.0`; if it fails partway through, delete and recreate the
workspace.
Example output: Example output:

View file

@ -83,7 +83,7 @@ uvx --from pyro-mcp pyro env list
Expected output: Expected output:
```bash ```bash
Catalog version: 2.3.0 Catalog version: 2.4.0
debian:12 [installed|not installed] Debian 12 environment with Git preinstalled for common agent workflows. debian:12 [installed|not installed] Debian 12 environment with Git preinstalled for common agent workflows.
debian:12-base [installed|not installed] Minimal Debian 12 environment for shell and core Unix tooling. debian:12-base [installed|not installed] Minimal Debian 12 environment for shell and core Unix tooling.
debian:12-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled. debian:12-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled.
@ -174,29 +174,30 @@ pyro run debian:12 -- git --version
After the CLI path works, you can move on to: After the CLI path works, you can move on to:
- persistent workspaces: `pyro task create debian:12 --source-path ./repo` - persistent workspaces: `pyro workspace create debian:12 --seed-path ./repo`
- live task updates: `pyro task sync push TASK_ID ./changes` - live workspace updates: `pyro workspace sync push WORKSPACE_ID ./changes`
- MCP: `pyro mcp serve` - MCP: `pyro mcp serve`
- Python SDK: `from pyro_mcp import Pyro` - Python SDK: `from pyro_mcp import Pyro`
- Demos: `pyro demo` or `pyro demo --network` - Demos: `pyro demo` or `pyro demo --network`
## Persistent Task Workspace ## Persistent Workspace
Use `pyro task ...` when you need repeated commands in one sandbox instead of one-shot `pyro run`. Use `pyro workspace ...` when you need repeated commands in one sandbox instead of one-shot `pyro run`.
```bash ```bash
pyro task create debian:12 --source-path ./repo pyro workspace create debian:12 --seed-path ./repo
pyro task sync push TASK_ID ./changes --dest src pyro workspace sync push WORKSPACE_ID ./changes --dest src
pyro task exec TASK_ID -- cat src/note.txt pyro workspace exec WORKSPACE_ID -- cat src/note.txt
pyro task logs TASK_ID pyro workspace logs WORKSPACE_ID
pyro task delete TASK_ID pyro workspace delete WORKSPACE_ID
``` ```
Task commands default to the persistent `/workspace` directory inside the guest. If you need the Workspace commands default to the persistent `/workspace` directory inside the guest. If you need
task identifier programmatically, use `--json` and read the `task_id` field. Use `--source-path` the identifier programmatically, use `--json` and read the `workspace_id` field. Use `--seed-path`
when the task should start from a host directory or a local `.tar` / `.tar.gz` / `.tgz` archive. when the workspace should start from a host directory or a local `.tar` / `.tar.gz` / `.tgz`
Use `pyro task sync push` for later host-side changes to a started task. Sync is non-atomic in archive. Use `pyro workspace sync push` for later host-side changes to a started workspace. Sync
`2.3.0`; if it fails partway through, delete and recreate the task from its seed. is non-atomic in `2.4.0`; if it fails partway through, delete and recreate the workspace from its
seed.
## Contributor Clone ## Contributor Clone

View file

@ -16,7 +16,7 @@ That keeps the model-facing contract small:
- one ephemeral VM - one ephemeral VM
- automatic cleanup - automatic cleanup
Move to `task_*` only when the agent truly needs repeated commands in one workspace across Move to `workspace_*` only when the agent truly needs repeated commands in one workspace across
multiple calls. multiple calls.
## OpenAI Responses API ## OpenAI Responses API
@ -30,7 +30,7 @@ Best when:
Recommended surface: Recommended surface:
- `vm_run` - `vm_run`
- `task_create(source_path=...)` + `task_sync_push` + `task_exec` when the agent needs persistent workspace state - `workspace_create(seed_path=...)` + `workspace_sync_push` + `workspace_exec` when the agent needs persistent workspace state
Canonical example: Canonical example:
@ -65,23 +65,23 @@ Best when:
Recommended default: Recommended default:
- `Pyro.run_in_vm(...)` - `Pyro.run_in_vm(...)`
- `Pyro.create_task(source_path=...)` + `Pyro.push_task_sync(...)` + `Pyro.exec_task(...)` when repeated workspace commands are required - `Pyro.create_workspace(seed_path=...)` + `Pyro.push_workspace_sync(...)` + `Pyro.exec_workspace(...)` when repeated workspace commands are required
Lifecycle note: Lifecycle note:
- `Pyro.exec_vm(...)` runs one command and auto-cleans the VM afterward - `Pyro.exec_vm(...)` runs one command and auto-cleans the VM afterward
- use `create_vm(...)` + `start_vm(...)` only when you need pre-exec inspection or status before - use `create_vm(...)` + `start_vm(...)` only when you need pre-exec inspection or status before
that final exec that final exec
- use `create_task(source_path=...)` when the agent needs repeated commands in one persistent - use `create_workspace(seed_path=...)` when the agent needs repeated commands in one persistent
`/workspace` that starts from host content `/workspace` that starts from host content
- use `push_task_sync(...)` when later host-side changes need to be imported into that running - use `push_workspace_sync(...)` when later host-side changes need to be imported into that
workspace without recreating the task running workspace without recreating it
Examples: Examples:
- [examples/python_run.py](../examples/python_run.py) - [examples/python_run.py](../examples/python_run.py)
- [examples/python_lifecycle.py](../examples/python_lifecycle.py) - [examples/python_lifecycle.py](../examples/python_lifecycle.py)
- [examples/python_task.py](../examples/python_task.py) - [examples/python_workspace.py](../examples/python_workspace.py)
## Agent Framework Wrappers ## Agent Framework Wrappers
@ -100,7 +100,7 @@ Recommended pattern:
- keep the framework wrapper thin - keep the framework wrapper thin
- map one-shot framework tool input directly onto `vm_run` - map one-shot framework tool input directly onto `vm_run`
- expose `task_*` only when the framework truly needs repeated commands in one workspace - expose `workspace_*` only when the framework truly needs repeated commands in one workspace
Concrete example: Concrete example:

View file

@ -19,12 +19,12 @@ Top-level commands:
- `pyro env prune` - `pyro env prune`
- `pyro mcp serve` - `pyro mcp serve`
- `pyro run` - `pyro run`
- `pyro task create` - `pyro workspace create`
- `pyro task sync push` - `pyro workspace sync push`
- `pyro task exec` - `pyro workspace exec`
- `pyro task status` - `pyro workspace status`
- `pyro task logs` - `pyro workspace logs`
- `pyro task delete` - `pyro workspace delete`
- `pyro doctor` - `pyro doctor`
- `pyro demo` - `pyro demo`
- `pyro demo ollama` - `pyro demo ollama`
@ -46,15 +46,12 @@ Behavioral guarantees:
- `pyro run` fails if guest boot or guest exec is unavailable unless `--allow-host-compat` is set. - `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 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 demo ollama` prints log lines plus a final summary line.
- `pyro task create` auto-starts a persistent workspace. - `pyro workspace create` auto-starts a persistent workspace.
- `pyro task create --source-path PATH` seeds `/workspace` from a host directory or a local - `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.
`.tar` / `.tar.gz` / `.tgz` archive before the task is returned. - `pyro workspace sync push WORKSPACE_ID SOURCE_PATH [--dest WORKSPACE_PATH]` imports later host-side directory or archive content into a started workspace.
- `pyro task sync push TASK_ID SOURCE_PATH [--dest WORKSPACE_PATH]` imports later host-side - `pyro workspace exec` runs in the persistent `/workspace` for that workspace and does not auto-clean.
directory or archive content into a started task workspace. - `pyro workspace logs` returns persisted command history for that workspace until `pyro workspace delete`.
- `pyro task exec` runs in the persistent `/workspace` for that task and does not auto-clean. - Workspace create/status results expose `workspace_seed` metadata describing how `/workspace` was initialized.
- `pyro task logs` returns persisted command history for that task until `pyro task delete`.
- Task create/status results expose `workspace_seed` metadata describing how `/workspace` was
initialized.
## Python SDK Contract ## Python SDK Contract
@ -71,17 +68,17 @@ Supported public entrypoints:
- `Pyro.inspect_environment(environment)` - `Pyro.inspect_environment(environment)`
- `Pyro.prune_environments()` - `Pyro.prune_environments()`
- `Pyro.create_vm(...)` - `Pyro.create_vm(...)`
- `Pyro.create_task(...)` - `Pyro.create_workspace(...)`
- `Pyro.push_task_sync(task_id, source_path, *, dest="/workspace")` - `Pyro.push_workspace_sync(workspace_id, source_path, *, dest="/workspace")`
- `Pyro.start_vm(vm_id)` - `Pyro.start_vm(vm_id)`
- `Pyro.exec_vm(vm_id, *, command, timeout_seconds=30)` - `Pyro.exec_vm(vm_id, *, command, timeout_seconds=30)`
- `Pyro.exec_task(task_id, *, command, timeout_seconds=30)` - `Pyro.exec_workspace(workspace_id, *, command, timeout_seconds=30)`
- `Pyro.stop_vm(vm_id)` - `Pyro.stop_vm(vm_id)`
- `Pyro.delete_vm(vm_id)` - `Pyro.delete_vm(vm_id)`
- `Pyro.delete_task(task_id)` - `Pyro.delete_workspace(workspace_id)`
- `Pyro.status_vm(vm_id)` - `Pyro.status_vm(vm_id)`
- `Pyro.status_task(task_id)` - `Pyro.status_workspace(workspace_id)`
- `Pyro.logs_task(task_id)` - `Pyro.logs_workspace(workspace_id)`
- `Pyro.network_info_vm(vm_id)` - `Pyro.network_info_vm(vm_id)`
- `Pyro.reap_expired()` - `Pyro.reap_expired()`
- `Pyro.run_in_vm(...)` - `Pyro.run_in_vm(...)`
@ -94,17 +91,17 @@ Stable public method names:
- `inspect_environment(environment)` - `inspect_environment(environment)`
- `prune_environments()` - `prune_environments()`
- `create_vm(...)` - `create_vm(...)`
- `create_task(...)` - `create_workspace(...)`
- `push_task_sync(task_id, source_path, *, dest="/workspace")` - `push_workspace_sync(workspace_id, source_path, *, dest="/workspace")`
- `start_vm(vm_id)` - `start_vm(vm_id)`
- `exec_vm(vm_id, *, command, timeout_seconds=30)` - `exec_vm(vm_id, *, command, timeout_seconds=30)`
- `exec_task(task_id, *, command, timeout_seconds=30)` - `exec_workspace(workspace_id, *, command, timeout_seconds=30)`
- `stop_vm(vm_id)` - `stop_vm(vm_id)`
- `delete_vm(vm_id)` - `delete_vm(vm_id)`
- `delete_task(task_id)` - `delete_workspace(workspace_id)`
- `status_vm(vm_id)` - `status_vm(vm_id)`
- `status_task(task_id)` - `status_workspace(workspace_id)`
- `logs_task(task_id)` - `logs_workspace(workspace_id)`
- `network_info_vm(vm_id)` - `network_info_vm(vm_id)`
- `reap_expired()` - `reap_expired()`
- `run_in_vm(...)` - `run_in_vm(...)`
@ -112,15 +109,13 @@ Stable public method names:
Behavioral defaults: Behavioral defaults:
- `Pyro.create_vm(...)` and `Pyro.run_in_vm(...)` default to `vcpu_count=1` and `mem_mib=1024`. - `Pyro.create_vm(...)` and `Pyro.run_in_vm(...)` default to `vcpu_count=1` and `mem_mib=1024`.
- `Pyro.create_task(...)` defaults 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_vm(...)` and `run_in_vm(...)`.
- `allow_host_compat` defaults to `False` on `create_task(...)`. - `allow_host_compat` defaults to `False` on `create_workspace(...)`.
- `Pyro.create_task(..., source_path=...)` seeds `/workspace` from a host directory or a local - `Pyro.create_workspace(..., seed_path=...)` seeds `/workspace` from a host directory or a local `.tar` / `.tar.gz` / `.tgz` archive before the workspace is returned.
`.tar` / `.tar.gz` / `.tgz` archive before the task is returned. - `Pyro.push_workspace_sync(...)` imports later host-side directory or archive content into a started workspace.
- `Pyro.push_task_sync(...)` imports later host-side directory or archive content into a started
task workspace.
- `Pyro.exec_vm(...)` runs one command and auto-cleans that VM after the exec completes. - `Pyro.exec_vm(...)` runs one command and auto-cleans that VM after the exec completes.
- `Pyro.exec_task(...)` runs one command in the persistent task workspace and leaves the task alive. - `Pyro.exec_workspace(...)` runs one command in the persistent workspace and leaves it alive.
## MCP Contract ## MCP Contract
@ -140,27 +135,25 @@ Advanced lifecycle tools:
- `vm_network_info` - `vm_network_info`
- `vm_reap_expired` - `vm_reap_expired`
Task workspace tools: Persistent workspace tools:
- `task_create` - `workspace_create`
- `task_sync_push` - `workspace_sync_push`
- `task_exec` - `workspace_exec`
- `task_status` - `workspace_status`
- `task_logs` - `workspace_logs`
- `task_delete` - `workspace_delete`
Behavioral defaults: Behavioral defaults:
- `vm_run` and `vm_create` default to `vcpu_count=1` and `mem_mib=1024`. - `vm_run` and `vm_create` default to `vcpu_count=1` and `mem_mib=1024`.
- `task_create` defaults 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`. - `vm_run` and `vm_create` expose `allow_host_compat`, which defaults to `false`.
- `task_create` exposes `allow_host_compat`, which defaults to `false`. - `workspace_create` exposes `allow_host_compat`, which defaults to `false`.
- `task_create` accepts optional `source_path` and seeds `/workspace` from a host directory or a - `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.
local `.tar` / `.tar.gz` / `.tgz` archive before the task is returned. - `workspace_sync_push` imports later host-side directory or archive content into a started workspace, with an optional `dest` under `/workspace`.
- `task_sync_push` imports later host-side directory or archive content into a started task
workspace, with an optional `dest` under `/workspace`.
- `vm_exec` runs one command and auto-cleans that VM after the exec completes. - `vm_exec` runs one command and auto-cleans that VM after the exec completes.
- `task_exec` runs one command in a persistent `/workspace` and leaves the task alive. - `workspace_exec` runs one command in a persistent `/workspace` and leaves the workspace alive.
## Versioning Rule ## Versioning Rule

View file

@ -25,7 +25,7 @@ also expected to update:
## Milestones ## Milestones
1. [`2.4.0` Workspace Contract Pivot](task-workspace-ga/2.4.0-workspace-contract-pivot.md) 1. [`2.4.0` Workspace Contract Pivot](task-workspace-ga/2.4.0-workspace-contract-pivot.md) - Done
2. [`2.5.0` PTY Shell Sessions](task-workspace-ga/2.5.0-pty-shell-sessions.md) 2. [`2.5.0` PTY Shell Sessions](task-workspace-ga/2.5.0-pty-shell-sessions.md)
3. [`2.6.0` Structured Export And Baseline Diff](task-workspace-ga/2.6.0-structured-export-and-baseline-diff.md) 3. [`2.6.0` Structured Export And Baseline Diff](task-workspace-ga/2.6.0-structured-export-and-baseline-diff.md)
4. [`2.7.0` Service Lifecycle And Typed Readiness](task-workspace-ga/2.7.0-service-lifecycle-and-typed-readiness.md) 4. [`2.7.0` Service Lifecycle And Typed Readiness](task-workspace-ga/2.7.0-service-lifecycle-and-typed-readiness.md)

View file

@ -1,5 +1,7 @@
# `2.4.0` Workspace Contract Pivot # `2.4.0` Workspace Contract Pivot
Status: Done
## Goal ## Goal
Make the public product read as a workspace-first sandbox instead of a Make the public product read as a workspace-first sandbox instead of a
@ -16,7 +18,7 @@ task-flavored alpha by replacing the `task_*` surface with `workspace_*`.
- `pyro workspace delete` - `pyro workspace delete`
- SDK: - SDK:
- `create_workspace` - `create_workspace`
- `sync_push_workspace` - `push_workspace_sync`
- `exec_workspace` - `exec_workspace`
- `status_workspace` - `status_workspace`
- `logs_workspace` - `logs_workspace`

View file

@ -1,30 +0,0 @@
from __future__ import annotations
import tempfile
from pathlib import Path
from pyro_mcp import Pyro
def main() -> None:
pyro = Pyro()
with (
tempfile.TemporaryDirectory(prefix="pyro-task-seed-") as seed_dir,
tempfile.TemporaryDirectory(prefix="pyro-task-sync-") as sync_dir,
):
Path(seed_dir, "note.txt").write_text("hello from seed\n", encoding="utf-8")
Path(sync_dir, "note.txt").write_text("hello from sync\n", encoding="utf-8")
created = pyro.create_task(environment="debian:12", source_path=seed_dir)
task_id = str(created["task_id"])
try:
pyro.push_task_sync(task_id, sync_dir)
result = pyro.exec_task(task_id, command="cat note.txt")
print(result["stdout"], end="")
logs = pyro.logs_task(task_id)
print(f"task_id={task_id} command_count={logs['count']}")
finally:
pyro.delete_task(task_id)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,30 @@
from __future__ import annotations
import tempfile
from pathlib import Path
from pyro_mcp import Pyro
def main() -> None:
pyro = Pyro()
with (
tempfile.TemporaryDirectory(prefix="pyro-workspace-seed-") as seed_dir,
tempfile.TemporaryDirectory(prefix="pyro-workspace-sync-") as sync_dir,
):
Path(seed_dir, "note.txt").write_text("hello from seed\n", encoding="utf-8")
Path(sync_dir, "note.txt").write_text("hello from sync\n", encoding="utf-8")
created = pyro.create_workspace(environment="debian:12", seed_path=seed_dir)
workspace_id = str(created["workspace_id"])
try:
pyro.push_workspace_sync(workspace_id, sync_dir)
result = pyro.exec_workspace(workspace_id, command="cat note.txt")
print(result["stdout"], end="")
logs = pyro.logs_workspace(workspace_id)
print(f"workspace_id={workspace_id} command_count={logs['count']}")
finally:
pyro.delete_workspace(workspace_id)
if __name__ == "__main__":
main()