Files
bot-bottle/docs/decisions/0006-shared-behavior-in-base-backend.md
T
didericis a401310865
test / image-input-builds (pull_request) Successful in 59s
test / integration-docker (pull_request) Successful in 1m3s
lint / lint (push) Successful in 3m32s
test / unit (pull_request) Successful in 2m47s
test / coverage (pull_request) Successful in 49s
tracker-policy-pr / check-pr (pull_request) Successful in 13s
docs: renumber PRD 0083 -> 0081 across the reconcile chunk
Follows the base branch back to 0081. 0083 was already claimed by
feat/pinned-infra-artifacts (docs/prds/0083-packaged-infra-artifacts.md), so
the two PRDs would have collided on whichever merged second; 0081 is free.

29 references across 21 files — the "PRD 0083" prose in the backends, the
shared gateway_attach flow, the tests, and the ADR — plus the PRD filename in
the ADR's reference link, which now resolves again.

requirements.gateway.lock matches "0083" inside a sha256 hash and is
deliberately untouched.

Unit suite unchanged: same 13 pre-existing failures as main
(test_cli_start_selector, test_firecracker_cleanup).
2026-07-27 11:35:07 -04: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 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.
  • 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).