Add workspace naming and discovery
Make concurrent workspaces easier to rediscover and resume without relying on opaque IDs alone. Add optional workspace names, key/value labels, workspace list, and workspace update across the CLI, Python SDK, and MCP surface, and persist last_activity_at so list ordering reflects real mutating activity. Update the stable contract, install/first-run docs, roadmap, and Python workspace example to teach the new discovery flow, and validate it with focused manager/CLI/API/server coverage plus uv lock, make check, make dist-check, and a real multi-workspace smoke for create, list, update, exec, reorder, and delete.
This commit is contained in:
parent
ab02ae46c7
commit
446f7fce04
21 changed files with 999 additions and 23 deletions
|
|
@ -22,7 +22,7 @@ Networking: tun=yes ip_forward=yes
|
|||
|
||||
```bash
|
||||
$ uvx --from pyro-mcp pyro env list
|
||||
Catalog version: 3.2.0
|
||||
Catalog version: 3.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.
|
||||
|
|
@ -73,8 +73,10 @@ installed `pyro` binary by dropping the `uvx --from pyro-mcp` prefix, or with `u
|
|||
a source checkout.
|
||||
|
||||
```bash
|
||||
$ uvx --from pyro-mcp pyro workspace create debian:12 --seed-path ./repo --json | tee /tmp/pyro-workspace.json
|
||||
$ uvx --from pyro-mcp pyro workspace create debian:12 --seed-path ./repo --name repro-fix --label issue=123 --json | tee /tmp/pyro-workspace.json
|
||||
$ export WORKSPACE_ID="$(python -c 'import json,sys; print(json.load(sys.stdin)["workspace_id"])' < /tmp/pyro-workspace.json)"
|
||||
$ uvx --from pyro-mcp pyro workspace list
|
||||
$ uvx --from pyro-mcp pyro workspace update "$WORKSPACE_ID" --label owner=codex
|
||||
$ uvx --from pyro-mcp pyro workspace sync push "$WORKSPACE_ID" ./changes
|
||||
$ uvx --from pyro-mcp pyro workspace file read "$WORKSPACE_ID" note.txt
|
||||
$ uvx --from pyro-mcp pyro workspace patch apply "$WORKSPACE_ID" --patch "$(cat fix.patch)"
|
||||
|
|
@ -95,7 +97,9 @@ $ uvx --from pyro-mcp pyro workspace delete "$WORKSPACE_ID"
|
|||
|
||||
```bash
|
||||
$ uvx --from pyro-mcp pyro demo
|
||||
$ uvx --from pyro-mcp pyro workspace create debian:12 --seed-path ./repo
|
||||
$ uvx --from pyro-mcp pyro workspace create debian:12 --seed-path ./repo --name repro-fix --label issue=123
|
||||
$ uvx --from pyro-mcp pyro workspace list
|
||||
$ uvx --from pyro-mcp pyro workspace update WORKSPACE_ID --label owner=codex
|
||||
$ uvx --from pyro-mcp pyro workspace sync push WORKSPACE_ID ./changes
|
||||
$ uvx --from pyro-mcp pyro workspace file list WORKSPACE_ID src --recursive
|
||||
$ uvx --from pyro-mcp pyro workspace file read WORKSPACE_ID src/app.py
|
||||
|
|
@ -248,7 +252,7 @@ State: started
|
|||
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 workspace sync push` when you need to import later host-side changes into a started
|
||||
workspace. Sync is non-atomic in `3.2.0`; if it fails partway through, prefer `pyro workspace reset`
|
||||
workspace. Sync is non-atomic in `3.3.0`; if it fails partway through, prefer `pyro workspace reset`
|
||||
to recover from `baseline` or one named snapshot. Use `pyro workspace diff` to compare the current
|
||||
`/workspace` tree to its immutable create-time baseline, `pyro workspace snapshot *` to create
|
||||
named checkpoints, and `pyro workspace export` to copy one changed file or directory back to the
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ uvx --from pyro-mcp pyro env list
|
|||
Expected output:
|
||||
|
||||
```bash
|
||||
Catalog version: 3.2.0
|
||||
Catalog version: 3.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.
|
||||
|
|
@ -140,7 +140,9 @@ for the published package, or `uv run pyro ...` from a source checkout.
|
|||
|
||||
```bash
|
||||
uv tool install pyro-mcp
|
||||
WORKSPACE_ID="$(pyro workspace create debian:12 --seed-path ./repo --json | python -c 'import json,sys; print(json.load(sys.stdin)["workspace_id"])')"
|
||||
WORKSPACE_ID="$(pyro workspace create debian:12 --seed-path ./repo --name repro-fix --label issue=123 --json | python -c 'import json,sys; print(json.load(sys.stdin)["workspace_id"])')"
|
||||
pyro workspace list
|
||||
pyro workspace update "$WORKSPACE_ID" --label owner=codex
|
||||
pyro workspace sync push "$WORKSPACE_ID" ./changes
|
||||
pyro workspace file read "$WORKSPACE_ID" note.txt
|
||||
pyro workspace patch apply "$WORKSPACE_ID" --patch "$(cat fix.patch)"
|
||||
|
|
@ -155,6 +157,7 @@ pyro workspace delete "$WORKSPACE_ID"
|
|||
This is the stable persistent-workspace contract:
|
||||
|
||||
- `workspace create` seeds `/workspace`
|
||||
- `workspace create --name/--label`, `workspace list`, and `workspace update` make workspaces discoverable
|
||||
- `workspace sync push` imports later host-side changes
|
||||
- `workspace file *` and `workspace patch apply` cover model-native text inspection and edits
|
||||
- `workspace exec` and `workspace shell *` keep work inside one sandbox
|
||||
|
|
@ -208,6 +211,8 @@ pyro run debian:12 -- git --version
|
|||
After the CLI path works, you can move on to:
|
||||
|
||||
- persistent workspaces: `pyro workspace create debian:12 --seed-path ./repo`
|
||||
- workspace discovery metadata: `pyro workspace create debian:12 --name repro-fix --label issue=123`
|
||||
- workspace discovery commands: `pyro workspace list` and `pyro workspace update WORKSPACE_ID --label owner=codex`
|
||||
- live workspace updates: `pyro workspace sync push WORKSPACE_ID ./changes`
|
||||
- guest networking policy: `pyro workspace create debian:12 --network-policy egress`
|
||||
- workspace secrets: `pyro workspace create debian:12 --secret API_TOKEN=expected --secret-file PIP_TOKEN=./token.txt`
|
||||
|
|
@ -269,7 +274,7 @@ Workspace commands default to the persistent `/workspace` directory inside the g
|
|||
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 `3.2.0`; if it fails partway through, prefer `pyro workspace reset` to recover
|
||||
is non-atomic in `3.3.0`; if it fails partway through, prefer `pyro workspace reset` to recover
|
||||
from `baseline` or one named snapshot. Use `pyro workspace diff` to compare the current workspace
|
||||
tree to its immutable create-time baseline, `pyro workspace snapshot *` to capture named
|
||||
checkpoints, and `pyro workspace export` to copy one changed file or directory back to the host. Use
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ Best when:
|
|||
Recommended surface:
|
||||
|
||||
- `vm_run`
|
||||
- `workspace_create(name=..., labels=...)` + `workspace_list` + `workspace_update` when the agent needs to rediscover or retag long-lived workspaces between turns
|
||||
- `workspace_create(seed_path=...)` + `workspace_sync_push` + `workspace_exec` when the agent needs persistent workspace state
|
||||
- `workspace_file_list` / `workspace_file_read` / `workspace_file_write` / `workspace_patch_apply` when the agent needs model-native file inspection and text edits inside one live workspace
|
||||
- `workspace_create(..., secrets=...)` + `workspace_exec(..., secret_env=...)` when the workspace needs private tokens or authenticated setup
|
||||
|
|
@ -73,6 +74,7 @@ Best when:
|
|||
Recommended default:
|
||||
|
||||
- `Pyro.run_in_vm(...)`
|
||||
- `Pyro.create_workspace(name=..., labels=...)` + `Pyro.list_workspaces()` + `Pyro.update_workspace(...)` when repeated workspaces need human-friendly discovery metadata
|
||||
- `Pyro.create_workspace(seed_path=...)` + `Pyro.push_workspace_sync(...)` + `Pyro.exec_workspace(...)` when repeated workspace commands are required
|
||||
- `Pyro.list_workspace_files(...)` / `Pyro.read_workspace_file(...)` / `Pyro.write_workspace_file(...)` / `Pyro.apply_workspace_patch(...)` when the agent needs model-native file inspection and text edits inside one live workspace
|
||||
- `Pyro.create_workspace(..., secrets=...)` + `Pyro.exec_workspace(..., secret_env=...)` when the workspace needs private tokens or authenticated setup
|
||||
|
|
@ -88,6 +90,8 @@ Lifecycle note:
|
|||
that final exec
|
||||
- use `create_workspace(seed_path=...)` when the agent needs repeated commands in one persistent
|
||||
`/workspace` that starts from host content
|
||||
- use `create_workspace(name=..., labels=...)`, `list_workspaces()`, and `update_workspace(...)`
|
||||
when the agent or operator needs to rediscover the right workspace later without external notes
|
||||
- use `push_workspace_sync(...)` when later host-side changes need to be imported into that
|
||||
running workspace without recreating it
|
||||
- use `list_workspace_files(...)`, `read_workspace_file(...)`, `write_workspace_file(...)`, and
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ Top-level commands:
|
|||
- `pyro mcp serve`
|
||||
- `pyro run`
|
||||
- `pyro workspace create`
|
||||
- `pyro workspace list`
|
||||
- `pyro workspace sync push`
|
||||
- `pyro workspace stop`
|
||||
- `pyro workspace start`
|
||||
|
|
@ -53,6 +54,7 @@ Top-level commands:
|
|||
- `pyro workspace shell signal`
|
||||
- `pyro workspace shell close`
|
||||
- `pyro workspace status`
|
||||
- `pyro workspace update`
|
||||
- `pyro workspace logs`
|
||||
- `pyro workspace delete`
|
||||
- `pyro doctor`
|
||||
|
|
@ -78,8 +80,10 @@ Behavioral guarantees:
|
|||
- `pyro demo ollama` prints log lines plus a final summary line.
|
||||
- `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 create --name NAME --label KEY=VALUE` attaches human-oriented discovery metadata without changing the stable `workspace_id`.
|
||||
- `pyro workspace create --network-policy {off,egress,egress+published-ports}` controls workspace guest networking and whether services may publish localhost ports.
|
||||
- `pyro workspace create --secret NAME=VALUE` and `--secret-file NAME=PATH` persist guest-only UTF-8 secrets outside `/workspace`.
|
||||
- `pyro workspace list` returns persisted workspaces sorted by most recent `last_activity_at`.
|
||||
- `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 stop WORKSPACE_ID` stops one persistent workspace without deleting its `/workspace`, snapshots, or command history.
|
||||
- `pyro workspace start WORKSPACE_ID` restarts one stopped workspace without resetting `/workspace`.
|
||||
|
|
@ -103,11 +107,14 @@ Behavioral guarantees:
|
|||
- `pyro workspace shell open --secret-env SECRET_NAME[=ENV_VAR]` maps one persisted secret into the opened shell environment.
|
||||
- `pyro workspace shell *` manages persistent PTY sessions inside a started workspace.
|
||||
- `pyro workspace logs` returns persisted command history for that workspace until `pyro workspace delete`.
|
||||
- `pyro workspace update` changes only discovery metadata such as `name` and key/value `labels`.
|
||||
- Workspace create/status results expose `workspace_seed` metadata describing how `/workspace` was initialized.
|
||||
- Workspace create/status/reset/update results expose `name`, `labels`, and `last_activity_at`.
|
||||
- Workspace create/status/reset results expose `network_policy`.
|
||||
- Workspace create/status/reset results expose `reset_count` and `last_reset_at`.
|
||||
- Workspace create/status/reset results expose safe `secrets` metadata with each secret name and source kind, but never the secret values.
|
||||
- `pyro workspace status` includes aggregate `service_count` and `running_service_count` fields.
|
||||
- `pyro workspace list` returns one summary row per persisted workspace with `workspace_id`, `name`, `labels`, `environment`, `state`, `created_at`, `last_activity_at`, `expires_at`, `command_count`, `service_count`, and `running_service_count`.
|
||||
- `pyro workspace service start`, `pyro workspace service list`, and `pyro workspace service status` expose published-port metadata when present.
|
||||
|
||||
## Python SDK Contract
|
||||
|
|
@ -125,7 +132,8 @@ Supported public entrypoints:
|
|||
- `Pyro.inspect_environment(environment)`
|
||||
- `Pyro.prune_environments()`
|
||||
- `Pyro.create_vm(...)`
|
||||
- `Pyro.create_workspace(..., network_policy="off", secrets=None)`
|
||||
- `Pyro.create_workspace(..., name=None, labels=None, network_policy="off", secrets=None)`
|
||||
- `Pyro.list_workspaces()`
|
||||
- `Pyro.push_workspace_sync(workspace_id, source_path, *, dest="/workspace")`
|
||||
- `Pyro.stop_workspace(workspace_id)`
|
||||
- `Pyro.start_workspace(workspace_id)`
|
||||
|
|
@ -160,6 +168,7 @@ Supported public entrypoints:
|
|||
- `Pyro.delete_workspace(workspace_id)`
|
||||
- `Pyro.status_vm(vm_id)`
|
||||
- `Pyro.status_workspace(workspace_id)`
|
||||
- `Pyro.update_workspace(workspace_id, *, name=None, clear_name=False, labels=None, clear_labels=None)`
|
||||
- `Pyro.logs_workspace(workspace_id)`
|
||||
- `Pyro.network_info_vm(vm_id)`
|
||||
- `Pyro.reap_expired()`
|
||||
|
|
@ -173,7 +182,8 @@ Stable public method names:
|
|||
- `inspect_environment(environment)`
|
||||
- `prune_environments()`
|
||||
- `create_vm(...)`
|
||||
- `create_workspace(..., network_policy="off", secrets=None)`
|
||||
- `create_workspace(..., name=None, labels=None, network_policy="off", secrets=None)`
|
||||
- `list_workspaces()`
|
||||
- `push_workspace_sync(workspace_id, source_path, *, dest="/workspace")`
|
||||
- `stop_workspace(workspace_id)`
|
||||
- `start_workspace(workspace_id)`
|
||||
|
|
@ -208,6 +218,7 @@ Stable public method names:
|
|||
- `delete_workspace(workspace_id)`
|
||||
- `status_vm(vm_id)`
|
||||
- `status_workspace(workspace_id)`
|
||||
- `update_workspace(workspace_id, *, name=None, clear_name=False, labels=None, clear_labels=None)`
|
||||
- `logs_workspace(workspace_id)`
|
||||
- `network_info_vm(vm_id)`
|
||||
- `reap_expired()`
|
||||
|
|
@ -220,8 +231,10 @@ Behavioral defaults:
|
|||
- `allow_host_compat` defaults to `False` on `create_vm(...)` and `run_in_vm(...)`.
|
||||
- `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.create_workspace(..., name=..., labels=...)` attaches human-oriented discovery metadata without changing the stable `workspace_id`.
|
||||
- `Pyro.create_workspace(..., network_policy="off"|"egress"|"egress+published-ports")` controls workspace guest networking and whether services may publish host ports.
|
||||
- `Pyro.create_workspace(..., secrets=...)` persists guest-only UTF-8 secrets outside `/workspace`.
|
||||
- `Pyro.list_workspaces()` returns persisted workspace summaries sorted by most recent `last_activity_at`.
|
||||
- `Pyro.push_workspace_sync(...)` imports later host-side directory or archive content into a started workspace.
|
||||
- `Pyro.stop_workspace(...)` stops one persistent workspace without deleting its `/workspace`, snapshots, or command history.
|
||||
- `Pyro.start_workspace(...)` restarts one stopped workspace without resetting `/workspace`.
|
||||
|
|
@ -248,6 +261,7 @@ Behavioral defaults:
|
|||
- `Pyro.open_shell(...)` opens a persistent PTY shell attached to one started workspace.
|
||||
- `Pyro.read_shell(...)` reads merged text output from that shell by cursor.
|
||||
- `Pyro.write_shell(...)`, `Pyro.signal_shell(...)`, and `Pyro.close_shell(...)` operate on that persistent shell session.
|
||||
- `Pyro.update_workspace(...)` changes only discovery metadata such as `name` and key/value `labels`.
|
||||
|
||||
## MCP Contract
|
||||
|
||||
|
|
@ -270,6 +284,7 @@ Advanced lifecycle tools:
|
|||
Persistent workspace tools:
|
||||
|
||||
- `workspace_create`
|
||||
- `workspace_list`
|
||||
- `workspace_sync_push`
|
||||
- `workspace_stop`
|
||||
- `workspace_start`
|
||||
|
|
@ -298,6 +313,7 @@ Persistent workspace tools:
|
|||
- `shell_signal`
|
||||
- `shell_close`
|
||||
- `workspace_status`
|
||||
- `workspace_update`
|
||||
- `workspace_logs`
|
||||
- `workspace_delete`
|
||||
|
||||
|
|
@ -308,8 +324,10 @@ Behavioral defaults:
|
|||
- `vm_run` and `vm_create` expose `allow_host_compat`, which defaults to `false`.
|
||||
- `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_create` accepts optional `name` and `labels` metadata for human discovery without changing the stable `workspace_id`.
|
||||
- `workspace_create` accepts `network_policy` with `off`, `egress`, or `egress+published-ports` to control workspace guest networking and service port publication.
|
||||
- `workspace_create` accepts optional `secrets` and persists guest-only UTF-8 secret material outside `/workspace`.
|
||||
- `workspace_list` returns persisted workspace summaries sorted by most recent `last_activity_at`.
|
||||
- `workspace_sync_push` imports later host-side directory or archive content into a started workspace, with an optional `dest` under `/workspace`.
|
||||
- `workspace_stop` stops one persistent workspace without deleting its `/workspace`, snapshots, or command history.
|
||||
- `workspace_start` restarts one stopped workspace without resetting `/workspace`.
|
||||
|
|
@ -327,6 +345,7 @@ Behavioral defaults:
|
|||
- `service_start` accepts optional `published_ports` to expose guest TCP ports on `127.0.0.1` when the workspace network policy is `egress+published-ports`.
|
||||
- `vm_exec` runs one command and auto-cleans that VM after the exec completes.
|
||||
- `workspace_exec` accepts optional `secret_env` mappings for one exec call and leaves the workspace alive.
|
||||
- `workspace_update` changes only discovery metadata such as `name` and key/value `labels`.
|
||||
- `service_start` accepts optional `secret_env` mappings for one service start call.
|
||||
- `shell_open` accepts optional `secret_env` mappings for the opened shell session.
|
||||
- `shell_open`, `shell_read`, `shell_write`, `shell_signal`, and `shell_close` manage persistent PTY shells inside a started workspace.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ goal:
|
|||
make the core agent-workspace use cases feel trivial from a chat-driven LLM
|
||||
interface.
|
||||
|
||||
Current baseline is `3.2.0`:
|
||||
Current baseline is `3.3.0`:
|
||||
|
||||
- the stable workspace contract exists across CLI, SDK, and MCP
|
||||
- one-shot `pyro run` still exists as the narrow entrypoint
|
||||
|
|
@ -46,7 +46,7 @@ More concretely, the model should not need to:
|
|||
## Milestones
|
||||
|
||||
1. [`3.2.0` Model-Native Workspace File Ops](llm-chat-ergonomics/3.2.0-model-native-workspace-file-ops.md) - Done
|
||||
2. [`3.3.0` Workspace Naming And Discovery](llm-chat-ergonomics/3.3.0-workspace-naming-and-discovery.md)
|
||||
2. [`3.3.0` Workspace Naming And Discovery](llm-chat-ergonomics/3.3.0-workspace-naming-and-discovery.md) - Done
|
||||
3. [`3.4.0` Tool Profiles And Canonical Chat Flows](llm-chat-ergonomics/3.4.0-tool-profiles-and-canonical-chat-flows.md)
|
||||
4. [`3.5.0` Chat-Friendly Shell Output](llm-chat-ergonomics/3.5.0-chat-friendly-shell-output.md)
|
||||
5. [`3.6.0` Use-Case Recipes And Smoke Packs](llm-chat-ergonomics/3.6.0-use-case-recipes-and-smoke-packs.md)
|
||||
|
|
@ -55,6 +55,9 @@ Completed so far:
|
|||
|
||||
- `3.2.0` added model-native `workspace file *` and `workspace patch apply` so chat-driven agents
|
||||
can inspect and edit `/workspace` without shell-escaped file mutation flows.
|
||||
- `3.3.0` added workspace names, key/value labels, `workspace list`, `workspace update`, and
|
||||
`last_activity_at` tracking so humans and chat-driven agents can rediscover and resume the right
|
||||
workspace without external notes.
|
||||
|
||||
## Expected Outcome
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# `3.3.0` Workspace Naming And Discovery
|
||||
|
||||
Status: Planned
|
||||
Status: Done
|
||||
|
||||
## Goal
|
||||
|
||||
|
|
@ -50,3 +50,4 @@ Planned additions:
|
|||
- README and install docs that show parallel named workspaces
|
||||
- examples that demonstrate issue-oriented workspace naming
|
||||
- smoke coverage for at least one multi-workspace flow
|
||||
- public contract, CLI help, and examples that show `workspace list` and `workspace update`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue