refactor: split orchestrator/gateway images + remove the per-bottle sidecar architecture #385

Merged
didericis merged 5 commits from split-orchestrator-gateway-images into main 2026-07-14 17:20:05 -04:00
Collaborator

Splits the conflated bot-bottle-sidecars image into distinct orchestrator + gateway images, then removes the per-bottle companion-container architecture entirely and purges the sidecar name from the repo (per the thread).

What changed

Image split (#384)

  • Dockerfile.gatewaybot-bottle-gateway:latest (renamed from Dockerfile.sidecars, contents unchanged) — the egress/git-gate/supervise data plane.
  • Dockerfile.orchestratorbot-bottle-orchestrator:latest — lean python:3.12-slim; the stdlib-only bot_bottle package is bind-mounted, so the #381 source-hash recreate semantics are unchanged.
  • OrchestratorService takes distinct image (control plane) + gateway_image (data plane) instead of feeding one image to both.

De-sidecar

  • Delete backend/docker/sidecar_bundle.py and docker's dead per-bottle compose renderer (live path uses consolidated_agent_compose).
  • Rename sidecar_init.pygateway_init.py; env var BOT_BOTTLE_SIDECAR_DAEMONSBOT_BOTTLE_GATEWAY_DAEMONS; egress_sidecar_env_entriesegress_gateway_env_entries; SIDECAR_PORTSGATEWAY_PORTS.
  • Disable the firecracker + macos-container backends (they launched a per-bottle companion): launch() fails closed, enumerate returns empty, cleanup/egress_apply drop the companion-container references. Firecracker's consolidated relaunch is #354; macos follows.
  • Purge sidecar from all live code, tests, current docs, scripts, and nix. Only docs/prds + docs/research keep it as dated history.

Verification

  • Full unit suite green (bar pre-existing /bin/sleep-missing env errors in test_gateway_init, unrelated).
  • Docker integration: gateway singleton, launch broker, real two-bottle multitenant isolation, and the gateway-image build all pass.
  • Both images build; the lean orchestrator image runs python3 -m bot_bottle.orchestrator; ensure_running brings the control plane up healthy with the orchestrator on bot-bottle-orchestrator:latest and the gateway on bot-bottle-gateway:latest.

Closes #384. macos-container / firecracker are intentionally disabled here (fc re-enabled by #354).

🤖 Generated with Claude Code

https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck

Splits the conflated `bot-bottle-sidecars` image into distinct **orchestrator** + **gateway** images, then removes the per-bottle **companion-container** architecture entirely and purges the `sidecar` name from the repo (per the thread). ## What changed **Image split (#384)** - `Dockerfile.gateway` → `bot-bottle-gateway:latest` (renamed from `Dockerfile.sidecars`, contents unchanged) — the egress/git-gate/supervise data plane. - `Dockerfile.orchestrator` → `bot-bottle-orchestrator:latest` — lean `python:3.12-slim`; the stdlib-only `bot_bottle` package is bind-mounted, so the #381 source-hash recreate semantics are unchanged. - `OrchestratorService` takes distinct `image` (control plane) + `gateway_image` (data plane) instead of feeding one image to both. **De-sidecar** - Delete `backend/docker/sidecar_bundle.py` and docker's dead per-bottle compose renderer (live path uses `consolidated_agent_compose`). - Rename `sidecar_init.py` → `gateway_init.py`; env var `BOT_BOTTLE_SIDECAR_DAEMONS` → `BOT_BOTTLE_GATEWAY_DAEMONS`; `egress_sidecar_env_entries` → `egress_gateway_env_entries`; `SIDECAR_PORTS` → `GATEWAY_PORTS`. - **Disable** the firecracker + macos-container backends (they launched a per-bottle companion): `launch()` fails closed, `enumerate` returns empty, `cleanup`/`egress_apply` drop the companion-container references. Firecracker's consolidated relaunch is #354; macos follows. - Purge `sidecar` from all live code, tests, current docs, scripts, and nix. Only `docs/prds` + `docs/research` keep it as dated history. ## Verification - Full unit suite green (bar pre-existing `/bin/sleep`-missing env errors in `test_gateway_init`, unrelated). - Docker integration: gateway singleton, launch broker, **real two-bottle multitenant isolation**, and the gateway-image build all pass. - Both images build; the lean orchestrator image runs `python3 -m bot_bottle.orchestrator`; `ensure_running` brings the control plane up healthy with the orchestrator on `bot-bottle-orchestrator:latest` and the gateway on `bot-bottle-gateway:latest`. Closes #384. macos-container / firecracker are intentionally disabled here (fc re-enabled by #354). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
didericis-claude added 1 commit 2026-07-14 16:24:44 -04:00
refactor(orchestrator): split conflated sidecar image into orchestrator + gateway images
lint / lint (push) Successful in 2m19s
test / unit (pull_request) Successful in 1m14s
test / integration (pull_request) Successful in 32s
test / coverage (pull_request) Successful in 1m28s
96b84eb84d
One image — `bot-bottle-sidecars:latest`, built from `Dockerfile.sidecars` —
served two unrelated roles: the egress/git-gate/supervise *data plane* and
the orchestrator *control plane* (which ran the same image with the entrypoint
overridden to `python3 -m bot_bottle.orchestrator`). The control plane is
stdlib-only, so it needed none of the mitmproxy/git/gitleaks payload it was
riding on — while being the most secret-dense process on the host (PRD 0070's
"secret concentration").

Split into two purpose-built images:

- `Dockerfile.gateway` -> `bot-bottle-gateway:latest` — the data plane
  (renamed from Dockerfile.sidecars; identical contents).
- `Dockerfile.orchestrator` -> `bot-bottle-orchestrator:latest` — a lean
  `python:3.12-slim` runtime; the bind-mounted `bot_bottle` package supplies
  the code (so the #381 source-hash recreate semantics are unchanged).

`OrchestratorService` now takes distinct `image` (control plane, default
`ORCHESTRATOR_IMAGE`) and `gateway_image` (data plane, default `GATEWAY_IMAGE`)
instead of feeding one `self.image` to both, and builds the lean image
(build-if-missing) before starting the container. The per-bottle bundle
constants in `backend/docker/sidecar_bundle.py` now alias the gateway
constants so a bundle and the shared gateway can never drift onto different
images. The `bot-bottle-sidecars` *image* name and `Dockerfile.sidecars` are
gone; the per-bottle *container* name prefix (`bot-bottle-sidecars-<slug>`) is
intentionally left for a separate change.

Verified end-to-end: both images build; the lean image runs the control plane;
`ensure_running` brings up the orchestrator on `bot-bottle-orchestrator:latest`
and the gateway on `bot-bottle-gateway:latest` (distinct images) and reports
healthy.

Closes #384.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
didericis-claude added the Kind/Enhancement label 2026-07-14 16:25:01 -04:00
Owner

We should strip out ALL the per bottle sidecar container code in this as well, since we’re moving away from that architecture.

We should also either rename or remove “sidecar_bundle”: at the end of this work, there should be nothing in the repo mentioning or named “sidecar”, and no per agent companion container

We should strip out ALL the per bottle sidecar container code in this as well, since we’re moving away from that architecture. We should also either rename or remove “sidecar_bundle”: at the end of this work, there should be nothing in the repo mentioning or named “sidecar”, and no per agent companion container
Author
Collaborator

Agreed — the per-bottle companion container is the old architecture, so this fits the consolidation. I'll fold it into this PR. Scoping it concretely so we agree on the boundary before I rip things out:

1. Remove the per-bottle sidecar container code (the architecture)

  • backend/docker/compose.py — the _sidecar_service (the per-bottle bundle service) and its wiring; the Docker live path already uses consolidated_agent_compose (agent-only) + the shared gateway, so this is dead weight there.
  • backend/docker/sidecar_bundle.py — deleted (its image constants already alias the gateway; sidecar_bundle_container_name / the bot-bottle-sidecars-<slug> container go away).
  • The bot-bottle-sidecars-* container-name prefixes + discovery/cleanup in backend/{docker,firecracker,macos_container}/{enumerate,cleanup}.py.

2. macos-container + firecracker still launch a per-bottle bundle on main

Removing the per-bottle path takes their current gateway mechanism with it. Per your earlier "ok to disable broken macos/firecracker during the refactor", I'll disable those per-bottle launch paths (fail-closed with a pointer) rather than leave dead sidecar code. Firecracker's real replacement is the consolidated shared gateway on #354; macos follows the same consolidation later.

3. Purge the name

  • Rename bot_bottle/sidecar_init.pygateway_init.py (it's the gateway image's PID-1 supervisor now) + update Dockerfile.gateway and tests.
  • Sweep remaining sidecar identifiers/comments in shared modules (egress / git_gate / supervise / paths / bottle_state), SIDECAR_PORTS, _SIDECAR_PREFIX, etc., to gateway-oriented names.
  • Update current docs (README.md, AGENTS.md, tests/README.md, docs/ci.md).

One boundary to confirm

docs/prds/* (~45) and docs/research/* (~150) mention "sidecar" as dated, point-in-time records (e.g. PRD 0024 is literally "consolidate sidecar bundle"). My default is to leave those as history and purge only live code + current docs. If you literally want zero occurrences repo-wide I'll rewrite those too — but that rewrites shipped design history, so I'd rather not unless you say so.

Given the size (~100 files) I'll push it in reviewable commits on this branch. Sound right, and — leave historical docs, or purge everything?

Agreed — the per-bottle companion container *is* the old architecture, so this fits the consolidation. I'll fold it into this PR. Scoping it concretely so we agree on the boundary before I rip things out: ## 1. Remove the per-bottle sidecar **container** code (the architecture) - `backend/docker/compose.py` — the `_sidecar_service` (the per-bottle bundle service) and its wiring; the Docker live path already uses `consolidated_agent_compose` (agent-only) + the shared gateway, so this is dead weight there. - `backend/docker/sidecar_bundle.py` — deleted (its image constants already alias the gateway; `sidecar_bundle_container_name` / the `bot-bottle-sidecars-<slug>` container go away). - The `bot-bottle-sidecars-*` container-name prefixes + discovery/cleanup in `backend/{docker,firecracker,macos_container}/{enumerate,cleanup}.py`. ## 2. macos-container + firecracker still launch a per-bottle bundle on `main` Removing the per-bottle path takes their current gateway mechanism with it. Per your earlier "ok to disable broken macos/firecracker during the refactor", I'll **disable those per-bottle launch paths** (fail-closed with a pointer) rather than leave dead sidecar code. Firecracker's real replacement is the consolidated shared gateway on #354; macos follows the same consolidation later. ## 3. Purge the name - Rename `bot_bottle/sidecar_init.py` → `gateway_init.py` (it's the gateway image's PID-1 supervisor now) + update `Dockerfile.gateway` and tests. - Sweep remaining `sidecar` identifiers/comments in shared modules (egress / git_gate / supervise / paths / bottle_state), `SIDECAR_PORTS`, `_SIDECAR_PREFIX`, etc., to gateway-oriented names. - Update current docs (`README.md`, `AGENTS.md`, `tests/README.md`, `docs/ci.md`). ## One boundary to confirm `docs/prds/*` (~45) and `docs/research/*` (~150) mention "sidecar" as **dated, point-in-time records** (e.g. PRD 0024 is literally "consolidate sidecar bundle"). My default is to **leave those as history** and purge only live code + current docs. If you literally want zero occurrences repo-wide I'll rewrite those too — but that rewrites shipped design history, so I'd rather not unless you say so. Given the size (~100 files) I'll push it in reviewable commits on this branch. Sound right, and — leave historical docs, or purge everything?
Owner

@didericis-claude can leave the historical docs

@didericis-claude can leave the historical docs
Author
Collaborator

Confirmed — leaving docs/prds + docs/research as history; purging live code + current docs.

One sequencing snag I hit tracing the removal, want your call before I gut anything:

The companion-container removal is the macos + firecracker launch.py rewrite. Their launch flow literally is start sidecar bundle → stage git-gate → start agent, and they're the only live creators of the bot-bottle-sidecars-<slug> container + setters of BOT_BOTTLE_SIDECAR_DAEMONS / egress_sidecar_env_entries. Deleting sidecar_bundle.py breaks their imports, so removing the companion container means neutralizing both backends in the same change.

The snag: #354 is already migrating firecracker off the per-bottle bundle onto the consolidated shared gateway — i.e. #354 removes fc's sidecar path as part of its cutover. If I also rip it out here, the two PRs conflict and fc breaks on main until #354 lands.

Recommendation (least conflict):

  1. Merge this PR's image split as-is (done + verified end-to-end).
  2. Firecracker's per-bottle removal rides #354 (that's its cutover anyway).
  3. A focused follow-up (rebased on #354) does the rest of the de-sidecar: delete sidecar_bundle.py, strip docker's dead per-bottle compose (bottle_plan_to_compose/_sidecar_bundle_service — already unused on the live path), disable macos (fail-closed until it gets the consolidated gateway), rename sidecar_init.pygateway_init.py, and purge the remaining sidecar identifiers + current docs.

Alternative if you'd rather not wait: I gut macos and fc here now, accept fc broken on main until #354, and #354 rebases onto this. Doable, just more churn/conflict.

Which do you want — fold fc's removal into #354 (recommended), or force it all into this PR now?

Confirmed — leaving `docs/prds` + `docs/research` as history; purging live code + current docs. One sequencing snag I hit tracing the removal, want your call before I gut anything: **The companion-container removal *is* the macos + firecracker `launch.py` rewrite.** Their launch flow literally is *start sidecar bundle → stage git-gate → start agent*, and they're the only live creators of the `bot-bottle-sidecars-<slug>` container + setters of `BOT_BOTTLE_SIDECAR_DAEMONS` / `egress_sidecar_env_entries`. Deleting `sidecar_bundle.py` breaks their imports, so removing the companion container means neutralizing both backends in the same change. **The snag:** #354 is *already* migrating firecracker off the per-bottle bundle onto the consolidated shared gateway — i.e. #354 removes fc's sidecar path as part of its cutover. If I also rip it out here, the two PRs conflict and fc breaks on `main` until #354 lands. **Recommendation (least conflict):** 1. Merge this PR's image split as-is (done + verified end-to-end). 2. Firecracker's per-bottle removal rides #354 (that's its cutover anyway). 3. A focused follow-up (rebased on #354) does the rest of the de-sidecar: delete `sidecar_bundle.py`, strip docker's dead per-bottle compose (`bottle_plan_to_compose`/`_sidecar_bundle_service` — already unused on the live path), **disable macos** (fail-closed until it gets the consolidated gateway), rename `sidecar_init.py`→`gateway_init.py`, and purge the remaining `sidecar` identifiers + current docs. Alternative if you'd rather not wait: I gut macos **and** fc here now, accept fc broken on `main` until #354, and #354 rebases onto this. Doable, just more churn/conflict. Which do you want — fold fc's removal into #354 (recommended), or force it all into this PR now?
Owner

@didericis-claude gut it now: leaving it in muddies the context for ai and creates more work to try and preserve codepaths we’re changing

@didericis-claude gut it now: leaving it in muddies the context for ai and creates more work to try and preserve codepaths we’re changing
didericis added 3 commits 2026-07-14 17:07:59 -04:00
Part of the de-sidecar cleanup (#385 discussion): the per-bottle companion
container is the old architecture.

- Rename `bot_bottle/sidecar_init.py` → `gateway_init.py` (it's the gateway
  image's PID-1 supervisor); env var `BOT_BOTTLE_SIDECAR_DAEMONS` →
  `BOT_BOTTLE_GATEWAY_DAEMONS`; log prefix `sidecar-init:` → `gateway-init:`.
  Update Dockerfile.gateway COPY/ENTRYPOINT and the test.
- Remove the dead per-bottle compose renderer from `backend/docker/compose.py`
  (`bottle_plan_to_compose`, `_sidecar_bundle_service`, `_agent_service`, and
  the network/bind/proxy helpers). Docker's live path uses
  `consolidated_agent_compose`; only the compose *lifecycle* helpers
  (up/down/ls/write) remain. Trim `test_compose.py` to the surviving helpers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
The per-agent companion container (the egress/git-gate/supervise data
plane run once per bottle) is the pre-consolidation architecture. Remove
it and disable the backends that still depend on it, per the #385 thread.

- Delete `backend/docker/sidecar_bundle.py`; docker's live path uses the
  consolidated shared gateway, not a per-bottle bundle.
- Disable the firecracker and macos-container backends: their `launch()`
  fails closed (they launched a per-bottle companion; firecracker's
  consolidated relaunch is #354, macos follows). Their `enumerate` return
  empty and `cleanup` drop the companion-container discovery (firecracker
  keeps VMM/run-dir cleanup).
- Fail-close both backends' `egress_apply` reload (it signalled the
  per-bottle container); consolidated egress policy resolves per-request
  against the orchestrator, so gateway-side apply is a follow-up.
- Rename `egress_sidecar_env_entries` → `egress_gateway_env_entries`,
  `SIDECAR_PORTS` → `GATEWAY_PORTS`.
- Move the shared DockerBottlePlan fixture to `tests/unit/_docker_bottle_plan.py`;
  delete tests for the removed launch paths; update cleanup/egress-apply tests.

Docker consolidated launch verified end-to-end (multitenant isolation
integration test passes). macos/firecracker are intentionally disabled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
refactor(de-sidecar): purge the "sidecar" name from live code, tests, and current docs
lint / lint (push) Failing after 2m3s
test / unit (pull_request) Successful in 1m11s
test / integration (pull_request) Successful in 26s
test / coverage (pull_request) Successful in 1m22s
09393b354b
Completes the de-sidecar cleanup: no live code, test, current doc,
script, or nix file mentions or is named "sidecar" any more. Only the
dated PRD/research docs keep the term as historical record (agreed on
the #385 thread).

- Rename `sidecar_init.py`→`gateway_init.py` was done earlier; this pass
  sweeps the remaining descriptive uses: the egress / git-gate / supervise
  components are the gateway's *daemons*, the shared container is the
  *gateway*, the old per-bottle container was the *companion container*.
- Rename `tests/integration/test_sidecar_bundle_image.py`→`test_gateway_image.py`
  and its class; update `docs/ci.md` + `tests/README.md` for the renamed/
  removed integration tests.
- `SIDECAR_PORTS` shell var in `scripts/firecracker-netpool.sh`→`GATEWAY_PORTS`.

Full unit suite green (bar the pre-existing `/bin/sleep`-missing env
errors in test_gateway_init); docker integration — gateway singleton,
broker, real two-bottle multitenant isolation, and the gateway-image
build — all pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
didericis-claude changed title from refactor(orchestrator): distinct orchestrator + gateway images (retire bot-bottle-sidecars) to refactor: split orchestrator/gateway images + remove the per-bottle sidecar architecture 2026-07-14 17:08:27 -04:00
Author
Collaborator

Done — gutted it now, in this PR. Four commits on the branch:

  1. split conflated sidecar image into orchestrator + gateway images (#384)
  2. rename sidecar_init→gateway_init, drop docker's dead per-bottle compose renderer
  3. remove the per-bottle companion-container architecture (delete sidecar_bundle.py; disable fc + macos fail-closed; fail-close their egress_apply reload)
  4. purge the "sidecar" name from live code, tests, and current docs

sidecar occurrences outside docs/prds + docs/research: 0. Historical design docs keep the term as record, as agreed.

macos + firecracker are disabled (fail-closed launch()) since their whole launch flow was start companion container → stage git-gate → start agent; firecracker's consolidated relaunch is #354, macos follows once it grows the shared gateway.

Verified: unit suite green (bar the pre-existing /bin/sleep env errors), and docker integration — gateway singleton, broker, real two-bottle multitenant isolation, gateway-image build — all pass.

Done — gutted it now, in this PR. Four commits on the branch: 1. `split conflated sidecar image into orchestrator + gateway images` (#384) 2. `rename sidecar_init→gateway_init, drop docker's dead per-bottle compose renderer` 3. `remove the per-bottle companion-container architecture` (delete `sidecar_bundle.py`; disable fc + macos fail-closed; fail-close their egress_apply reload) 4. `purge the "sidecar" name from live code, tests, and current docs` **`sidecar` occurrences outside `docs/prds` + `docs/research`: 0.** Historical design docs keep the term as record, as agreed. macos + firecracker are **disabled** (fail-closed `launch()`) since their whole launch flow was *start companion container → stage git-gate → start agent*; firecracker's consolidated relaunch is #354, macos follows once it grows the shared gateway. Verified: unit suite green (bar the pre-existing `/bin/sleep` env errors), and docker integration — gateway singleton, broker, **real two-bottle multitenant isolation**, gateway-image build — all pass.
didericis added 1 commit 2026-07-14 17:12:20 -04:00
fix(tests): pyright strict — export _plan fixture, drop unused imports
test / unit (pull_request) Successful in 1m10s
test / integration (pull_request) Successful in 25s
test / coverage (pull_request) Successful in 1m20s
lint / lint (push) Successful in 2m16s
test / unit (push) Successful in 1m12s
test / integration (push) Successful in 26s
test / coverage (push) Successful in 1m19s
Update Quality Badges / update-badges (push) Successful in 1m20s
56d879f0b3
- `tests/unit/_docker_bottle_plan.py`: add `__all__ = ["_plan"]` so the
  underscore-prefixed fixture isn't flagged reportUnusedFunction.
- `tests/unit/test_egress_apply.py`: drop `SimpleNamespace` / `patch`
  imports left unused after the reload test became fail-closed.

`pyright .` → 0 errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
didericis merged commit 56d879f0b3 into main 2026-07-14 17:20:05 -04:00
didericis deleted branch split-orchestrator-gateway-images 2026-07-14 17:20:18 -04:00
Sign in to join this conversation.