Blog
Saved0

Typed contracts for AI workflows: structured outputs aren't enough

A model's valid JSON said "amount: 125000". The next step read it as the payment due, and the supplier got $80,000 too much. Why every AI step needs a contract that carries meaning, status and failure, not just shape.

LinkedIn
On this page
  1. The JSON was valid. The payment wasn't.
  2. Why do AI workflows break at the boundaries between steps?
  3. Is schema validation enough?
  4. What a useful contract carries
  5. Proposal isn't fact: put status in the type
  6. Every kind of step needs its own contract
  7. Mappings are part of the contract
  8. Structured output is formatting, not proof
  9. Changing a contract is more than staying parseable
  10. Common anti-patterns
  11. Ten rules for typed AI workflows
  12. The result: uncertainty ends at a contract
  13. Frequently asked questions
  14. How Frozion puts a contract on every step
  15. Sources
The short answer

Structured output guarantees shape, not meaning. Between every consequential AI step, use a versioned contract that states units, basis and time, whether the value is proposed or verified, where it came from, who may use it, and which typed failures are possible. Treat model output as a proposal until the receiving step validates it.

The JSON was valid. The payment wasn't.

A manufacturer's supplier-rebate workflow reads a signed agreement, extracts the rebate terms, checks eligibility, routes unusual cases for approval and tells the payment system to issue the credit.

The agreement allows up to $125,000 in rebates for 2026, including credits already paid. The supplier has received $80,000, so at most $45,000 is still due.

A model reads the agreement and returns:

{ "supplierId": "S-2291", "amount": 125000, "currency": "USD", "period": "2026" }

It's valid. The supplier exists, the currency is allowed, the amount is positive, the period is recognized. The next step has an input called amount too, and it reads it as the amount to pay now. A visual mapping joins amount to amount. The policy check confirms it's within the $125,000 annual cap. A manager sees "within contractual limit" and approves. The payment API pays $125,000 and returns success. The supplier's total for the year is now $205,000.

The number had a type: number. What it didn't carry was its meaning. One step produced an annual entitlement. The next consumed an amount payable now.

A model output field named amount meaning annual cap is mapped to a payment step field named amount meaning payable now, leading to an overpayment
The mapping matched field names. The meanings were different, and every check passed.

Why do AI workflows break at the boundaries between steps?

Because information crosses more kinds of boundaries, and many of them can change meaning: model to validator, query to context, approval to resume, proposal to API. Loose JSON and naming conventions let meaning slip at each one.

Distributed systems always needed interface contracts. AI applications need them more. A rule returns a value from code. A model generates a value from context. A query returns a point-in-time view. An approval binds to a rendered summary. An event resumes a paused case later. A tool adapter turns a proposal into an irreversible action.

Ambiguity creeps in through fields that look alike but mean different things; numbers without units, currency, basis or effective date; optional fields with silent defaults; enum values that drift between systems; IDs missing tenant or legal-entity scope; model output that's well formed but unsupported by evidence; structured facts flattened into prose and re-parsed; errors as free-text strings; and running cases resuming against newer schemas.

When these surface downstream, they get called hallucinations or integration bugs. Often the real cause is that nobody defined what was allowed to cross the boundary.

"Between every step" means every boundary where ownership, trust, time, state, policy or side effects change, not every private function call. A contract can use JSON Schema, an OpenAPI description, an event envelope, a database schema or a domain validator. What matters is that it exists, is versioned and is enforced.

Is schema validation enough?

No. A payload can pass structural validation and still fail on meaning, evidence, authority, state or side-effect safety. These are six different questions, and each needs its own check.

LayerQuestionPasses the schema but fails anyway
StructureRight fields, types, formats?amount is a number; currency is three letters
MeaningDoes each field mean what the receiver expects?Annual cap to the producer, payable now to the consumer
EvidenceAre values supported by current, traceable sources?A valid policy number that isn't in the retrieved policy
AuthorityMay this user use this data and request this action?A real account outside the user's delegated scope
StateIs this transition allowed right now?Approved before a material mapping or policy change
Side effectIs it safe to commit, retry and record?Valid request, but a retry could duplicate a payment

OWASP separates syntactic from semantic validation and recommends validating untrusted data as early as possible. Model output is untrusted data: well formed, and possibly wrong, unsafe or wrong for the next step.

What a useful contract carries

A contract can be compact, but it has to stop the receiver from guessing. Eight dimensions recur:

