ADR-0011: Schemas live in a separate package
ADR-0011: Schemas live in a separate package
Section titled “ADR-0011: Schemas live in a separate package”Status: Accepted Date: 2026-04-20
Context
Section titled “Context”ADR-0010 commits us to Zod. That raises a follow-on question: where do schemas live? Two options, each with a real trade-off.
-
Option A — schemas in
@agent-platform/core. Single source of truth: define a schema once, derive the type viaz.infer. Idiomatic Zod usage. But core becomes dependent on Zod, which means every consumer of core — including future Business Packs that only want types — inherits the Zod runtime dependency. ADR-0009 would be superseded. -
Option B — separate
@agent-platform/schemaspackage. Core stays types-only (ADR-0009 preserved). Schemas reference core types and assert equality at compile time. Consumers who only need types import fromcore; consumers who need validation import fromschemas.
Decision
Section titled “Decision”Option B. Schemas live in @agent-platform/schemas. Core types are authoritative; schemas are validated against them at compile time via Assert<Equals<z.infer<typeof Schema>, CoreType>>. If a schema drifts from its core type — by a field, by an enum value, by an optional-vs-required difference — the package fails to build.
Consequences
Section titled “Consequences”- ADR-0009 preserved. Consumers that only want types don’t pull Zod into their dependency graph. This matters when a Business Pack or a future UI package wants the
AgentDefinitiontype without the 30KB validator library. - Two sources of truth, in theory. In practice, drift is caught at compile time, not at runtime or in review. The cost of this safety is the ~7 lines of
Assert<Equals<>>declarations at the bottom of each schema file. - Clear split between contract and enforcement.
coreanswers “what is the shape?”;schemasanswers “is this input trustworthy?”. Separating these questions makes reasoning easier when the runtime gets complex. - Slightly more work per new type. Adding a type in core that needs runtime validation means adding a corresponding schema in
schemas. Not all core types need schemas (seeschemas/README.md— interfaces with function members are excluded), so this is not mechanical duplication. - Reversible. If single-source-of-truth pressure builds up (e.g., we find ourselves constantly editing two files to add a field), moving schemas into core is a package merge. The drift-check code becomes dead at that point and can be deleted.
Consequences for the repo
Section titled “Consequences for the repo”@agent-platform/corehas zero runtime dependencies. Still true.@agent-platform/schemasdepends on@agent-platform/core(workspace) andzod(runtime).- Future Business Packs depending on validated inputs will import from both; Business Packs that don’t do validation (unlikely but possible) can import from
corealone.
Alternatives considered
Section titled “Alternatives considered”- Schemas in core (Option A above, supersedes ADR-0009). Single source of truth is genuinely elegant, but the dependency coupling is the dominant consideration at our size. If we later decide the
Assert<Equals<>>pattern is more ceremony than it’s worth, merging is an easy path. Going the other direction (splitting after we’d collocated) is harder. - Generate schemas from types (codegen via TypeScript reflection). Tools in this space (e.g.,
ts-to-zod) exist but drop nominal types like branded IDs and sometimes losereadonly/exactOptionalfidelity. We’re too strict for them. - Generate types from schemas. Inverse of the above: schemas as source, types derived via
z.infer. Same problem as Option A (core must depend on Zod) plus the types become less readable —z.infer<typeof Foo>is worse to hover over than a plain interface.