# ADR 0006: Shared behaviour lives in the base backend - **Status:** Accepted - **Date:** 2026-07-27 - **Deciders:** didericis ## Context `BottleBackend` has three concrete implementations (Firecracker, docker, macOS-container). Cross-backend operations can be expressed two ways: 1. **Abstract method per backend** — the base declares `@abstractmethod foo()` and each backend writes its own `foo()` end to end. 2. **Template method** — the base owns the *control flow* of `foo()` as a concrete method and each backend overrides small **primitives** it calls. Form 1 gives each backend total freedom, which is exactly the problem: the three implementations drift. The bring-up reconcile (PRD 0081) first shipped as form 1 and immediately diverged — each backend re-implemented "gather CA → list running bottles → push to each", and one backend's copy silently skipped failures while another's aborted, an inconsistency caught only in review (#519). The behaviour that must be identical (the loop, the error policy, the ordering) was duplicated in the place least likely to stay in sync. ## Decision Prefer the template-method form for cross-backend behaviour: **encode shared control flow and policy in the base backend; backends override primitives, not control flow.** Lean toward *less* freedom in the concrete backends and *more* consistency in the base. Concretely, a cross-backend operation on `BottleBackend` should be a concrete method that: - owns the sequencing, iteration, and error policy (e.g. fail-hard vs. best-effort, fail-fast vs. attempt-all-then-aggregate); and - delegates only the irreducibly backend-specific steps to `@abstractmethod` primitives with narrow, well-typed signatures. The first application is `attach_bottled_agents_to_gateway()` (PRD 0081): the base gathers resources once, attempts every running bottle, and raises an aggregate `InfraLaunchError` if any failed; backends supply only `_gateway_attach_resources()`, `_running_bottles()`, and `_attach_bottle_to_gateway()`. A backend-specific `@abstractmethod` with no shared control flow (e.g. `_launch_impl`, `enumerate_active`) stays as form 1 — this ADR is about operations whose *shape* should be uniform, not about banning abstract methods. ## Consequences - The error policy, ordering, and loop for a cross-backend operation have one source of truth; a new backend cannot forget them or diverge. - New backends implement smaller, more focused primitives instead of re-deriving a whole flow. - The base carries more logic and needs generic-enough primitive signatures (e.g. a backend-specific attach-target type parameter on `BottleBackend`). - Genuinely backend-specific one-offs still use a plain abstract method; the guidance is a default, not an absolute. ## Links - PRD 0081 (`docs/prds/0081-reprovision-gateway-state-on-bringup.md`). - Review that prompted this: PR #519. - `bot_bottle/backend/base.py` (`BottleBackend.attach_bottled_agents_to_gateway`).