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` 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/)
@ -16,10 +16,11 @@ It exposes the same runtime in three public forms:
- Install: [docs/install.md](docs/install.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)
- 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/)
- 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)
- Integration targets: [docs/integrations.md](docs/integrations.md)
- Public contract: [docs/public-contract.md](docs/public-contract.md)
@ -56,7 +57,7 @@ What success looks like:
```bash
Platform: linux-x86_64
Runtime: PASS
Catalog version: 2.3.0
Catalog version: 2.4.0
...
[pull] phase=install 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:
- 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`
- update a live task from the host with `uvx --from pyro-mcp pyro task sync push TASK_ID ./changes`
- create a persistent workspace with `uvx --from pyro-mcp pyro workspace create debian:12 --seed-path ./repo`
- 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)
## Supported Hosts
@ -130,7 +131,7 @@ uvx --from pyro-mcp pyro env list
Expected output:
```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-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.
@ -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).
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.
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
longer-term interaction model.
```bash
pyro task create debian:12 --source-path ./repo
pyro task sync push TASK_ID ./changes --dest src
pyro task exec TASK_ID -- cat src/note.txt
pyro task logs TASK_ID
pyro task delete TASK_ID
pyro workspace create debian:12 --seed-path ./repo
pyro workspace sync push WORKSPACE_ID ./changes --dest src
pyro workspace exec WORKSPACE_ID -- cat src/note.txt
pyro workspace logs WORKSPACE_ID
pyro workspace delete WORKSPACE_ID
```
Task workspaces start in `/workspace` and keep command history until you delete them. For machine
consumption, add `--json` and read the returned `task_id`. Use `--source-path` when you want the
task to start from a host directory or a local `.tar` / `.tar.gz` / `.tgz` archive instead of an
empty workspace. Use `pyro task sync push` when you want to import later host-side changes into a
started task. Sync is non-atomic in `2.3.0`; if it fails partway through, delete and recreate the
task from its seed.
Persistent workspaces start in `/workspace` and keep command history until you delete them. For
machine consumption, add `--json` and read the returned `workspace_id`. Use `--seed-path` when
you want the workspace to start from a host directory or a local `.tar` / `.tar.gz` / `.tgz`
archive instead of an empty workspace. Use `pyro workspace sync push` when you want to import
later host-side changes into a started workspace. Sync is non-atomic in `2.4.0`; if it fails
partway through, delete and recreate the workspace from its seed.
## Public Interfaces
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
- `pyro mcp serve` for MCP clients
@ -359,14 +360,14 @@ For repeated commands in one workspace:
from pyro_mcp import Pyro
pyro = Pyro()
task = pyro.create_task(environment="debian:12", source_path="./repo")
task_id = task["task_id"]
workspace = pyro.create_workspace(environment="debian:12", seed_path="./repo")
workspace_id = workspace["workspace_id"]
try:
pyro.push_task_sync(task_id, "./changes", dest="src")
result = pyro.exec_task(task_id, command="cat src/note.txt")
pyro.push_workspace_sync(workspace_id, "./changes", dest="src")
result = pyro.exec_workspace(workspace_id, command="cat src/note.txt")
print(result["stdout"], end="")
finally:
pyro.delete_task(task_id)
pyro.delete_workspace(workspace_id)
```
## MCP Tools
@ -389,18 +390,18 @@ Advanced lifecycle 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)`
- `task_sync_push(task_id, source_path, dest="/workspace")`
- `task_exec(task_id, command, timeout_seconds=30)`
- `task_status(task_id)`
- `task_logs(task_id)`
- `task_delete(task_id)`
- `workspace_create(environment, vcpu_count=1, mem_mib=1024, ttl_seconds=600, network=false, allow_host_compat=false, seed_path=null)`
- `workspace_sync_push(workspace_id, source_path, dest="/workspace")`
- `workspace_exec(workspace_id, command, timeout_seconds=30)`
- `workspace_status(workspace_id)`
- `workspace_logs(workspace_id)`
- `workspace_delete(workspace_id)`
## Integration Examples
- 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 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)
- 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)

View file

