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
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>
66 lines
2.9 KiB
Markdown
66 lines
2.9 KiB
Markdown
# 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.
|
|
|
|
## Links
|
|
|
|
- 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`).
|