ADR 0011: Schema Library — Re-export Zod from @aidokit/core

Field Value
Status Accepted
Date 2026-05-12
Deciders Project maintainers
Supersedes
Superseded by
Related ADRs 0001 (Runtime + language), 0005 (Adapter shape), 0006 (Stack-pack shape), 0007 (MCP catalog)

Summary

Use Zod as the schema/validation library, re-exported from @aidokit/core under the @aidokit/core/schema subpath. Adapter and stack-pack authors import their schemas from @aidokit/core/schema and never depend on zod directly. This gives us best-in-class TypeScript-first runtime validation with type inference, while keeping the option to swap implementations behind the @aidokit/core versioning surface.

Two alternatives were considered: defining bespoke schema types in @aidokit/core (rejected — operational cost) and using Valibot (rejected for v0.1 on maturity, but flagged as a credible swap-target if Zod's bundle weight becomes an issue).

Maintainer confirmed Zod on 2026-05-12. Valibot remains a viable swap target if bundle weight becomes a real concern during the v1.x window; the subpath export protects downstream consumers from the swap.


Context

Several @aidokit packages need runtime schema validation:

The validation library is public surface if adapter authors need to validate their own manifests using the same primitives. That implies two design constraints:

  1. The library must be stable enough that minor version bumps don't break downstream consumers.
  2. The library must be importable in TypeScript without ceremony.

Earlier ADRs fix the surrounding environment:

.docs/ARCHITECTURE.md §21 lists "Whether @aidokit/core should re-export zod or define its own schemas" as an open question.


Decision

1. Zod is the implementation; @aidokit/core/schema is the API surface

Zod is added to @aidokit/core as a direct dependency. @aidokit/core exposes a subpath export:

// packages/core/src/schema.ts
import { z } from 'zod';
export { z };

// And our own schemas, built using z:
export const AdapterManifestSchema = z.object({
  name: z.string(),
  cliTarget: z.string(),
  specVersion: z.literal('2.0'),
  sdkVersion: z.string(),
  conformance: z.enum(['minimum', 'standard', 'strict']),
  capabilityDeclarations: z.object({
    writesPaths: z.array(z.string()),
    runsShellCommands: z.array(z.string()),
    networkCalls: z.array(z.string()),
  }),
});

export type AdapterManifest = z.infer<typeof AdapterManifestSchema>;

@aidokit/core package.json declares:

{
  "exports": {
    ".": "./dist/index.js",
    "./schema": "./dist/schema.js"
  }
}

Downstream packages import only from the subpath:

import { z, AdapterManifestSchema } from '@aidokit/core/schema';

Adapters and stack packs MUST NOT add zod as a direct dependency. They consume it via @aidokit/core/schema.

2. Schemas drive both runtime validation and TypeScript types

We pick one source of truth — Zod schemas — and infer TypeScript types from them via z.infer<typeof Schema>. Hand-written types that duplicate the schemas are forbidden; they drift.

The AdapterManifest, StackPackManifest, ProjectContext, MCPDef, and PrereqDef types currently sketched in the specs are recoded as Zod schemas. The inferred TS types are exported alongside.

3. JSON Schemas (for aidokit validate) are derived from Zod

aidokit validate (v0.5) validates task briefs / change summaries / test reports against JSON Schemas. Those JSON Schemas are generated from Zod via zod-to-json-schema, not hand-written. This keeps a single source of truth.

The generated JSON Schemas are checked into packages/core/src/schemas/*.json so they can be consumed by:

Generation is a build-time step in @aidokit/core. Drift between Zod schemas and generated JSON Schemas fails CI.

4. Zod version pinned at the @aidokit/core boundary

@aidokit/core pins Zod to a tested minor range (^3.23 or current at v0.1 release). Zod major-version bumps require:

This shields downstream consumers from upstream churn while still letting us upgrade on our schedule.

5. @aidokit/core/schema does NOT re-export every Zod helper

Only z (the schema-builder) plus our project-specific schemas are re-exported. Zod's deprecated APIs and the @zod/v3 namespace are not re-exported. This lets us trim the surface if Zod ships large API changes.

6. The Zod runtime is small enough to ship

Zod adds ~50KB minified to @aidokit/core. This is acceptable for a CLI tool; the alternative ("custom validator") would cost more in maintenance time than the install ever costs in download time.


Consequences

Positive

Negative

Neutral


Alternatives considered

Alternative 1: Define bespoke schema types in @aidokit/core

Write our own tiny schema builder in @aidokit/core with just the primitives we need (object, array, string, number, union, literal, enum).

Pros

Cons

Reason for not recommending: the operational cost is large; the install-size saving is small (≤50KB) and most users never see it.

Alternative 2: Valibot

Modern alternative to Zod; ~10x smaller bundle, similar API shape, growing user base.

Pros

Cons

Reason for not recommending (right now): Valibot is a credible choice and may become the better default within v1.0's lifetime. Today, Zod's ecosystem maturity wins. The @aidokit/core/schema subpath export keeps the swap option open.

If the maintainer prefers Valibot now, the only material change in this ADR is the implementation detail; the rest of the design (subpath export, generated JSON Schemas, schema-as-source-of-truth) is identical.

Alternative 3: arktype

Type-first runtime validator with TypeScript-like syntax inside strings.

Pros

Cons

Reason for not recommending: intriguing technology, wrong maturity stage for our timeline.

Alternative 4: ajv (JSON Schema validator)

The de-facto JSON Schema runtime. Write JSON Schemas directly; validate at runtime with ajv.

Pros

Cons

Reason for not recommending: we want TS-first development, and Zod gives us JSON Schemas as a derived artefact at no cost via zod-to-json-schema.

Alternative 5: runtypes / io-ts / others

Older TS-first validation libraries.

Pros

Cons

Reason for not recommending: Zod dominates them on every relevant axis.


Implementation notes

If accepted:

If Valibot is accepted instead:


Validation

This decision is validated when:


References

Internal

External