DimensionWhat to make explicit
Identity and versionContract ID, dialect, version, owner, lifecycle
StructureTypes, required fields, cardinality, nulls, formats, ranges, allowed values
MeaningUnits, scale, currency, basis, time period, identifiers, taxonomy
StatusProposed, validated, authorized, approved, committed, rejected or unknown
ProvenanceSources, evidence spans, timestamps, model or rule version, abstain reason
ScopeTenant, legal entity, resource, purpose, authority, data classification
ValidationInvariants, policy obligations, approval needs, allowed next states
Failure and changeTyped errors, repair or escalation routes, compatibility, migration

A shallow contract says amount is a number. A stronger one carries the proposition:

{
  "status": "PROPOSED",
  "remainingRebate": { "value": 45000, "currency": "USD", "basis": "incremental_payable",
                       "period": "2026", "derivedFrom": ["annualCap", "priorCredits"] },
  "evidence": [{ "doc": "agreement-7781", "clause": "4.2" }],
  "scope": { "tenant": "eu-mfg", "legalEntity": "DE-01" }
}

That's an illustration, not a standard. The point is the difference between passing a number and passing something the next step can evaluate without inventing context.

Proposal isn't fact: put status in the type

A model's output shouldn't gain the authority of a verified system fact just because both are JSON. Treat status as part of the type, moving through stages such as proposed, structurally valid, domain valid, evidence supported, authorized, approved and committed. Not every workflow needs every stage. What matters is that consequential transitions are explicit.

A payment step shouldn't accept a proposed payment instruction, only an authorized or approved one, depending on policy. A review step binds its decision to the exact proposal, evidence, schema and mapping version shown. If anything material changes, the old approval doesn't cover the new payload (see human-in-the-loop approval).

This is the line between probabilistic and deterministic work in practice: the model's output stops at a typed checkpoint before anything consequential happens (more in deterministic vs probabilistic AI steps).

Every kind of step needs its own contract

StepWhat its contract must cover
Rule or calculationExact types, invariants, precision, units, deterministic failures
ModelTask, output schema, evidence references, abstain option, evaluation profile, allowed next status
Query or retrievalResult schema, tenant scope, source, snapshot time, classification, empty-result meaning
Policy decisionDecision, obligations, policy revision, facts evaluated, authority context
API or toolRequest, response and error schemas, authority, side-effect class, operation ID, retry rules
EventID, type, source, schema version, correlation, timing, duplicate behavior
Human approvalExact proposal and evidence, reviewer authority, conditions, expiry, what it unlocks
Sub-workflowPorts, authority, policy, state ownership, side effects, version compatibility (see reusable sub-workflows)

OpenAPI, CloudEvents and Problem Details for HTTP APIs are good foundations here, because success, failure, identity and version shouldn't be reconstructed from free-text strings.

Mappings are part of the contract

Many failures happen on the edge between steps, not inside them. A governed mapping shows which producer field feeds each consumer field; whether units, scale, precision, time zone or ID namespace change; how enums translate and what happens to unknown values; whether missing values are rejected, defaulted or sent for review; what's dropped, redacted or flattened into prose; which scope and provenance survive; and which mapping version a running case used.

A default isn't a convenience. It's a business decision made without evidence. A unit conversion changes what a number means. An enum fallback to OTHER can erase exactly the distinction that should trigger review. Never infer compatibility from matching field names.

Structured output is formatting, not proof

Schema-constrained generation cuts parser failures and makes validation easier. It doesn't prove the values are correct, supported, authorized or safe. A perfectly valid object can contain an invented customer ID, a real account outside the user's scope, an amount from the wrong document, a date in the wrong time zone, or tool arguments that satisfy the schema and break policy. OWASP lists improper output handling among the top LLM risks for exactly this reason.

