Add persistent task workspace alpha

Start the first workspace milestone toward the task-oriented product without changing the existing one-shot vm_run/pyro run contract.

Add a disk-backed task registry in the manager, auto-started task workspaces rooted at /workspace, repeated non-cleaning exec, and persisted command journals exposed through task create/exec/status/logs/delete across the CLI, Python SDK, and MCP server.

Update the public contract, docs, examples, and version/catalog metadata for 2.1.0, and cover the new surface with manager, CLI, SDK, and MCP tests. Validation: UV_CACHE_DIR=.uv-cache make check and UV_CACHE_DIR=.uv-cache make dist-check.
This commit is contained in:
Thales Maciel 2026-03-11 20:10:10 -03:00
parent 6e16e74fd5
commit 58df176148
19 changed files with 1730 additions and 48 deletions

View file

@ -22,7 +22,7 @@ Networking: tun=yes ip_forward=yes
```bash
$ uvx --from pyro-mcp pyro env list
Catalog version: 2.0.0
Catalog version: 2.1.0
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-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled.
@ -70,11 +70,32 @@ deterministic structured result.
```bash
$ uvx --from pyro-mcp pyro demo
$ uvx --from pyro-mcp pyro task create debian:12
$ uvx --from pyro-mcp pyro mcp serve
```
`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 ...`:
```bash
$ uvx --from pyro-mcp pyro task create debian:12
Task: ...
Environment: debian:12
State: started
Workspace: /workspace
Execution mode: guest_vsock
Resources: 1 vCPU / 1024 MiB
Command count: 0
$ uvx --from pyro-mcp pyro task exec TASK_ID -- sh -lc 'printf "hello from task\n" > note.txt'
[task-exec] task_id=... sequence=1 cwd=/workspace execution_mode=guest_vsock exit_code=0 duration_ms=...
$ uvx --from pyro-mcp pyro task exec TASK_ID -- cat note.txt
hello from task
[task-exec] task_id=... sequence=2 cwd=/workspace execution_mode=guest_vsock exit_code=0 duration_ms=...
```
Example output:
```json

View file

@ -83,7 +83,7 @@ uvx --from pyro-mcp pyro env list
Expected output:
```bash
Catalog version: 2.0.0
Catalog version: 2.1.0
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-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled.
@ -174,10 +174,26 @@ pyro run debian:12 -- git --version
After the CLI path works, you can move on to:
- persistent workspaces: `pyro task create debian:12`
- MCP: `pyro mcp serve`
- Python SDK: `from pyro_mcp import Pyro`
- Demos: `pyro demo` or `pyro demo --network`
## Persistent Task Workspace
Use `pyro task ...` when you need repeated commands in one sandbox instead of one-shot `pyro run`.
```bash
pyro task create debian:12
pyro task exec TASK_ID -- sh -lc 'printf "hello from task\n" > note.txt'
pyro task exec TASK_ID -- cat note.txt
pyro task logs TASK_ID
pyro task delete TASK_ID
```
Task commands default to the persistent `/workspace` directory inside the guest. If you need the
task identifier programmatically, use `--json` and read the `task_id` field.
## Contributor Clone
```bash

View file