@ -22,7 +22,7 @@ Networking: tun=yes ip_forward=yes
```bash
$ 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-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,18 +70,18 @@ deterministic structured result.
```bash
$ uvx --from pyro-mcp pyro demo
$ uvx --from pyro-mcp pyro task create debian:12 --source-path ./repo
$ uvx --from pyro-mcp pyro task sync push TASK_ID ./changes
$ uvx --from pyro-mcp pyro workspace create debian:12 --seed-path ./repo
$ uvx --from pyro-mcp pyro workspace sync push WORKSPACE_ID ./changes
$ 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 ...`:
When you need repeated commands in one sandbox, switch to `pyro workspace ...`:
```bash
$ uvx --from pyro-mcp pyro task create debian:12 --source-path ./repo
Task: ...
$ uvx --from pyro-mcp pyro workspace create debian:12 --seed-path ./repo
Workspace ID: ...
Environment: debian:12
State: started
Workspace: /workspace
@ -90,18 +90,19 @@ Execution mode: guest_vsock
Resources: 1 vCPU / 1024 MiB
Command count: 0
$ uvx --from pyro-mcp pyro task sync push TASK_ID ./changes --dest src
[task-sync] task_id=... mode=directory source=... destination=/workspace/src entry_count=... bytes_written=... execution_mode=guest_vsock
$ uvx --from pyro-mcp pyro workspace sync push WORKSPACE_ID ./changes --dest src
[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
hello from synced task
[task-exec] task_id=... sequence=1 cwd=/workspace execution_mode=guest_vsock exit_code=0 duration_ms=...
$ uvx --from pyro-mcp pyro workspace exec WORKSPACE_ID -- cat src/note.txt
hello from synced workspace
[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
`pyro task sync push` when you need to import later host-side changes into a started task.
Sync is non-atomic in `2.3.0`; if it fails partway through, delete and recreate the task.
`pyro workspace sync push` when you need to import later host-side changes into a started
workspace. Sync is non-atomic in `2.4.0`; if it fails partway through, delete and recreate the
workspace.
Example output:

View file

@ -83,7 +83,7 @@ uvx --from pyro-mcp pyro env list
Expected output:
```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-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,29 +174,30 @@ pyro run debian:12 -- git --version
After the CLI path works, you can move on to:
- persistent workspaces: `pyro task create debian:12 --source-path ./repo`
- live task updates: `pyro task sync push TASK_ID ./changes`
- persistent workspaces: `pyro workspace create debian:12 --seed-path ./repo`
- live workspace updates: `pyro workspace sync push WORKSPACE_ID ./changes`
- MCP: `pyro mcp serve`
- Python SDK: `from pyro_mcp import Pyro`
- 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
pyro task create debian:12 --source-path ./repo
pyro task sync push TASK_ID ./changes --dest src
pyro task exec TASK_ID -- cat src/note.txt
pyro task logs TASK_ID
pyro task delete TASK_ID
pyro workspace create debian:12 --seed-path ./repo
pyro workspace sync push WORKSPACE_ID ./changes --dest src
pyro workspace exec WORKSPACE_ID -- cat src/note.txt
pyro workspace logs WORKSPACE_ID
pyro workspace delete WORKSPACE_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. Use `--source-path`
when the task should start from a host directory or a local `.tar` / `.tar.gz` / `.tgz` archive.
Use `pyro task sync push` for later host-side changes to a started task. Sync is non-atomic in
`2.3.0`; if it fails partway through, delete and recreate the task from its seed.
Workspace commands default to the persistent `/workspace` directory inside the guest. If you need
the identifier programmatically, use `--json` and read the `workspace_id` field. Use `--seed-path`
when the workspace should start from a host directory or a local `.tar` / `.tar.gz` / `.tgz`
archive. Use `pyro workspace sync push` for later host-side changes to a started workspace. Sync
is non-atomic in `2.4.0`; if it fails partway through, delete and recreate the workspace from its
seed.
## Contributor Clone

View file

@ -16,7 +16,7 @@ That keeps the model-facing contract small:
- one ephemeral VM
- 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.
## OpenAI Responses API
@ -30,7 +30,7 @@ Best when:
Recommended surface:
- `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:
@ -65,23 +65,23 @@ Best when:
Recommended default:
- `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:
- `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(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
- use `push_task_sync(...)` when later host-side changes need to be imported into that running
workspace without recreating the task
- use `push_workspace_sync(...)` when later host-side changes need to be imported into that
running workspace without recreating it
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)
- [examples/python_workspace.py](../examples/python_workspace.py)
## Agent Framework Wrappers
@ -100,7 +100,7 @@ Recommended pattern:
- keep the framework wrapper thin
- 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:

View file

@ -19,12 +19,12 @@ Top-level commands:
- `pyro env prune`
- `pyro mcp serve`
- `pyro run`
- `pyro task create`
- `pyro task sync push`
- `pyro task exec`
- `pyro task status`
- `pyro task logs`
- `pyro task delete`
- `pyro workspace create`
- `pyro workspace sync push`
- `pyro workspace exec`
- `pyro workspace status`
- `pyro workspace logs`
- `pyro workspace delete`
- `pyro doctor`
- `pyro demo`
- `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`, `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 create --source-path PATH` seeds `/workspace` from a host directory or a local
`.tar` / `.tar.gz` / `.tgz` archive before the task is returned.
- `pyro task sync push TASK_ID SOURCE_PATH [--dest WORKSPACE_PATH]` imports later host-side
directory or archive content into a started task 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`.
- Task create/status results expose `workspace_seed` metadata describing how `/workspace` was
initialized.
- `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 sync push WORKSPACE_ID SOURCE_PATH [--dest WORKSPACE_PATH]` imports later host-side directory or archive content into a started workspace.
- `pyro workspace exec` runs in the persistent `/workspace` for that workspace and does not auto-clean.
- `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.
## Python SDK Contract
@ -71,17 +68,17 @@ Supported public entrypoints:
- `Pyro.inspect_environment(environment)`
- `Pyro.prune_environments()`
- `Pyro.create_vm(...)`
- `Pyro.create_task(...)`
- `Pyro.push_task_sync(task_id, source_path, *, dest="/workspace")`
- `Pyro.create_workspace(...)`
- `Pyro.push_workspace_sync(workspace_id, source_path, *, dest="/workspace")`
- `Pyro.start_vm(vm_id)`
- `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.delete_vm(vm_id)`
- `Pyro.delete_task(task_id)`
- `Pyro.delete_workspace(workspace_id)`
- `Pyro.status_vm(vm_id)`
- `Pyro.status_task(task_id)`
- `Pyro.logs_task(task_id)`
- `Pyro.status_workspace(workspace_id)`
- `Pyro.logs_workspace(workspace_id)`
- `Pyro.network_info_vm(vm_id)`
- `Pyro.reap_expired()`
- `Pyro.run_in_vm(...)`
@ -94,17 +91,17 @@ Stable public method names:
- `inspect_environment(environment)`
- `prune_environments()`
- `create_vm(...)`
- `create_task(...)`
- `push_task_sync(task_id, source_path, *, dest="/workspace")`
- `create_workspace(...)`
- `push_workspace_sync(workspace_id, source_path, *, dest="/workspace")`
- `start_vm(vm_id)`
- `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)`
- `delete_vm(vm_id)`
- `delete_task(task_id)`
- `delete_workspace(workspace_id)`
- `status_vm(vm_id)`
- `status_task(task_id)`
- `logs_task(task_id)`
- `status_workspace(workspace_id)`
- `logs_workspace(workspace_id)`
- `network_info_vm(vm_id)`
- `reap_expired()`
- `run_in_vm(...)`
@ -112,15 +109,13 @@ 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`.
- `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_task(...)`.
- `Pyro.create_task(..., source_path=...)` seeds `/workspace` from a host directory or a local
`.tar` / `.tar.gz` / `.tgz` archive before the task is returned.
- `Pyro.push_task_sync(...)` imports later host-side directory or archive content into a started
task workspace.
- `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.push_workspace_sync(...)` imports later host-side directory or archive content into a started workspace.
- `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
@ -140,27 +135,25 @@ Advanced lifecycle tools:
- `vm_network_info`
- `vm_reap_expired`
Task workspace tools:
Persistent workspace tools:
- `task_create`
- `task_sync_push`
- `task_exec`
- `task_status`
- `task_logs`
- `task_delete`
- `workspace_create`
- `workspace_sync_push`
- `workspace_exec`
- `workspace_status`
- `workspace_logs`
- `workspace_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`.
- `workspace_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`.
- `task_create` accepts optional `source_path` and seeds `/workspace` from a host directory or a
local `.tar` / `.tar.gz` / `.tgz` archive before the task is returned.
- `task_sync_push` imports later host-side directory or archive content into a started task
workspace, with an optional `dest` under `/workspace`.
- `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_sync_push` imports later host-side directory or archive content into a started workspace, with an optional `dest` under `/workspace`.
- `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

View file

@ -25,7 +25,7 @@ also expected to update:
## 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)
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)

View file

@ -1,5 +1,7 @@
# `2.4.0` Workspace Contract Pivot
Status: Done
## Goal
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`
- SDK:
- `create_workspace`
- `sync_push_workspace`
- `push_workspace_sync`
- `exec_workspace`
- `status_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()