feat(bottles): implement bottle factory abstraction per PRD 0003
test / run tests/run_tests.py (pull_request) Successful in 16s

Introduce claude_bottle/bottles/ with a Bottle Protocol and a
get_bottle_factory() that dispatches on CLAUDE_BOTTLE_PLATFORM
(default "docker"). Move every Docker-specific subprocess.run call
from cli/start.py, plus the orchestration of build, networks, the
pipelock sidecar, container launch, and per-container provisioning
(prompt, skills, ssh, .git), into create_docker_bottle.

Drop bottles[].runtime from the manifest schema. Auto-detect whether
gVisor is registered with the daemon and pass --runtime=runsc when it
is; the preflight shows the resolved runtime so the choice is visible.
Manifests still carrying 'runtime' get a clear error pointing at the
auto-detect behavior, rather than silent ignore.

Out of scope: cli/cleanup.py and cli/list.py still call docker
directly. They enumerate active bottles across the host, which is a
separate concern from "create a bottle" and is left for a follow-up
that introduces a list_active/cleanup primitive on the factory.
This commit is contained in:
2026-05-10 22:15:05 -04:00
parent d5c056f36e
commit d75cc9325f
8 changed files with 468 additions and 240 deletions
+4 -10
View File
@@ -45,9 +45,10 @@ like `cloudflare-dns.com` would have to be on the allowlist for the
agent to reach it at all. The container itself adds a layer between
the agent and the host, but the v1 design leans more on secret
minimization and egress allowlisting than on the container as a
hardened boundary. Linux hosts can opt into [gVisor](https://gvisor.dev/)
per bottle (see `runtime` in the manifest below) for a userspace
syscall barrier; the broader v2 discussion lives in
hardened boundary. On Linux hosts where [gVisor](https://gvisor.dev/)
is registered with Docker, claude-bottle auto-detects it and launches
every bottle under `runsc` for a userspace syscall barrier — no
manifest configuration required. The broader v2 discussion lives in
`docs/research/stronger-isolation-alternatives.md`.
The egress proxy and OAuth-token handling below are the load-bearing
@@ -76,13 +77,6 @@ project entries overriding home entries on key conflict).
{
"bottles": {
"gitea-dev": {
// Container runtime for the agent. Default "runc"; set to
// "runsc" on Linux hosts to launch the agent under gVisor for
// a userspace syscall barrier between the agent and the host
// kernel. claude-bottle verifies the runtime is registered with
// Docker before launch; gVisor is not available on macOS.
"runtime": "runsc",
"env": {
"GITEA_TOKEN": "?paste your Gitea API token",
"GITHUB_TOKEN": "${GH_PAT}",