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:
parent
48b82d8386
commit
2de31306b6
9 changed files with 148 additions and 150 deletions
67
README.md
67
README.md
|
|
@ -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`.
|
||||||
|
|
||||||
[](https://pypi.org/project/pyro-mcp/)
|
[](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)
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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`
|
||||||
|
|
|
||||||
|
|
@ -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()
|
|
||||||
30
examples/python_workspace.py
Normal file
30
examples/python_workspace.py
Normal 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()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue