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:

  1. Confirm every imported package's LICENSE file (read directly).
  2. Confirm no import path includes ee/ or any directory containing AGPL / SSPL / ELv2 / BSL / fully-proprietary / no-LICENSE code.
  3. Confirm no wrapped package requires runtime authentication to a commercial cloud.
  4. 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/core model router as @cool-ai/beach-llm-mastra adapter; consumers depend on Beach's LLMProvider interface and optionally peer-dep the adapter.
  • No: import @mastra/core types into @cool-ai/beach-core.
  • No: define routeEvent() to take a Mastra Agent as 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):

  1. Mastra adoption is allowed at participant boundaries, never at the router or envelope. A Beach actor may use @mastra/core to call models; an LLMProvider adapter wrapping Mastra is fine. Beach's routeEvent and Manifest interfaces never accept Mastra types as parameters; the wrap happens at the participant edge.
  2. 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.
  3. llm-context and 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.