@ -7,7 +7,7 @@ CLI path in [install.md](install.md) or [first-run.md](first-run.md).
## Recommended Default
Use `vm_run` first.
Use `vm_run` first for one-shot commands.
That keeps the model-facing contract small:
@ -16,7 +16,8 @@ That keeps the model-facing contract small:
- one ephemeral VM
- automatic cleanup
Only move to lifecycle tools when the agent truly needs VM state across multiple calls.
Move to `task_*` only when the agent truly needs repeated commands in one workspace across
multiple calls.
## OpenAI Responses API
@ -29,6 +30,7 @@ Best when:
Recommended surface:
- `vm_run`
- `task_create` + `task_exec` when the agent needs persistent workspace state
Canonical example:
@ -63,17 +65,20 @@ Best when:
Recommended default:
- `Pyro.run_in_vm(...)`
- `Pyro.create_task(...)` + `Pyro.exec_task(...)` when repeated workspace commands are required
Lifecycle note:
- `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
that final exec
- use `create_task(...)` when the agent needs repeated commands in one persistent `/workspace`
Examples:
- [examples/python_run.py](../examples/python_run.py)
- [examples/python_lifecycle.py](../examples/python_lifecycle.py)
- [examples/python_task.py](../examples/python_task.py)
## Agent Framework Wrappers
@ -91,8 +96,8 @@ Best when:
Recommended pattern:
- keep the framework wrapper thin
- map framework tool input directly onto `vm_run`
- avoid exposing lifecycle tools unless the framework truly needs them
- map one-shot framework tool input directly onto `vm_run`
- expose `task_*` only when the framework truly needs repeated commands in one workspace
Concrete example:

View file

@ -19,6 +19,11 @@ Top-level commands:
- `pyro env prune`
- `pyro mcp serve`
- `pyro run`
- `pyro task create`
- `pyro task exec`
- `pyro task status`
- `pyro task logs`
- `pyro task delete`
- `pyro doctor`
- `pyro demo`
- `pyro demo ollama`
@ -40,6 +45,9 @@ Behavioral guarantees:
- `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 task create` auto-starts a persistent workspace.
- `pyro task exec` runs in the persistent `/workspace` for that task and does not auto-clean.
- `pyro task logs` returns persisted command history for that task until `pyro task delete`.
## Python SDK Contract
@ -56,11 +64,16 @@ Supported public entrypoints:
- `Pyro.inspect_environment(environment)`
- `Pyro.prune_environments()`
- `Pyro.create_vm(...)`
- `Pyro.create_task(...)`
- `Pyro.start_vm(vm_id)`
- `Pyro.exec_vm(vm_id, *, command, timeout_seconds=30)`
- `Pyro.exec_task(task_id, *, command, timeout_seconds=30)`
- `Pyro.stop_vm(vm_id)`
- `Pyro.delete_vm(vm_id)`
- `Pyro.delete_task(task_id)`
- `Pyro.status_vm(vm_id)`
- `Pyro.status_task(task_id)`
- `Pyro.logs_task(task_id)`
- `Pyro.network_info_vm(vm_id)`
- `Pyro.reap_expired()`
- `Pyro.run_in_vm(...)`
@ -73,11 +86,16 @@ Stable public method names:
- `inspect_environment(environment)`
- `prune_environments()`
- `create_vm(...)`
- `create_task(...)`
- `start_vm(vm_id)`
- `exec_vm(vm_id, *, command, timeout_seconds=30)`
- `exec_task(task_id, *, command, timeout_seconds=30)`
- `stop_vm(vm_id)`
- `delete_vm(vm_id)`
- `delete_task(task_id)`
- `status_vm(vm_id)`
- `status_task(task_id)`
- `logs_task(task_id)`
- `network_info_vm(vm_id)`
- `reap_expired()`
- `run_in_vm(...)`
@ -85,8 +103,11 @@ Stable public method names:
Behavioral defaults:
- `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`.
- `allow_host_compat` defaults to `False` on `create_vm(...)` and `run_in_vm(...)`.
- `allow_host_compat` defaults to `False` on `create_task(...)`.
- `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.
## MCP Contract
@ -106,11 +127,22 @@ Advanced lifecycle tools:
- `vm_network_info`
- `vm_reap_expired`
Task workspace tools:
- `task_create`
- `task_exec`
- `task_status`
- `task_logs`
- `task_delete`
Behavioral defaults:
- `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`.
- `vm_run` and `vm_create` expose `allow_host_compat`, which defaults to `false`.
- `task_create` exposes `allow_host_compat`, which defaults to `false`.
- `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.
## Versioning Rule