So the workflow needs typed non-success outcomes: abstained (with what's missing), invalid structure, domain invalid, out of scope, policy denied, needs review, and uncertain side effect. These stop the next step reading silence, null, a timeout or free text as success, and they give clear routes: reject, repair, fetch more evidence, try another step, escalate, reconcile or stop.

A model output passing through six stacked validation layers, each with a typed failure exit, before reaching an action
Shape is the first check, not the last. Each layer has its own typed way to fail.

Changing a contract is more than staying parseable

ChangeLooks safe becauseCan still break because
Add an optional fieldOld consumers ignore itThe producer expects it to trigger a check old consumers never run
Extend an enumIt's still a stringUnknown values fall into an unsafe default branch
Change a defaultThe field is still optionalThe same missing value now produces a different decision
Change units or basisStill numericDollars become cents, annual becomes monthly, cap becomes payable
Change status meaningSame fieldsA proposal is treated as validated, or a stale approval as current
Change a mappingBoth endpoint schemas unchangedThe relationship between them changed silently

Bind contract and mapping versions to each published workflow version, and check at publication for unmapped required fields, narrowed enums, unsafe defaults, missing failure routes and illegal status transitions. Running cases stay on the contracts they started with unless they're migrated deliberately. Record which contract and mapping each step used, what passed and failed, and what the approver saw, so incidents and replays can be reconstructed exactly.

Common anti-patterns

Anti-patternWhy it failsWhat to do instead
Map fields by matching namesSame name, different meaningMap by declared meaning, units and basis
Treat schema-valid model output as factShape says nothing about truth or authorityKeep it proposed until each layer is validated
Numbers without units, basis or periodReceivers guessCarry meaning in the contract
Silent defaults in mappingsA business decision made without evidenceReject, derive explicitly or route for review
Errors as free textDownstream reads them as success or crashesUse typed failure outcomes
Parse-compatible means compatibleNew enums, defaults or units change behaviorCheck semantic compatibility before publishing
Resume paused work on a new contractThe old approval covers a different payloadKeep cases on their contract version

Ten rules for typed AI workflows

  1. Put contracts on consequential boundaries, not just internal functions.
  2. Make units, basis, time, identifiers and status explicit.
  3. Treat model output as a proposal until it's validated.
  4. Validate at every trust or ownership boundary.
  5. Treat mappings, conversions and defaults as governed artifacts.
  6. Make failure, abstention, denial and uncertain effects typed outcomes.
  7. Bind contract and mapping versions to the published workflow and each running case.
  8. Bind approval to the exact typed proposal and evidence reviewed.
  9. Check semantic compatibility before publishing a change.
  10. Record contract versions, validation results and transitions for every case.

The result: uncertainty ends at a contract

Attention in enterprise AI usually goes to what's inside each step: the model, the rules, the retrieval, the API, the reviewer. Many of the hardest failures happen between them, where meaning, authority, state and evidence get translated or quietly lost.

Typed contracts make those edges part of the design. The contract states what may cross. The mapping states how it changes. Validation decides whether the next step may use it. The record shows what actually happened. The goal isn't to make models behave deterministically. It's to make sure their uncertainty stops at an explicit, inspectable contract before it becomes an enterprise obligation.


Frequently asked questions

Does structured output from an LLM make it safe to use?

Structured output makes a model's answer parseable, not correct. A valid object can still hold an invented ID, an account outside the user's scope, an amount from the wrong document or a tool call that breaks policy. Validate meaning, evidence, authority and state before acting on it.

What should a schema contract between AI steps include?

A good contract carries identity and version, structure, meaning (units, basis, time, scope), status such as proposed or approved, provenance, classification and authority scope, validation rules, typed failures, and rules for how it may evolve.

How do you change a schema without breaking running AI workflows?

Check semantic compatibility, not just whether old payloads still parse. New enum values, changed defaults, units or status can change behavior. Bind contract and mapping versions to each published workflow, and keep running cases on their versions unless migrated deliberately.

How does this work for agents we already run?

Route the agent's tool calls through a governed runtime over MCP or HTTPS. Each call meets an explicit contract and mapping, is checked by policy before and after the step, and its validated outcome is recorded with the contract version used.

With Frozion

How Frozion puts a contract on every step

In Frozion, API Client Builder gives every API client explicit contracts and mappings, and carries a write to an effect the system of record confirms. Collections & Query Builder works with typed operational collections, join and result contracts, and query receipts. Policy is enforced before and after every step of a workflow built in Visual Orchestration. Workflow version compare, batch simulation on recorded cases and approval to publish govern every change, and the decision record shows which version handled each case.


Sources

  1. JSON Schema Draft 2020-12Machine-readable vocabulary for validating JSON structure.
  2. OWASP Input Validation Cheat SheetSyntactic versus semantic validation, applied early.
  3. OWASP LLM05:2025 Improper Output HandlingValidate model output before it reaches downstream systems.
  4. OpenAPI SpecificationLanguage-agnostic description of HTTP interfaces.
  5. W3C SHACLValidating graph data against explicit constraints.
  6. RFC 9457, Problem Details for HTTP APIsTyped, machine-readable errors instead of free text.
  7. CloudEventsCommon event identity and metadata.
  8. W3C PROV-OProvenance for contracts, mappings and validation decisions.
  9. NIST AI Risk Management FrameworkLifecycle testing, documentation and monitoring for AI systems.
More in Work that finishes

Work that finishes

Retries & side effects 2
Durable & event-driven 1
Contracts & routing 3
APIs & integration 1

Other topics

Pick up where you left off: