Files
bot-bottle/docs/decisions/0006-shared-behavior-in-base-backend.md
T
didericis-claude 606a57b7ed
test / unit (pull_request) Successful in 1m1s
lint / lint (push) Successful in 1m9s
tracker-policy-pr / check-pr (pull_request) Successful in 14s
test / integration-docker (pull_request) Successful in 1m2s
test / image-input-builds (pull_request) Successful in 56s
test / coverage (pull_request) Successful in 17s
docs: renumber PRD 0081 -> 0083 across the reconcile chunk
Follows the base PR's renumber (0081 was already taken; main is on 0082). Update
every `PRD 0081` reference in the gateway-attach code, tests, and ADR 0006, plus
ADR 0006's link to the PRD file. No behaviour change.

Refs #516, #519.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 15:15:44 +00:00

2.9 KiB

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 0083) 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 0083): 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.
  • PRD 0083 (docs/prds/0083-reprovision-gateway-state-on-bringup.md).
  • Review that prompted this: PR #519.
  • bot_bottle/backend/base.py (BottleBackend.attach_bottled_agents_to_gateway).