ADR 0004: Schema-First Artifact Design: YAML Schemas to Pydantic Models¶
Status: Accepted Date: 2026-03-07 Deciders: Platform team
Context¶
spec-bridge-skill-tool validates the YAML frontmatter of SDD artifacts produced by AI agents.
To perform this validation, it needs a description of what each artifact's frontmatter must
contain: required fields, types, allowed values, and constraints.
There are several ways to encode this description:
- Hardcoded Pydantic models: Define
class SpecFrontmatter(BaseModel)directly in Python source code for each artifact type. - Schema YAML files -> runtime Pydantic derivation: Store schema definitions as YAML files; derive Pydantic models from them at runtime. Skills reference schemas by version string only.
- JSON Schema: Store schemas as JSON Schema files; validate directly without Pydantic.
- Per-skill ad-hoc validation: Each skill writes its own validation logic without a shared schema format.
Decision¶
Option 2: Schema-first with runtime Pydantic model derivation.
Schema files are YAML, stored in init_cmd/schemas/<version>/ within the skill-tool package.
The active schema version is resolved from spec-bridge.conf (schema_version key, default v1).
Skills reference schemas by version string only -- full path resolution is handled by
skill_tool.core.schema.
core/schema.py loads the YAML schema file and derives a Pydantic model from it at runtime.
Skill modules call core.schema.load_model(artifact_type, version) and receive a ready-to-use
Pydantic model class. No skill module hardcodes field names or types.
Changing an artifact's frontmatter structure requires: 1. Updating the schema YAML first. 2. Updating the skill module and template. 3. Updating tests.
Rationale¶
Schema as the single source of truth¶
With hardcoded Pydantic models (Option 1), the artifact format is defined in Python code that
lives inside skill_tool. To change a field name, a developer edits Python -- and must remember
to update the corresponding YAML template and documentation separately. With Option 2, the YAML
schema is the canonical definition; the Pydantic model and documentation are derived from it.
There is one place to change, one place to review.
Version-addressed schemas enable safe evolution¶
Encoding the schema version in spec-bridge.conf means projects can pin to v1 while the tool
ships v2 schemas. Old projects continue to validate against the version they were initialized
with. New projects opt in to v2 via config. No forced migrations.
Runtime derivation keeps skill modules thin¶
Skills do not need to import or maintain Pydantic model classes. They call core.schema.load_model
and receive a model. This keeps skill modules focused on outcome verification logic, not type
bookkeeping.
Option 1 rejected¶
Hardcoded models scatter the schema definition across Python files. When an artifact's frontmatter needs a new required field, the developer must update the model, the template, and any documentation -- with no single authoritative source tying them together.
Option 3 (JSON Schema) considered¶
JSON Schema validation is powerful but verbose and requires a separate validation library. Deriving Pydantic models from YAML is simpler, keeps error messages in the Pydantic style the codebase already uses, and integrates naturally with Python 3.11+ type annotations.
Option 4 rejected¶
Ad-hoc per-skill validation makes it impossible to enforce consistent field naming and types across all artifacts. It also means each skill reinvents the validation wheel.
Consequences¶
Positive¶
- One YAML file per artifact type is the single source of truth for its structure
- Schema versioning enables safe, backward-compatible artifact format evolution
- Skill modules are thin: validation logic is in the schema, not in Python class definitions
- Adding a new required field to an artifact is a one-line YAML change, automatically picked up by all skills that validate that artifact type
- Schema files can be read by humans and tools outside the Python ecosystem
Negative¶
- Runtime Pydantic model derivation adds startup latency (benchmarked to stay within the < 2 second tool invocation budget)
- Schema YAML syntax must be kept simple enough for
core/schema.pyto reliably derive Pydantic models; highly complex union types or discriminated unions may be difficult to encode
Mitigation¶
Schema loading is cached within a single tool invocation (no repeated file reads). The YAML
schema format is documented in init_cmd/schemas/README.md with examples covering all supported
field types.
References¶
- Constitution:
.spec-bridge/memory/constitution.md- Architecture section, Schema Contract - ADR-002:
0002-two-responsibility-model.md - ADR-003:
0003-skill-module-isolation.md - Feature spec:
specs/043-modular-skill-aligned-cli-refactor/spec.md - Implementation:
skill-tool/src/skill_tool/core/schema.py - Schema files:
skill-tool/src/skill_tool/init_cmd/schemas/v1/