116 lines
5.0 KiB
Markdown
116 lines
5.0 KiB
Markdown
# PRD prd-new: smolmachines sidecar VM
|
|
|
|
- **Status:** Active
|
|
- **Author:** codex
|
|
- **Created:** 2026-07-09
|
|
- **Issue:** #332
|
|
|
|
## Summary
|
|
|
|
Run the smolmachines backend's trusted sidecar bundle as its own smolVM instead
|
|
of a Docker container. A bottle then consists of an agent VM plus a sidecar VM,
|
|
with the agent VM's TSI allowlist limited to the per-bottle agent-facing
|
|
sidecar surface.
|
|
|
|
## Problem
|
|
|
|
The smolmachines backend currently runs the agent in smolVM but keeps
|
|
egress/git-gate/supervise in a Docker sidecar bundle. That hybrid launch path
|
|
keeps Docker in the trusted runtime path and leaves smolmachines with a
|
|
different isolation boundary than its agent VM design suggests.
|
|
|
|
The existing Docker sidecar path also uses Docker port publishing to bind only
|
|
agent-facing services to the per-bottle host address. TSI is IP-only, so the
|
|
smolVM replacement must preserve that scoping: publishing sidecar services on
|
|
generic host localhost would let the agent reach unrelated host services or
|
|
other bottle sidecars if those services share the allowed address.
|
|
|
|
## Goals / Success Criteria
|
|
|
|
1. `BOT_BOTTLE_BACKEND=smolmachines ./cli.py start <agent>` runs with both the
|
|
agent and sidecar as smolVMs.
|
|
2. The smolmachines launch path no longer uses `docker run` for the sidecar
|
|
bundle.
|
|
3. Agent-facing egress, git-gate, and supervise endpoints are exposed only
|
|
through the bottle's own published sidecar surface.
|
|
4. Internal sidecar-only ports are not reachable from the agent VM.
|
|
5. The agent VM TSI allowlist remains fail-closed and limited to the per-bottle
|
|
address.
|
|
6. Sidecar config, secrets, and state are delivered to the sidecar VM without
|
|
exposing provider or forge credentials to the agent VM.
|
|
7. Teardown removes both VMs and any host-side published port state.
|
|
|
|
## Non-goals
|
|
|
|
- Rewriting the egress, git-gate, or supervise daemons.
|
|
- Changing the Docker backend's sidecar bundle behavior.
|
|
- Replacing the existing agent image build and pack pipeline except where shared
|
|
helper extraction is needed for sidecar image packing.
|
|
- Weakening the current smolmachines TSI allowlist checks.
|
|
|
|
## Design
|
|
|
|
The sidecar VM continues to use the existing sidecar bundle image and
|
|
`/app/sidecar_init.py` supervisor. The launch flow builds the sidecar image,
|
|
packs it into a `.smolmachine` artifact, creates a per-bottle sidecar VM from
|
|
that artifact, passes the same daemon-selection environment the Docker bundle
|
|
uses today, mounts or copies the same daemon-private config/state inputs, starts
|
|
the sidecar VM, wraps the sidecar VM's raw smolVM-published ports with a local
|
|
per-bottle forwarder, then starts the agent VM with
|
|
`--allow-cidr <per-bottle-address>/32`.
|
|
|
|
smolVM currently exposes guest ports as `HOST:GUEST` port pairs bound on host
|
|
loopback. It does not expose an address-scoped bind like
|
|
`127.0.0.16:<port>:9099`. Because TSI is IP-only, bot-bottle must not advertise
|
|
those raw loopback ports directly to the agent. Instead, bot-bottle runs one
|
|
small stdlib Python TCP forwarder process per bottle:
|
|
|
|
```text
|
|
agent VM
|
|
-> TSI allows only <bottle-address>/32
|
|
-> bot-bottle forwarder bound to <bottle-address>:<random-port>
|
|
-> raw smolVM-published sidecar port on 127.0.0.1:<raw-port>
|
|
-> sidecar VM service
|
|
```
|
|
|
|
Agent-facing URLs are stamped from the forwarder ports, never the raw smolVM
|
|
ports:
|
|
|
|
- `HTTP_PROXY` / `HTTPS_PROXY` point at the published egress proxy port.
|
|
- `GIT_GATE_URL` points at the published git HTTP port when git-gate is enabled.
|
|
- `MCP_SUPERVISE_URL` points at the published supervise port when supervise is
|
|
enabled.
|
|
|
|
Internal sidecar-only services stay bound inside the sidecar VM and are not
|
|
published.
|
|
|
|
### Forwarder constraints
|
|
|
|
- Reject wildcard listener binds (`0.0.0.0`, `::`) and generic localhost
|
|
listener binds (`127.0.0.1`, `::1`) for agent-facing forwards.
|
|
- Accept only fixed loopback targets selected by launch; the client cannot
|
|
choose or influence the forwarding target.
|
|
- Forward bytes transparently without parsing HTTP, CONNECT, TLS, Git, or MCP.
|
|
- Log lifecycle and endpoint metadata only; never log payload bytes.
|
|
- Create forwards only for egress, git HTTP when enabled, and supervise when
|
|
enabled.
|
|
- Fail closed if any listener cannot bind exactly the requested per-bottle
|
|
address.
|
|
|
|
## Implementation chunks
|
|
|
|
1. Extract the existing image-to-smolmachine pack helper so agent and sidecar
|
|
artifacts share the same cache and registry path.
|
|
2. Add a strict stdlib host TCP forwarder for per-bottle address-bound
|
|
forwarding to raw smolVM-published sidecar ports.
|
|
3. Add a sidecar VM launch spec and lifecycle helpers: pack, create, start,
|
|
publish/discover ports, stop, delete.
|
|
4. Switch smolmachines launch from Docker sidecar bundle lifecycle to sidecar VM
|
|
plus forwarder lifecycle.
|
|
5. Update egress apply / reload paths for the sidecar VM supervisor.
|
|
6. Add unit tests for argv shape, URL stamping, teardown, and fail-closed
|
|
behavior.
|
|
7. Add or update integration coverage for agent-to-sidecar reachability, host
|
|
localhost denial, other-bottle alias denial, internal port denial, and
|
|
teardown cleanup.
|