feat: support smolmachines bottle commit
This commit is contained in:
@@ -7,10 +7,11 @@
|
||||
|
||||
## Summary
|
||||
|
||||
Add a `commit` CLI command that freezes a running Docker bottle's
|
||||
container state to a named Docker image. Operators can then resume the
|
||||
bottle from that exact filesystem snapshot, or export the image with
|
||||
`docker save` to migrate work to a different host.
|
||||
Add a `commit` CLI command that freezes a running bottle's state to a
|
||||
resumable local artifact. Docker bottles are stored as Docker images;
|
||||
smolmachines bottles are stored as `.smolmachine` artifacts. Operators
|
||||
can then resume the bottle from that exact filesystem snapshot, or
|
||||
export the artifact to migrate work to a different host.
|
||||
|
||||
## Problem
|
||||
|
||||
@@ -29,30 +30,29 @@ snapshot before a planned host reboot or hardware migration.
|
||||
|
||||
## Goals / Success Criteria
|
||||
|
||||
- `./cli.py commit [<slug>]` takes a snapshot of the running Docker
|
||||
agent container and stores it as a local Docker image.
|
||||
- `./cli.py commit [<slug>]` takes a snapshot of the running agent and
|
||||
stores it as a local artifact.
|
||||
- Without a slug argument the command shows the same interactive picker
|
||||
as `start` (the list of active slugs).
|
||||
- The committed image tag is stored in per-bottle state so that the next
|
||||
`./cli.py resume <slug>` automatically uses the committed image instead
|
||||
of rebuilding from the Dockerfile.
|
||||
- The committed artifact reference is stored in per-bottle state so
|
||||
that the next `./cli.py resume <slug>` automatically uses the
|
||||
snapshot instead of rebuilding from the Dockerfile.
|
||||
- `mark_preserved` is called so the state dir survives the normal
|
||||
session-end cleanup.
|
||||
- A `docker save` hint is printed so operators know how to export the
|
||||
image for migration.
|
||||
- The command errors clearly on non-Docker backends (smolmachines does
|
||||
not expose a container-level commit API in its current CLI surface).
|
||||
- A backend-specific export hint is printed so operators know how to
|
||||
migrate the snapshot.
|
||||
- The command errors clearly on unsupported backends.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Smolmachines or macOS-container backend support.
|
||||
- macOS-container backend support.
|
||||
- Automatic commit on agent exit.
|
||||
- Image push to a remote registry.
|
||||
- Storing the image tag in the manifest or sharing it between operators.
|
||||
|
||||
## Design
|
||||
|
||||
### Image tag
|
||||
### Docker image tag
|
||||
|
||||
`bot-bottle-committed-<slug>:latest` — namespaced under `bot-bottle-`
|
||||
to match existing image naming conventions; `committed` distinguishes it
|
||||
@@ -68,13 +68,15 @@ directory:
|
||||
~/.bot-bottle/state/<identity>/
|
||||
metadata.json
|
||||
Dockerfile (capability-block override; optional)
|
||||
committed-image (committed image tag; optional)
|
||||
committed-image (committed artifact reference; optional)
|
||||
transcript/
|
||||
```
|
||||
|
||||
`bottle_state.committed_image_path(identity)` returns the path.
|
||||
`write_committed_image` / `read_committed_image` are the read/write
|
||||
helpers, matching the existing `per_bottle_dockerfile` pattern.
|
||||
helpers, matching the existing `per_bottle_dockerfile` pattern. Docker
|
||||
stores a Docker tag in this file; smolmachines stores the absolute path
|
||||
to the committed `.smolmachine` artifact.
|
||||
|
||||
### `commit` command
|
||||
|
||||
@@ -83,14 +85,15 @@ helpers, matching the existing `per_bottle_dockerfile` pattern.
|
||||
```
|
||||
|
||||
1. Resolve slug (arg or interactive picker from `enumerate_active_agents`).
|
||||
2. Check metadata: if `backend` is set and is not `docker`, die with a
|
||||
clear "not supported" error.
|
||||
3. Derive container name: `bot-bottle-<slug>` (matches the agent
|
||||
provision plan's `instance_name` convention).
|
||||
4. Run `docker commit <container> bot-bottle-committed-<slug>:latest`.
|
||||
5. Write the image tag to `~/.bot-bottle/state/<slug>/committed-image`.
|
||||
2. Check metadata and branch by backend.
|
||||
3. For Docker, derive container name `bot-bottle-<slug>` and run
|
||||
`docker commit <container> bot-bottle-committed-<slug>:latest`.
|
||||
4. For smolmachines, derive machine name `bot-bottle-<slug>` and run
|
||||
`smolvm pack create --from-vm <machine> -o ~/.bot-bottle/state/<slug>/committed-smolmachine`.
|
||||
5. Write the Docker image tag or smolmachine artifact path to
|
||||
`~/.bot-bottle/state/<slug>/committed-image`.
|
||||
6. Call `mark_preserved(<slug>)` so the state dir survives session-end.
|
||||
7. Print the resume hint and a `docker save` export example.
|
||||
7. Print the resume hint and a backend-specific export example.
|
||||
|
||||
### Resume from committed image
|
||||
|
||||
@@ -120,6 +123,22 @@ If the committed image has been deleted from the local daemon (e.g.
|
||||
after `docker rmi` or a `docker system prune`), the launch falls back
|
||||
to a normal Dockerfile build, matching the pre-commit behavior.
|
||||
|
||||
### Resume from committed smolmachine
|
||||
|
||||
`bot_bottle/backend/smolmachines/launch.py` checks the committed
|
||||
reference before the normal Docker build -> pack cache path:
|
||||
|
||||
```python
|
||||
committed = read_committed_image(plan.slug)
|
||||
if committed and Path(committed).is_file():
|
||||
return Path(committed)
|
||||
return _ensure_smolmachine(plan.agent_image, dockerfile=plan.agent_dockerfile_path)
|
||||
```
|
||||
|
||||
The returned path is passed to `smolvm machine create --from`, so the
|
||||
resumed VM boots from the committed snapshot. If the artifact has been
|
||||
deleted, launch falls back to the normal build and pack flow.
|
||||
|
||||
## Testing strategy
|
||||
|
||||
- Unit tests for `write_committed_image` / `read_committed_image` in
|
||||
@@ -127,10 +146,14 @@ to a normal Dockerfile build, matching the pre-commit behavior.
|
||||
pattern.
|
||||
- Unit tests for `commit_container` in `tests/unit/test_docker_util_image.py`,
|
||||
mocking `subprocess.run` and asserting on the `docker commit` argv.
|
||||
- Unit tests for `cmd_commit` argument parsing and the "unsupported
|
||||
backend" error path, mocking `enumerate_active_agents` and
|
||||
`commit_container`.
|
||||
- Unit tests for `cmd_commit` argument parsing, Docker commit,
|
||||
smolmachines pack, and the unsupported backend error path, mocking
|
||||
`enumerate_active_agents`, `commit_container`, and
|
||||
`pack_create_from_vm`.
|
||||
- Unit tests for the launch-step committed-image branch: patch
|
||||
`read_committed_image` to return a tag, patch `image_exists` to return
|
||||
True, and assert that `build_image` is not called and `plan.image` is
|
||||
overridden.
|
||||
- Unit tests for the smolmachines launch-step committed-artifact branch:
|
||||
patch `read_committed_image` to return an existing path and assert the
|
||||
normal `_ensure_smolmachine` path is skipped.
|
||||
|
||||
Reference in New Issue
Block a user