Add task sync push milestone

Tasks could start from host content in 2.2.0, but there was still no post-create path to update a live workspace from the host. This change adds the next host-to-task step so repeated fix or review loops do not require recreating the task for every local change.

Add task sync push across the CLI, Python SDK, and MCP server, reusing the existing safe archive import path from seeded task creation instead of introducing a second transfer stack. The implementation keeps sync separate from workspace_seed metadata, validates destinations under /workspace, and documents the current non-atomic recovery path as delete-and-recreate.

Validation:
- uv lock
- UV_CACHE_DIR=.uv-cache uv run pytest --no-cov tests/test_cli.py tests/test_vm_manager.py tests/test_api.py tests/test_server.py tests/test_public_contract.py
- UV_CACHE_DIR=.uv-cache make check
- UV_CACHE_DIR=.uv-cache make dist-check
- real guest-backed smoke: task create --source-path, task sync push, task exec to verify both files, task delete
This commit is contained in:
Thales Maciel 2026-03-11 22:20:55 -03:00
parent aa886b346e
commit 9e11dcf9ab
19 changed files with 461 additions and 41 deletions

View file

@ -22,7 +22,7 @@ Networking: tun=yes ip_forward=yes
```bash
$ uvx --from pyro-mcp pyro env list
Catalog version: 2.2.0
Catalog version: 2.3.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.
@ -71,6 +71,7 @@ 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 mcp serve
```
@ -89,16 +90,18 @@ 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 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 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=...
$ 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=...
```
Use `--source-path` when the task should start from a host directory or a local
`.tar` / `.tar.gz` / `.tgz` archive instead of an empty `/workspace`.
`.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.
Example output:

View file

@ -83,7 +83,7 @@ uvx --from pyro-mcp pyro env list
Expected output:
```bash
Catalog version: 2.2.0
Catalog version: 2.3.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.
@ -175,6 +175,7 @@ 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`
- MCP: `pyro mcp serve`
- Python SDK: `from pyro_mcp import Pyro`
- Demos: `pyro demo` or `pyro demo --network`
@ -185,8 +186,8 @@ Use `pyro task ...` when you need repeated commands in one sandbox instead of on
```bash
pyro task create debian:12 --source-path ./repo
pyro task exec TASK_ID -- sh -lc 'printf "hello from task\n" > note.txt'
pyro task exec TASK_ID -- cat note.txt
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
```
@ -194,6 +195,8 @@ 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. 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.
## Contributor Clone

View file

@ -30,7 +30,7 @@ Best when:
Recommended surface:
- `vm_run`
- `task_create(source_path=...)` + `task_exec` when the agent needs persistent workspace state
- `task_create(source_path=...)` + `task_sync_push` + `task_exec` when the agent needs persistent workspace state
Canonical example:
@ -65,7 +65,7 @@ Best when:
Recommended default:
- `Pyro.run_in_vm(...)`
- `Pyro.create_task(source_path=...)` + `Pyro.exec_task(...)` when repeated workspace commands are required
- `Pyro.create_task(source_path=...)` + `Pyro.push_task_sync(...)` + `Pyro.exec_task(...)` when repeated workspace commands are required
Lifecycle note:
@ -74,6 +74,8 @@ Lifecycle note:
that final exec
- use `create_task(source_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
Examples:

View file

@ -20,6 +20,7 @@ Top-level commands:
- `pyro mcp serve`
- `pyro run`
- `pyro task create`
- `pyro task sync push`
- `pyro task exec`
- `pyro task status`
- `pyro task logs`
@ -48,6 +49,8 @@ Behavioral guarantees:
- `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
@ -69,6 +72,7 @@ Supported public entrypoints:
- `Pyro.prune_environments()`
- `Pyro.create_vm(...)`
- `Pyro.create_task(...)`
- `Pyro.push_task_sync(task_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)`
@ -91,6 +95,7 @@ Stable public method names:
- `prune_environments()`
- `create_vm(...)`
- `create_task(...)`
- `push_task_sync(task_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)`
@ -112,6 +117,8 @@ Behavioral defaults:
- `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.
- `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.
@ -136,6 +143,7 @@ Advanced lifecycle tools:
Task workspace tools:
- `task_create`
- `task_sync_push`
- `task_exec`
- `task_status`
- `task_logs`
@ -149,6 +157,8 @@ Behavioral defaults:
- `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`.
- `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.