refactor(orchestrator): rename Sidecar -> Gateway for the consolidated data plane

Retire "sidecar" for the consolidated per-host path (PRD 0070 naming
decision): the orchestrator is the umbrella/control plane, and the
egress/git/supervise data-plane unit it runs is the "gateway".

- git mv sidecar.py -> gateway.py and the two integration + one unit test
  files; DockerSidecar->DockerGateway, Sidecar->Gateway,
  SidecarError->GatewayError, SIDECAR_*->GATEWAY_*, ensure_sidecar->
  ensure_gateway, sidecar_status->gateway_status, container name
  bot-bottle-orch-sidecar->bot-bottle-orch-gateway.
- Prose rename across broker/registry/egress/policy_resolver + PRD 0070.
- Preserved: the image name bot-bottle-sidecars, the
  BOT_BOTTLE_SIDECAR_IMAGE env var, Dockerfile.sidecars, and PRD 0069's
  own stage-name cross-references (that doc still uses "sidecar").

No behavior change. Full unit suite green (1679 tests; the 13
test_sidecar_init /bin/sleep errors are pre-existing NixOS-local noise).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
This commit is contained in:
2026-07-13 18:12:17 -04:00
parent 2cbb178f88
commit 00418a2834
17 changed files with 151 additions and 151 deletions
+20 -20
View File
@@ -10,8 +10,8 @@
## Summary
Replace the **per-bottle sidecar bundle** with a single **persistent,
per-host orchestrator**: one long-lived service that runs the sidecar
Replace the **per-bottle gateway bundle** with a single **persistent,
per-host orchestrator**: one long-lived service that runs the gateway
functions (egress / git-gate / supervise), coordinates with the console,
and brokers agent launches and teardown. It is **virtualized from the
start** using each backend's native isolation primitive — a Firecracker
@@ -22,13 +22,13 @@ container on the legacy backend — and is fronted by a single
## Motivation
Today each bottle spins up its own sidecar bundle (egress mitmproxy +
Today each bottle spins up its own gateway bundle (egress mitmproxy +
git-gate + supervise). That costs:
- **Resources.** N bottles → N heavy bundles booting and idling.
- **Operational churn.** Per-launch container/VM lifecycle for the
sidecars, a control path baked at launch and torn down at exit.
- **A blurry contract.** "How a bottle talks to its sidecar" is
gateways, a control path baked at launch and torn down at exit.
- **A blurry contract.** "How a bottle talks to its gateway" is
re-implemented per backend instead of being one agreed interface.
A per-host orchestrator collapses the first two and forces the third to
@@ -52,11 +52,11 @@ weaken, and the weakened ones must be mitigated by design, not hand-waved.
structured, auditable privileged core instead of a fat socket.
- **Attribution is enforced, not assumed.** Making source-IP identity a
first-class contract invariant (below) means each backend must *prove*
it, rather than the sidecar implicitly trusting network position.
it, rather than the gateway implicitly trusting network position.
### What gets weaker, and the mitigation
1. **Secret concentration.** Per-bottle sidecars isolate secrets at the
1. **Secret concentration.** Per-bottle gateways isolate secrets at the
process boundary — each holds only its bottle's tokens/keys. A host
orchestrator concentrates **every bottle's** egress tokens, git deploy
keys, and the console credential in one long-lived process. A single
@@ -75,7 +75,7 @@ weaken, and the weakened ones must be mitigated by design, not hand-waved.
fleet, plus launch authority, plus the console token.
- *Mitigation:* the orchestrator is itself confined (its own VM/container
with its own fail-closed egress); make it **restartable without killing
running agent VMs** (agents keep running; they briefly lose sidecar
running agent VMs** (agents keep running; they briefly lose gateway
connectivity until it's back); persist state to a host volume so a
restart re-adopts live bottles rather than losing them.
@@ -114,13 +114,13 @@ responsibility** and part of the contract:
- **Apple** — the host-only network.
If a backend can't honor the invariant, source-IP consolidation is not
safe there and that backend keeps per-bottle sidecars. The invariant is a
safe there and that backend keeps per-bottle gateways. The invariant is a
hard precondition, not an aspiration.
**Defense-in-depth — a per-bottle identity token.** On top of the
network-layer invariant, inject a per-bottle secret token into every
request the agent makes to the orchestrator (the agent already egresses
through the sidecar proxy, so this is cheap to add). It gives an
through the gateway proxy, so this is cheap to add). It gives an
**application-layer** proof of identity independent of the network layer:
- On **Firecracker** the `/31` + nft already make source IP unspoofable
@@ -133,7 +133,7 @@ Requirements: the token is **per-bottle, unguessable, and
non-cross-leakable** — a bottle can only ever prove it is *itself* (it
can't learn another bottle's token, given bottle isolation), so a hostile
agent gains nothing by presenting it. The orchestrator provisions the
token at launch and the sidecar requires it to attribute + authorize.
token at launch and the gateway requires it to attribute + authorize.
Note this hardens *attribution*, not *secret exposure*: it's app-layer, so
a compromised orchestrator still sees every token (that's the
concentration problem, addressed separately under Secret handling).
@@ -178,7 +178,7 @@ manifest anywhere a raw token value is accepted, discovered from
`AgentProvider`s are — because maintaining every forge/cloud/OAuth
provider in-tree is untenable. The orchestrator's vault mints via these
providers; but the abstraction is shippable independently and today's
per-bottle sidecars can use it too.
per-bottle gateways can use it too.
## Design
@@ -195,7 +195,7 @@ Three surfaces; only one is per-backend.
every host (no vsock/unix-socket portability caveats); a local unix
socket is a fine optimization, but HTTP is the wire contract.
2. **Data plane (agent → orchestrator)** — the egress / git / supervise
endpoints. Already agnostic today (agents dial `http://sidecar:9099`);
endpoints. Already agnostic today (agents dial `http://gateway:9099`);
only the *address* and *how packets get there* are per-backend.
3. **Launch / wire (orchestrator → backend)** — the irreducibly
backend-specific part; lives on `BottleBackend`.
@@ -249,7 +249,7 @@ forged one from a compromised co-located component. The orchestrator signs;
the broker verifies with the orchestrator's public key (provisioned at
broker install). This is the concrete form of security #3's "structured
requests only." Same JSON-with-ids + JWT shape for all
orchestrator↔broker/sidecar communication; cases that don't fit get
orchestrator↔broker/gateway communication; cases that don't fit get
handled as they arise, with this as the default.
If `BottleBackend` bloats, the pressure valve is composition one level
@@ -299,10 +299,10 @@ and integrate a console against.
the data plane and the console reach state through the control-plane RPC,
never a direct file handle.** No agent-facing component gets the file, so
none can forge attribution. (This supersedes the earlier `ro`-mount idea.)
- *Transitional caveat:* today the per-bottle **supervise sidecar
- *Transitional caveat:* today the per-bottle **supervise gateway
rw-bind-mounts `bot-bottle.db`** to write proposals — exactly the
pattern the orchestrator removes (supervise consolidates into the
orchestrator; sidecar writes become RPC calls). Until that lands, don't
orchestrator; gateway writes become RPC calls). Until that lands, don't
put the attribution registry behind a data-plane-writable mount.
Implementation note for the VM slices: SQLite **WAL** over a guest share
@@ -327,7 +327,7 @@ orchestrator becomes a VM. Decouple the two risks instead:
Backend order (cheapest proof → hardest → last):
1. **Docker orchestrator** — nearly free (the sidecar bundle is already
1. **Docker orchestrator** — nearly free (the gateway bundle is already
containers; collapse N bundles into one persistent container). Proves
consolidation + the `BottleBackend` seam with the least moving parts.
2. **Firecracker orchestrator** — the real work: the shim + VM-to-VM
@@ -336,13 +336,13 @@ Backend order (cheapest proof → hardest → last):
the dev-harness so the app logic is already proven.
3. **macOS (Apple container)** — last (container-to-container networking).
Keep the sidecar **service one shared thing** throughout.
Keep the gateway **service one shared thing** throughout.
## Non-goals
- Removing OCI/Dockerfile support for agent images (0069's concern).
- A single database for *all* config (see the three-tier table).
- Changing the per-bottle isolation of agent workloads — only the sidecar
- Changing the per-bottle isolation of agent workloads — only the gateway
is consolidated; agents stay one-VM/container-each.
## Relationship to other work
@@ -393,7 +393,7 @@ Keep the sidecar **service one shared thing** throughout.
`BottleBackend.wire()` (DNAT+forward for fc, shared-net for
docker/apple), not orchestrator logic. **Not a blocker** — resolvable at
implementation time (may require a bit of host modification, as the pool
setup already does on NixOS); an earlier "sidecars in VMs" spike showed
setup already does on NixOS); an earlier "gateways in VMs" spike showed
it's feasible.
- **Live-reload protocol** for per-bottle policy over the HTTP control
plane (add/remove routes/keys/proposals without a restart).