Adopt-vs-build gate
The 2026-04-30 external-review bundles surfaced the structural risk Beach faces: a 15-person consultancy cannot maintain general-purpose primitives that the OSS ecosystem (chiefly Mastra OSS, also Cloudflare Agents, Vercel AI SDK, OpenAI Agents JS, VoltAgent) maintains well. Each cross-cutting primitive Beach reinvents (Postgres store, Redis store, OTel exporter set, scorer kit, Inngest adapter, voice providers) is one engineer-week of build plus an ongoing tax of bug-fixes, version-tracking, ecosystem-feature-parity. Beach pays that tax 15× through its consumers; Mastra's sponsors pay it once across the ecosystem.
Beach cannot win the maintenance race. The gate below is the structural defence — it converts the published "wrap, don't reinvent" policy from prose into an enforceable CR-review check.
Decision A-008 — every CR is checked against the gate
Status: Confirmed. Decision date: 2026-04-30. Decision maker: John Andrew, four-stakeholder convergence (PO + TA + Legal + Reviewer all agreed item 10 of the convergence — the adopt-vs-build gate).
Question: Should every Beach CR be reviewed against an explicit adopt-vs-build gate, with build-the-primitive requiring override?
Decision: Yes. The gate fires at every CR-review with the four checks below.
Reason: Without the gate, the disciplined backlog erodes one P3 ticket at a time. Beach's 53-issue CAIB roadmap is unusually disciplined at the headline level (zero items on model routing, supervisor patterns, RAG, agent inspection, playground, eval scorers — the agent-framework axis Beach correctly avoids), but quietly leaked at five cross-cutting layers (storage, observability, voice, durable execution, A2UI renderer). Each individual CR was defensible in isolation; together they constituted a maintenance tax Beach cannot afford. The gate ends that pattern by demanding the question be asked at every CR, not just the obvious ones.
The gate — four checks at every CR review
For every CR proposing to build, ship, or extend a Beach package, the review verifies:
Check 1 — Does this CR build a primitive that maintained OSS already provides?
For each package the CR proposes to build or extend, the reviewer names the closest-maintained-OSS equivalent. The check passes if either:
- No equivalent exists — Beach is genuinely first to the shape (router, manifest, three-part envelope,
respond()discipline, channel double-wrap, tier-annotated composition, filter-and-distribute primitive). Build is justified. - An equivalent exists but Beach builds with explicit override — the override requires architect adjudication and is recorded in the CR's status block with the rationale.
The check fails if the CR builds a primitive that the OSS ecosystem already maintains and the CR does not name an explicit override. The CR is restructured to wrap the OSS primitive instead.
The five known leakage layers (2026-04-30):
| Layer | OSS equivalent | Beach's adapt-vs-build choice |
|---|---|---|
| Storage backends | @mastra/pg, @mastra/libsql, @mastra/redis, @mastra/mongodb, @mastra/dynamodb, @mastra/clickhouse, @mastra/duckdb, @mastra/cloudflare, @mastra/upstash |
Adapt — @cool-ai/beach-missives-mastra-adapter |
| Observability exporters | @mastra/observability + 11 exporters; OpenLLMetry; OpenLIT; Langfuse |
Adopt OTel directly — emit spans only |
| Voice providers | @mastra/voice-* (14+ packages); Cloudflare Agents voice; LiveKit Agents |
Adapt — @cool-ai/beach-voice-mastra |
| Durable execution | Trigger.dev (Apache-2.0, self-hostable); @mastra/inngest (Apache-2.0 SDK, SSPL server) |
Adapt — @cool-ai/beach-durable-trigger-dev reference |
| A2UI renderer | @a2ui/lit, @a2ui/react, @a2ui/flutter, @a2ui/angular (Google reference) |
Adopt — Beach is @a2ui/lit-shaped, not a competing renderer |
These five are the canonical instances. A new CR proposing to build something that falls into any of these layers is rejected without override.
Check 2 — Licence-and-path audit
Per licence-stance.md, every adapter CR's review includes the four-step licence-and-path audit:
- Confirm every imported package's LICENSE file (read directly).
- Confirm no import path includes
ee/or any directory containing AGPL / SSPL / ELv2 / BSL / fully-proprietary / no-LICENSE code. - Confirm no wrapped package requires runtime authentication to a commercial cloud.
- Confirm any
ee/exclusion is documented in the adapter README.
Failure on any check rejects the CR.
Check 3 — Adapter-shaped, not direct dependency
Beach's core packages never import a Mastra (or any third-party adapter-target) package. Adapter packages may. Beach interfaces never carry third-party types in their signatures.
Specifically:
- Yes: wrap
@mastra/coremodel router as@cool-ai/beach-llm-mastraadapter; consumers depend on Beach'sLLMProviderinterface and optionally peer-dep the adapter. - No: import
@mastra/coretypes into@cool-ai/beach-core. - No: define
routeEvent()to take a MastraAgentas a parameter. - No: make Beach's manifest registry talk to Mastra's workflow snapshots.
The wrap happens at the participant boundary, never at the router or envelope. Storage and observability are infrastructure participants, not domain primitives — adopt via thin adapters that satisfy Beach's interfaces.
Check 4 — Connection-injection convention
Per the 2026-04-30 convergence (Legal's flag), every adapter package accepts consumer-provided clients (database connection pools, Mastra store instances, voice providers, durable executor instances) at construction time. The adapter never opens its own pool; the consumer is responsible for connection management.
This rule applies uniformly across beach-llm-mastra, beach-missives-mastra, beach-channel-mastra, beach-voice-mastra, beach-durable-mastra, beach-evals-mastra — and any future Beach adapter wrapping infrastructure with connection-pool semantics.
Operational consequence — the gate is non-negotiable
Three architectural rules codify this gate (per 06-overlap-and-divergence.md in the external-review bundle):
- Mastra adoption is allowed at participant boundaries, never at the router or envelope. A Beach actor may use
@mastra/coreto call models; anLLMProvideradapter wrapping Mastra is fine. Beach'srouteEventandManifestinterfaces never accept Mastra types as parameters; the wrap happens at the participant edge. - Storage and observability are infrastructure participants — adopt via adapters.
MissiveStore,ArtifactStore, the OTel exporter set — adopt via thin adapters that satisfy Beach's interfaces. Don't ship@cool-ai/beach-missives-postgres; ship@cool-ai/beach-missives-mastra-adapter. llm-contextand tier-annotated composition are Beach contributions — build them. The filter-and-distribute primitive (@cool-ai/beach-core) and the composer primitive are the ones Beach builds. Don't water them down by sliding into Mastra's processor model, which is operationally similar but trust-model different.
These rules are non-negotiable. Architects, reviewers, and consumers can reopen the application of a rule at a specific change (does this change build a primitive Mastra OSS already maintains?) but not the rule itself. Reopening a rule requires explicit decision-log revision, not change-level discretion.
What this prevents — and the cost it accepts
What it prevents:
- The five known leakage layers from continuing to drift. Each becomes a Mastra adapter or an OTel-direct emit; none becomes a Beach-shaped primitive.
- The next twelve P3 tickets reproducing the trajectory at five separate layers. Beach's package count goes down, not up, as the discipline takes effect.
- The "Beach delegates to Mastra (this is in Beach's README today)" caveat from being principle without proof. The wrap discipline is shippable code, not guidance prose.
The cost it accepts:
- The wrap discipline is not free. Wrappers carry their own maintenance, and tracking upstream changes (Mastra release cadence, A2UI v0.9 → v1.0, OpenTelemetry semantic-conventions evolution) is non-trivial. Beach accepts that cost as a structural choice — wrapper-maintenance plus upstream-tracking is materially smaller than building-and-maintaining-bespoke-primitives across five layers.
The gate exists because the cost of not having it is asymmetric. Failure to gate produces leakage one P3 ticket at a time and shows up as a maintenance crisis 18 months later. Failure under the gate produces a slightly more conservative backlog and a slightly slower per-CR design phase — an order-of-magnitude smaller cost.