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

@ -18,7 +18,7 @@ It exposes the same runtime in three public forms:
- 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.2.0: [CHANGELOG.md#220](CHANGELOG.md#220)
- What's new in 2.3.0: [CHANGELOG.md#230](CHANGELOG.md#230)
- 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)
@ -55,7 +55,7 @@ What success looks like:
```bash
Platform: linux-x86_64
Runtime: PASS
Catalog version: 2.2.0
Catalog version: 2.3.0
...
[pull] phase=install environment=debian:12
[pull] phase=ready environment=debian:12
@ -75,6 +75,7 @@ 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`
- move to Python or MCP via [docs/integrations.md](docs/integrations.md)
## Supported Hosts
@ -199,8 +200,8 @@ workspace without recreating the sandbox every time.
```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
```
@ -208,7 +209,9 @@ pyro task delete TASK_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.
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.
## Public Interfaces
@ -353,8 +356,8 @@ pyro = Pyro()
task = pyro.create_task(environment="debian:12", source_path="./repo")
task_id = task["task_id"]
try:
pyro.exec_task(task_id, command="printf 'hello from task\\n' > note.txt")
result = pyro.exec_task(task_id, command="cat note.txt")
pyro.push_task_sync(task_id, "./changes", dest="src")
result = pyro.exec_task(task_id, command="cat src/note.txt")
print(result["stdout"], end="")
finally:
pyro.delete_task(task_id)
@ -381,6 +384,7 @@ 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)`