diff --git a/README.md b/README.md index 47d81b9..7c40db8 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,27 @@ The container is removed automatically when the session ends. If the script is killed with SIGKILL the exit trap won't fire and the container may be left running; remove it with `docker rm -f `. +### Smolmachines backend (experimental, macOS-only) + +A second backend runs the agent in a smolvm micro-VM (libkrun) with the +sidecar bundle still in Docker. Selected via +`CLAUDE_BOTTLE_BACKEND=smolmachines ./cli.py start `. Requires +`smolvm` on PATH (`curl -sSL https://smolmachines.com/install.sh | sh`). + +**Known limitation, v1:** smolvm's TSI uses macOS networking, and +Docker Desktop's container IPs aren't reachable from macOS, so the +smolmachines bottle dials the sidecar bundle through host loopback +port-forwards (`127.0.0.1:`). TSI filters by IP only, so the +allowlist is `127.0.0.1/32` — meaning the agent VM can reach **any +service bound to macOS's loopback**, not just the bundle's published +ports. Practical implication: while a smolmachines bottle is running, +host-local dev services (postgres on 5432, dev servers, etc.) are +reachable from inside the agent even if you intended them to be +host-private. The docker backend keeps the bottle on a `--internal` +docker network and doesn't have this issue. A future revision will +narrow this via a per-bottle loopback alias + host-side proxy (see +PRD 0023's "loopback scoping" section). + ## Manifest Bottles and agents live as Markdown files with YAML frontmatter under diff --git a/docs/prds/0023-smolmachines-backend.md b/docs/prds/0023-smolmachines-backend.md index 5e511a0..a98b1c5 100644 --- a/docs/prds/0023-smolmachines-backend.md +++ b/docs/prds/0023-smolmachines-backend.md @@ -600,6 +600,22 @@ PRD 0024's bundle image is a prerequisite — this PRD assumes the plan is to filter on a deterministic name prefix `claude-bottle-` + cross-reference with on-disk metadata under `state//`. +8. **Loopback scoping (Docker Desktop pivot).** The original + design pinned the bundle at a docker bridge IP and set TSI's + allowlist to `/32`. On Docker Desktop / macOS the + daemon runs inside its own Linux VM, so bridge IPs aren't + reachable from macOS networking — TSI's syscall impersonation + can't reach them. Resolution: publish each agent-facing bundle + port on host loopback (`-p 127.0.0.1::`) and set TSI to + `127.0.0.1/32`. **This widens the TSI allowlist to anything + bound to macOS's loopback** — postgres, dev servers, other + bottles' published ports, mDNSResponder, etc. The agent can't + reach them by intent, but TSI can't filter by port. Follow-up + to scope back: bind each bottle's bundle ports on a per-bottle + loopback alias (e.g. `127.0.0.2` for bottle A, `127.0.0.3` for + B) added via `ifconfig lo0 alias`, set TSI to that single /32. + Needs sudo for alias setup; a small daemon-or-script we ship + alongside the launcher could handle it. ## References