feat(smolmachines): end-to-end launch + Bottle.exec + smoke + probes (PRD 0023 chunk 2d) #67
Reference in New Issue
Block a user
Delete Branch "prd-0023-chunk-2d-launch"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Final sub-PR of chunk 2. End-to-end launch flow for the smolmachines backend: per-bottle docker bridge → sidecar bundle → smolvm guest → exec round trip → teardown. PRD 0023's two acceptance probes (localhost-reach + egress-port-bypass) included.
End-to-end smoke + both probes green locally on macOS with smolvm 0.8.0 + docker.
Two architecture pivots forced by empirical smolvm 0.8.0
--fromand--smolfileare mutually exclusive. We need--fromto avoid the registry-pull race that hit onmachine_start(libkrun agent's network attempt got refused by macOS withconnect: permission deniedon IPv6). So the Smolfile is dropped entirely; per-bottle env + allow_cidrs flow as CLI flags (--allow-cidr CIDR,-e K=V) directly tomachine_create. Thesmolfile.pyrenderer + its tests are deleted as dead code.smolvm pack create --imagedoesn't pull from local docker. Only OCI registries via crane.claude-bottle:latestlives in the local docker daemon and isn't reachable that way. Chunk 2d uses analpine:latestplaceholder; the agent-image-conversion gap is a chunk-4 concern.API
launchuses anExitStackso partial-bringup failures unwind cleanly. The lifecycle is:create_bundle_network → start_bundle → machine_create --from → machine_start, each registered for reverse teardown on context exit.Tests
--allow-cidr/-eflag support; one test renamed).tests/integration/test_smolmachines_launch.py, gated on macOS + smolvm + docker + notGITEA_ACTIONS:test_smoke_exec_echo—bottle.exec("echo hello-from-vm")round-trips with the right stdout/returncode.test_localhost_reach_probe— agent dials127.0.0.1:9→ connect refused. The regression test for the gap the PRD design pivot was about.test_egress_port_bypass_probe— agent dials<bundle-ip>:9099→ connect refused. Chunk 2d's bundle isn't running any daemons (daemons_csv=""), so nothing listens on :9099 anyway; chunk 3 preserves this once egress is up but bound to127.0.0.1inside the bundle.What's left
PRD 0023 chunks 3-5:
127.0.0.1:9099inside the bundle (one-line change to PRD 0024's bundle entrypoint; chunk 2d's daemons_csv="" defers this).CLAUDE_BOTTLE_BACKEND=smolmachines.Files
launch.py,tests/integration/test_smolmachines_launch.py.smolfile.py,tests/unit/test_smolfile.py.backend.py(real launch wired),bottle.py(exec/cp/exec_claude implementations),bottle_plan.py(agent_from_path + guest_env),prepare.py(pack_create + cache),smolvm.py(--from/--allow-cidr/-eflag support).End-to-end launch flow for the smolmachines backend. Brings up the per-bottle docker bridge + sidecar bundle, creates and starts the smolvm guest pointed at the bundle's pinned IP via TSI's `--allow-cidr <bundle-ip>/32`, yields a SmolmachinesBottle handle that routes exec/cp through `smolvm machine exec / cp`, tears everything down on context exit. launch.py: - ExitStack-managed: create_bundle_network → start_bundle → machine_create → machine_start (each registered for reverse teardown). - daemons_csv="" for chunk 2d — bundle init logs "no daemons selected" and idles. Real daemon bringup with inner-Plan-driven env + volumes lands in chunk 4. bottle.py: - SmolmachinesBottle.exec → smolvm.machine_exec (captured). - SmolmachinesBottle.exec_claude → direct subprocess.run with inherited TTY for interactive sessions. - SmolmachinesBottle.cp_in → smolvm.machine_cp. Architecture pivots forced by smolvm 0.8.0's CLI shape: 1. `--from <smolmachine>` and `--smolfile <toml>` are MUTUALLY EXCLUSIVE in smolvm 0.8.0. We need --from to avoid the registry-pull race that bit us on machine_start (libkrun agent's network attempt got refused by macOS with "connect: permission denied" on IPv6). So Smolfile is dropped entirely; per-bottle env + allow_cidrs flow as CLI flags (`--allow-cidr CIDR`, `-e K=V`) directly to machine_create. 2. `smolvm pack create --image` doesn't pull from the local docker daemon — only OCI registries via crane. The real claude-bottle:latest image lives in the local docker daemon and isn't reachable that way. Chunk 2d ships with an alpine placeholder; the agent-image-conversion gap belongs to chunk 4 (push the image to a registry, or smolvm grows a docker-daemon transport). Other changes: - machine_create grew `image=` / `from_path=` / `allow_cidrs=` / `env=` kwargs; smolfile= dropped. - bottle_plan: smolfile_path → agent_from_path + guest_env. - prepare: pack_create against `alpine:latest`, cached under ~/.cache/claude-bottle/smolmachines/ keyed by image ref. - Deleted smolfile.py + test_smolfile.py (dead code now). Tests: - Unit: 540 passing (smolvm wrapper grew 4 new flag forms; one test renamed to reflect --from + --allow-cidr + -e combo). - Integration: 3 new cases in tests/integration/ test_smolmachines_launch.py, gated on Darwin + smolvm on PATH + docker + not GITEA_ACTIONS: * smoke: bottle.exec("echo hello-from-vm") round-trips with the correct stdout + returncode. * localhost-reach probe: agent dials 127.0.0.1:9 → connect refused (TSI's <bundle-ip>/32 allowlist doesn't include loopback). The regression test for the gap the PRD design pivot was about. * egress-port-bypass probe: agent dials <bundle-ip>:9099 (egress's port) → connect refused. Chunk 2d has no daemons running so nothing's listening anyway; chunk 3 will preserve this property once egress is up but bound to 127.0.0.1 inside the bundle. End-to-end smoke + both probes green locally on macOS with smolvm 0.8.0. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>