Data contracts for AI agent pipelines answer a question most agent architectures leave implicit: when one step hands a payload to the next — model to tool, tool to model, agent to agent, agent to warehouse — what shape is that payload guaranteed to have, who is responsible when it doesn’t, and what happens next? Data engineering solved this a generation ago for tables and pipelines. The same discipline maps cleanly onto agent boundaries once you treat LLM output as producer data.
The stakes are specific to agents. A traditional pipeline that receives a malformed row usually crashes loudly. An agent pipeline that receives a malformed tool result often keeps going — the model improvises around the gap, downstream steps consume the improvisation, and the failure surfaces three hops later as a confidently wrong answer with no stack trace pointing back to the boundary that actually broke.
This guide covers the three documented mechanisms that anchor contract enforcement in agent stacks — schema-constrained generation, protocol-level schema declaration in MCP, and the application- and data-layer validation tooling (Pydantic, dbt, Great Expectations) that predates agents — plus the versioning, ownership, and runtime-violation policies that turn a schema file into an actual contract.
- 01Every agent hop is a producer/consumer boundary.Model-to-tool, tool-to-model, agent-to-agent, agent-to-warehouse — each handoff needs the same shape guarantees a database table gets. LLM output is producer data.
- 02Generation-time enforcement now exists at the token level.OpenAI Structured Outputs and Anthropic strict tool use constrain sampling to schema-valid tokens — per the vendors, a conforming payload is guaranteed, not requested.
- 03MCP carries the contract inside the protocol.Every MCP tool declares an inputSchema; an optional outputSchema binds the server with MUST-level conformance language while clients SHOULD validate results.
- 04The prior art transfers almost verbatim.dbt fails the build on contract mismatch, Pydantic coerces by default and hard-matches in strict mode, Great Expectations validates content beyond shape, and ODCS 3.1 standardizes ownership and versioning.
- 05A contract without a violation policy is documentation.Decide in advance: refusals get surfaced, tool errors go back to the model for self-correction, validation failures route to a dead-letter path, schema drift fails the build.
01 — The FramingWhy every agent hop is a contract boundary.
The data-engineering world converged on a working definition worth importing wholesale. The Open Data Contract Standard ecosystem defines a data contract as a document covering the ownership, structure, semantics, quality, and terms of use for data exchanged between a producer and its consumers — an API, but for data. Swap “data producer” for “an LLM or agent step” and the mapping is complete: the model that emits a tool call is a producer, the tool is a consumer, and the tool’s result makes it a producer for the model’s next turn.
What makes agent pipelines different from classic pipelines is how many of these boundaries exist and how weakly typed they are by default. A single agent turn can cross a model-to-tool boundary, a tool-to-external-API boundary, and an agent-to-warehouse boundary — and in a naive implementation, every one of them is “whatever JSON the model happened to emit.” Reliability work on agents tends to focus on prompts and context; our context-engineering reliability playbook covers that half. Contracts are the other half: the guarantees that hold even when the model has a bad day.
The trend worth naming: enforcement has been moving steadily closer to the point of generation. First came application-level validators that checked model output after the fact. Then protocol-level schema declarations that travel with the tool definition. Now both major model vendors offer token-level constraints where a non-conforming payload cannot be sampled in the first place. Each step removes a class of failure the previous layer could only detect, not prevent.
“A data contract is a document that defines the ownership, structure, semantics, quality, and terms of use for exchanging data between a data producer and their consumers. Think of an API, but for data.”— datacontract.com, Open Data Contract Standard framing
02 — Generation TimeEnforcement at the token level: Structured Outputs & strict tool use.
The strongest contract is one the producer cannot violate. Both OpenAI and Anthropic now ship exactly that, in vendor-equivalent forms. OpenAI’s Structured Outputs “ensures model responses adhere to your supplied JSON Schema,” per the docs — invoked with "strict": true, with schemas typically authored in Pydantic (Python) or Zod (JavaScript) and passed to the API as text: { format: { type: "json_schema" } }. OpenAI states the feature is available in its latest large language models, starting with GPT-4o; older models fall back to plain JSON mode, which carries no schema guarantee.
Anthropic’s equivalent lives inside tool use: tools declare an input_schema in JSON Schema, and Claude returns a tool_use block with a name and input. Without strict mode, that is instruction-following, not a guarantee — Anthropic’s own docs warn that Claude might return incompatible types (the string “2” where the integer 2 is expected) or omit required fields, breaking your functions at runtime. With strict tool use ("strict": true on the tool definition), the docs state the guarantee directly: it “guarantees Claude’s tool inputs match your JSON Schema by constraining the model’s token sampling to schema-valid outputs (a technique called grammar-constrained sampling).” Two things are guaranteed: the input strictly follows the schema, and the tool name is always drawn from the provided tool list. Anthropic’s worked example is a booking tool requiring passengers as an integer — strict mode always yields the integer 2, never “two” or “2”.
Structured Outputs
Model responses adhere to the supplied JSON Schema — vendor-stated benefits: reliable type-safety without manual validation and retries, programmatically detectable refusals, and simpler prompts with no formatting exhortations. Author schemas in Pydantic or Zod.
Strict tool use
Grammar-constrained sampling: the compiled schema restricts token sampling to schema-valid outputs. Guarantees the tool input matches input_schema and the tool name is always valid. Framed by Anthropic explicitly around agentic reliability.
One design detail matters for violation handling later: under OpenAI’s Structured Outputs, a safety refusal does not arrive as a malformed payload. It arrives in a dedicated refusal field, distinct from schema-conforming output — which means your contract-validation layer can tell “the model declined” apart from “the producer broke the contract.” Those are different events and, as section 08 argues, they deserve different responses.
Anthropic frames the stakes in the same terms this post does: building reliable agentic systems requires guaranteed schema conformance — for validating tool parameters, building agentic workflows, and ensuring type-safe function calls. When both vendors independently converge on token-level constraint as the answer, treat it as the default for any tool whose input feeds real systems.
03 — Schema AuthoringWhat strict schemas can and can’t express.
The guarantee comes with a constrained authoring surface. A schema destined for OpenAI’s strict mode must mark every field as required (no optional fields), use a plain object at the root (no top-level anyOf), and set additionalProperties: false on every object node. The supported keyword surface covers string, number, boolean, integer, object, array, enum, and anyOf — while composition keywords allOf, not, dependentRequired, and if/then/else conditionals are unsupported. That is a real porting constraint: a rich existing JSON Schema often needs restructuring before it can become a generation-time contract.
Max per schema
OpenAI’s vendor-stated structural ceiling for a strict-mode schema. Generous for tool inputs, but auto-generated schemas from large ORM models can approach it.
Max nesting
Deeply recursive structures need flattening before they can serve as a generation-time contract. Keep agent payloads shallow by design.
Across all properties
Total enum budget per schema, alongside a 120,000-character cap on combined property names, definitions, and enum strings. Big controlled vocabularies belong in a lookup tool, not the schema.
Anthropic documents the same family of restrictions: strict tool use shares its schema-compilation pipeline with Anthropic’s separate Structured Outputs feature, and both share one JSON Schema limitations subset — capped structure, no free-form composition keywords. The practical takeaway is vendor-neutral: design agent payload schemas inside the restricted subset from day one, and the same schema document can serve OpenAI, Anthropic, and your runtime validators without translation. Treat the limits above as vendor-stated and subject to change — check the current docs before you standardize.
04 — Protocol LayerMCP: the contract travels with the tool.
The Model Context Protocol takes the next step: instead of the contract living in your application code, it lives in the protocol. Per the MCP tools specification (current as of the 2026-07-28 revision — covered in depth in our stateless-spec migration guide), every tool declares an inputSchema in JSON Schema, defaulting to the 2020-12 draft when no $schema is present. That schema is a normative part of the tool’s contract with the model — not a convention bolted on by the client.
The consumer side got contract language too. Tools can declare an optional outputSchema, and the spec’s wording is RFC-style and asymmetric: servers MUST provide structured results that conform to it; clients SHOULD validate structured results against it. That is a producer/consumer data contract with enforcement teeth on the producer — exactly the split dbt and ODCS arrived at in the warehouse world. If you’re building a server, our TypeScript MCP server tutorial walks through declaring these schemas from scratch.
The spec also distinguishes two violation channels with different remediation semantics — a distinction worth cloning even in non-MCP stacks:
Protocol errors
Malformed requests and unknown tools surface as standard JSON-RPC errors. These are plumbing failures — the caller is wrong, and the fix belongs in code, not in the model’s next attempt.
Tool execution errors
Input-validation failures, business-logic errors, and upstream API failures return as results the model can read. The spec recommends feeding them back to the model to enable self-correction — a designed retry path.
structuredContent and LLM “structured outputs” are unrelated mechanisms — the spec says so explicitly. structuredContent is server-produced tool-result data validated against an outputSchema (producer-side shape). Structured outputs constrain the model’s own generation at sampling time. They solve different halves of the pipeline; a robust agent boundary usually needs both.Two more spec details matter for multi-tool pipelines. First, tool names are only guaranteed unique within a single server — the SHOULD-level constraints are 1-128 characters from [A-Za-z0-9_.-], case-sensitive — so an agent mounting several MCP servers needs an explicit disambiguation strategy, such as prefixing by server identifier, or two contracts can silently collide under one name. Second, MCP has no protocol-level session: the spec’s guidance for stateful sequences is to pass an explicit opaque handle (a basket_id, say) between calls and to state its retention and expiry policy in the tool description so the model can reason about it. State that crosses hops is part of the contract too.
05 — Prior ArtThe data layer already solved this: Pydantic, dbt, Great Expectations.
Everything above the warehouse eventually lands in it, and the tooling that guards that layer is the oldest and most battle-tested part of the stack. Three tools carry the pattern.
Pydantic — the application-layer enforcer
Pydantic’s docs are precise about what validation guarantees: the output of instantiating a model conforms to the declared types and constraints — not that the raw input was valid. By default it coerces (the string “123” becomes the integer 123), which makes default Pydantic a lenient contract enforcer. Its strict mode performs no conversion — values must already match the declared type — the direct application-layer analogue of the vendors’ schema-strict generation modes. Two features make Pydantic V2 the natural hub of a Python agent stack: a single ValidationError is raised regardless of how many fields failed, carrying structured detail on every failure — one inspectable object a pipeline can log and route rather than a generic crash — and model_json_schema() emits JSON Schema from the same class. One Pydantic model can therefore serve as both the contract your code enforces at runtime and the exact schema handed to OpenAI Structured Outputs or an Anthropic input_schema: one schema, two enforcement points.
dbt model contracts — fail the build, not the consumer
dbt’s model contracts (contract: enforced: true) require every column to declare a name and data type, optionally adding platform-supported constraints — though enforcement varies by platform: most cloud warehouses enforce only not_null, while Postgres enforces all constraint types. The mechanism to steal is the preflight: on every run, dbt checks the model’s query against the contract before building, and if the shapes don’t match, the build fails outright — nothing shape-mismatched ever ships downstream. Note the scope limits: contracts apply to SQL models materialized as table, view (limited), or incremental — not Python models, ephemeral models, or sources and seeds.
Great Expectations — content, not just shape
Schema conformance says nothing about whether the values make sense. Great Expectations fills that gap: an open-source Python library built around declarative, testable assertions — expectations, grouped into expectation suites — about what data should look like, validated as part of a pipeline run so quality issues are caught systematically rather than discovered downstream. In an agent pipeline, that is the layer that catches the payload which parses perfectly but claims a delivery date in the past. The current major version on the docs site is GX Core 1.19.1.
Pydantic V2
Lenient by default (coercion), exact in strict mode. One ValidationError with per-field detail to route; model_json_schema() bridges the same class to model-side contracts.
dbt model contracts
Preflight schema check on every run — a contracted model that doesn’t match its declared shape fails the build. Constraint enforcement varies by platform; not_null is the common denominator.
Great Expectations
Declarative, testable assertions about what data should look like, grouped into expectation suites and validated during the pipeline run. The layer that catches semantically wrong payloads with perfect syntax.
06 — The MapWhere the contract is enforced, layer by layer.
Put together, the mechanisms form a layered map — six enforcement points, each with its own violation behavior. No single layer covers the pipeline; the design question for any given boundary is which layers to turn on, not which one to pick.
| Layer | Enforcement point | Mechanism | On violation | Documented in |
|---|---|---|---|---|
| Where the payload is produced | ||||
| Model generation | OpenAI Structured Outputs / Anthropic strict tool use | Grammar-constrained sampling against the supplied JSON Schema | Non-conforming payloads can’t be sampled; safety refusals surface in a separate refusal field | OpenAI + Anthropic docs |
| Protocol (agent ↔ tool) | MCP inputSchema / outputSchema | JSON Schema declared in the tool definition; servers MUST conform, clients SHOULD validate | isError: true result returned to the model for self-correction; JSON-RPC error for malformed calls | MCP specification |
| Where the payload lands | ||||
| Application (your code) | Pydantic model validation | Type coercion by default; exact type match in strict mode | Single ValidationError carrying every field failure — routable to a dead-letter path | Pydantic V2 docs |
| Warehouse | dbt model contracts | Preflight schema check before every build | Build fails outright; nothing shape-mismatched ships downstream | dbt docs |
| Content quality | Great Expectations suites | Declarative expectations validated during the pipeline run | Failed validation result flagged for triage | Great Expectations docs |
| Governance | ODCS 3.1 contract document (Bitol) | Versioned YAML with owner, schema, quality rules, terms, SLAs | Data Contract CLI test fails in CI before deploy | datacontract.com / ODCS |
Reading the map top to bottom: generation-time constraint prevents the producer from lying about shape, the protocol layer makes the contract discoverable by every consumer, the application layer catches what slips between systems, the warehouse layer refuses to propagate drift, the quality layer interrogates meaning, and the governance layer writes down who answers the pager. A pipeline with only the top layer trusts every non-model component blindly; a pipeline with only the bottom layers discovers model failures hours late. The layers are complements, not competitors.
07 — GovernanceVersioning, breaking changes, and who owns the contract.
A schema becomes a contract the moment someone else depends on it — which is also the moment changing it becomes a policy question. dbt has the most concrete documented answer to what counts as breaking: removing an existing column, changing a column’s data type, and removing or modifying constraints all raise hard contract errors, as does removing a contracted model entirely — with one telling nuance. For versioned models, removal is an error; for unversioned models, only a warning. Translated to agents: version the output schema of any agent other systems consume, and breaking changes become new versions with a deprecation runway instead of silent mutations.
Just as transferable is dbt’s guidance on when to contract at all: add contracts to stable models, and reserve them for “public” models — those shared across teams or relied on by downstream systems — because contracting too early complicates future schema changes. The agent translation writes itself: don’t contract-lock an agent’s output while you’re still iterating on it weekly; contract it the day another team’s pipeline starts consuming it.
The ownership question — who owns a contract when the producer is an LLM — has a documented answer in the Open Data Contract Standard, now at version 3.1 and stewarded by Bitol, a Linux Foundation project (the ecosystem consolidated on ODCS after the separate Data Contract Specification was deprecated in its favor). An ODCS document’s sections map almost one-to-one onto what an agent contract needs: Fundamentals (id, version, status — versioning built in), Schema, Data Quality, Terms of Use, SLAs — and a Team section naming an explicit owner and support channel. The model doesn’t own the contract; the team that operates the agent does, and the contract document is where that name lives. The open-source Data Contract CLI runs contract tests in CI — in Python scripts, as a GitHub Action, or as a web server — the same place your dbt preflight and expectation suites already run. For teams formalizing this across a real pipeline estate, it’s the kind of governance scaffolding our AI transformation engagements put in place before any agent touches production data.
input_schema property names, enum or const values, or pattern regexes, because cached schemas don’t get the same protection as prompts and responses under HIPAA. The contract document itself is a data-handling surface. Govern it like one.08 — Violation PolicyWhen the contract breaks at runtime: the agent’s playbook.
Every mechanism in this guide emits a distinguishable signal on violation — and each signal implies a different correct response. The worst pattern is collapsing them into one generic retry loop: retrying a safety refusal wastes tokens and can look like prompt-manipulation; retrying a warehouse schema mismatch just fails again; not retrying a transient tool error throws away the self-correction path the MCP spec deliberately designed in. Write the mapping down before the first violation, not after.
The refusal field
Under OpenAI Structured Outputs, a safety refusal arrives in a dedicated field, not as malformed output. That’s a decision, not a defect — surface it to the caller or a human.
MCP execution error
isError: true results carry validation and business-logic failures back to the model, and the spec recommends exactly that to enable self-correction. Give the loop a bounded retry budget.
Pydantic ValidationError
One structured exception listing every field failure. Log it, route the offending payload to a dead-letter store with its context, and alert — don’t let the agent improvise past it.
dbt preflight failure
The build fails before anything ships. That’s the system working. The fix belongs with the producer — a new contract version or a corrected model — never a consumer-side patch.
One more state deserves first-class handling: the violation that happens mid-run, after earlier steps have already committed side effects. A contract failure at step four of a six-step agent workflow raises the question of what to do with steps one through three. That is recovery territory rather than validation territory — checkpointing, compensation, and rollback — and we’ve covered the patterns in depth in our agent rollback and checkpoint patterns reference. The contract layer’s job is narrower but essential: fail loudly, fail early, and hand recovery a precise account of what broke and where.
Looking forward, expect the layers to keep converging on shared schema documents. The ingredients already exist: one Pydantic class can emit the JSON Schema that constrains generation, validates at ingest, and documents the tool; MCP makes the same schema discoverable over the wire; ODCS wraps it in versioning and ownership. The teams that win with agents over the next few years are unlikely to be the ones with the cleverest prompts — more likely the ones whose payloads are boring, typed, and owned.
09 — ConclusionSchema as safety, contract as policy.
Treat every agent hop as a contract boundary — and decide what happens when it breaks.
The mechanics are no longer the hard part. Both major model vendors can constrain generation to a schema at the token level, MCP carries schemas inside the protocol with MUST-level producer obligations, and the data layer has enforced contracts for years with Pydantic, dbt, and Great Expectations. Every layer of the enforcement map is documented, shipping, and boring — in the best sense.
What most agent pipelines are missing is the policy half: schemas that are versioned instead of mutated, contracts reserved for stable interfaces and owned by a named team, and a written violation playbook that treats a refusal, a tool error, a validation failure, and schema drift as the four different events they are. None of that requires new technology — it requires deciding, in advance, what your pipeline does when a payload arrives wrong.
Start small: pick the one agent boundary whose failure would hurt most, write its schema in the strict-mode subset, enforce it at generation time and at ingest, version it, and name an owner. Then do the next one. Contract by contract, the pipeline stops being a chain of hopeful handoffs and becomes what it should have been from the start — an API, but for data.