Schema-Driven Development
A manifesto
If you've ever written the schema so your agent could generate everything around it, you're already doing this. Schema-driven development is the name for treating that decision as the durable standard — and moving meaning into its own layer.
Six principles
- Define a shape once — Every shape that crosses a boundary is declared exactly once.
- Reuse before you define — Resolve a shared definition before writing your own.
- Define before you generate — Declare the shape first; then write the code to use it.
- Version definitions like code — Schemas change through proposals and semver — reviewed, diffed, attributable.
- Separate shape from meaning — The author declares what data is; what it means is added by everyone who uses it.
- Derive everything — Everything that carries the data — storage, APIs, validation, forms, docs, agent context — is derived from its definition.
Where this applies
Schema-Driven Development is for the data shapes that cross a boundary — between services, storage, the UI, and your agents. A throwaway script's local struct doesn't need a schema; the User five systems share does.
It begins where disagreement becomes expensive.
Not a schema registry, a serialization-format war, Big Design Up Front, or a replacement for specs and tests — a set of principles for deciding and sharing the shapes that matter.
We version our code. We reinvent our data shapes.
We spent decades making code shareable: version control, packages, semver, review, dependency graphs. We version it, review it, publish it, depend on it.
Data shapes never grew the same habit. The formats exist — but none became the shared commons for data that packages are for code. User, Order, Address, Invoice — the structures every system runs on — get reinvented inside each codebase, a little differently every time. The one thing every system has to agree on is the one thing we never learned to share. Not the records — the shapes. What a User is, not who your users are.
Code can be rewritten. A disagreement about what a User is cannot be patched around forever — that isn't bad code, it's broken software.
Decide what things are. Generate the rest.
This debt was always there; agents just made it impossible to ignore. They generate almost everything now — so the only lasting leverage is what you decide the data actually is.
Agent-built systems do not fail at syntax. They fail at agreement. Your agents have no shared memory across sessions and your team has no shared context window. A schema is the single place where that agreement can live, be versioned, and be deterministically verified.
Nobody wants a schema registry. Everyone wants their next change to not silently contradict the last one.
“Your codebase has five definitions of
User. Your agent wrote four of them.”
Schema-Driven Development
It's the practice of deciding data shapes before generating code. In the agent era, that practice needs a second layer: shared, typed context around what those shapes mean.
A practice in the TDD / DDD lineage. Schemas before code. Pin down the nouns; let agents write the verbs.
We value
- a resolved definition over a convenient local copy
- deciding the shape over writing the code (the code is cheap now)
- one shape, many authors of its meaning over one owner of both
- versioned disagreement over silent drift
There is value on the right — speed, autonomy, a single owner. We value the left more, because the left compounds and the right doesn't.
What one schema removes
Agents make every undecided data shape cheap to create and expensive to live with.
Point your codebases and its agents at one referenceable schema, and a whole category of busywork stops being generated in the first place:
- Duplicate forms and validators for a shape that was already defined once
- Hand-written mapping layers between services, storage, and agents that each read the shape a little differently
- Storage drift, and the migrations that come to mop it up
- Re-explaining the same field to every fresh agent session and every new teammate
The generation is cheap. The coordination and translation work is what compounds.
What people might push back on
Questions that people may have who are unfamiliar with schema-driven development.
Isn't this just advocating for a schema registry?
Not another .md file in the repo root.
AGENTS.md, CLAUDE.md, .cursorrules, llms.txt, most of them config your tool reads at startup. SCHEMA.md isn't config. It's a claim about the repo — here is where data shapes get decided, for every human, agent, and CI job that opens the project. The file on its own changes nothing. What changes behaviour is the one line you add to AGENTS.md or CLAUDE.md pointing at it; that line gets read every session. Leave it out and SCHEMA.md is llms.txt all over again: present, unread, decorative.How is this different from spec-driven development?
Schema-first already failed — SOAP, WSDL, design-first OpenAPI. Why now?
Reuse before you define.
Define before you generate.
Derive everything.
- shape
- the structure of a piece of data: its fields and their types.
- schema
- a shape declared on purpose, in one place, that humans and agents resolve alike.
- context
- typed meaning attached to a definition: what a field implies, who masks it, how long to keep it. Authored by its users, not its owner.
- derived artifact
- anything produced from a schema (storage, API, validation, forms, docs, agent context); generated or hand-written, it answers to the schema.
- resolve
- to look up and use a shared definition by reference, instead of redeclaring your own.
See where your repo stands
Try this to see where your code is at. No risk, read-only — one paste into your agent.
Fetch https://schema-driven.dev/llms.txt to learn the standard, then work
in three phases. Don't write anything in phase 1; don't start phase 2 until
I say yes; don't declare a schema in phase 3 until I've picked it.
Phase 1 — explore (read-only). Don't create, edit, or generate anything.
Map the schemas I already have but can't see: the data shapes that live in
the code with no single declared definition. Look for —
1. Shapes defined more than once: a User, an Address, an Order redeclared
across types, validation, database models, API payloads, and the
context I hand my agents — each copy a little different.
2. Shapes that cross boundaries: the structures that travel between UI,
validation, storage, and agent context with no one place that says
what they are.
3. Shapes my agents keep re-inventing: the definitions a coding agent has
to guess at because nothing authoritative exists to resolve.
Report an inventory: name each hidden schema, list every place it's
redefined today, and show where the copies have already drifted apart.
Rank them by how much a single declaration would remove. Change nothing —
this is a map, not a migration. Then ask me: "Want to make this repo
schema-driven?"
Phase 2 — adopt. Only if I say yes, and reusing the inventory from phase 1
(don't re-scan):
1. Create a /schemas directory in the project root — the single source of
truth for every data shape in the repo.
2. Write a SCHEMA.md in the project root. Declare the repo schema-driven,
point to /schemas as the authoritative state, and state the rule: no
code is generated for a data type before its schema is proposed and
approved.
3. Add the rule to your agent: locate CLAUDE.md, AGENTS.md, or
.cursorrules (create CLAUDE.md if none exist) and add this line at the
very top: "IRON RULE: Before creating, modifying, or generating code
for any data type, you must read SCHEMA.md first."
The repo is now schema-driven — but nothing is converted yet.
Phase 3 — declare the first schema. Bring back the ranked map from phase 1
and recommend which shape to hit first: the one declaration that removes
the most pain. Tell me why, and wait for me to choose — don't convert
anything on your own. Once I've picked, define that one schema as a strict
JSON Schema (json-schema.org, draft 2020-12) under /schemas, then add a
script that derives its types and validation from that schema — proposing
the definition for me to approve before any code generates against it.
Then stop, and we choose the next one together.
Then watch it work. Ask your agent to add a phone number to signup, and watch it propose a schema first. Then ask it to reuse an Address you defined in another repo, or to note that a field is masked in logs somewhere that outlives the session — and watch it run out of places to look.