ai-agentsstructured-outputsjson-schematool-callingarchitectureinteroperability

Data Contracts for AI Agents: Typed Context, Structured Outputs, and Schema Evolution

By John Babich10/19/202611 min read
Intermediate
Data Contracts for AI Agents: Typed Context, Structured Outputs, and Schema Evolution

Data Contracts for AI Agents: Typed Context, Structured Outputs, and Schema Evolution

Natural language is a wonderful interface between a person and an agent.

It is a terrible invisible interface between two production systems.

Consider the instruction: "If the customer is eligible, apply the normal loyalty adjustment and let billing know."

What counts as eligible? Which adjustment? Is the amount in dollars or cents? Does "let billing know" mean create a ticket, send an email, or emit an event? What happens if the customer has two accounts? Can the action be retried?

A capable model can fill those gaps. That is exactly the problem. Different runs may fill them differently.

Reliable agents use language to understand intent and data contracts to cross boundaries. A contract defines the shape, meaning, version, and validation rules of information moving between the user, agent, tools, other agents, durable state, and downstream applications.

The schema is not clerical overhead. It is where an agent stops improvising and starts participating in a system.


TL;DR

  • Put typed contracts around context, plans, tool calls, handoffs, events, and final outcomes.
  • Validate at both sides of every boundary; never trust model output because it "looks like JSON."
  • Define semantics, units, provenance, and null behavior, not only field types.
  • Use explicit contract versions and compatibility rules.
  • Prefer additive evolution and tolerant readers, but reject unknown fields at high-risk write boundaries.
  • Return structured, actionable errors that help the agent repair a request safely.
  • Store the contract version with every trace, artifact, and side effect.

Why prompt clarity is not enough

Prompt engineering improves the probability that a model behaves as intended. Contracts decide whether the rest of the system accepts what it produced.

Those are complementary controls.

A prompt might say:

Include the refund amount and explain the reason.

A contract can require:

{
  "type": "object",
  "required": ["accountId", "amountMinor", "currency", "reasonCode"],
  "properties": {
    "accountId": { "type": "string", "minLength": 1 },
    "amountMinor": { "type": "integer", "minimum": 1 },
    "currency": { "type": "string", "pattern": "^[A-Z]{3}$" },
    "reasonCode": {
      "type": "string",
      "enum": ["duplicate", "service_failure", "policy_exception"]
    },
    "note": { "type": "string", "maxLength": 500 }
  },
  "additionalProperties": false
}

The prompt guides reasoning. The schema prevents $12.99, twelve dollars, and 1299 from becoming three interpretations of the same field.

JSON Schema exists precisely to describe and validate JSON structure, and its guidance emphasizes loosely coupled documents that can evolve with minimal breakage. (JSON Schema use cases)

Find every boundary

Teams often add schemas only to tool calls. That is a good start, but agents cross more boundaries than tools.

User intake

Convert free-form requests into a typed task brief:

  • objective
  • constraints
  • allowed resources
  • deadline
  • approval policy
  • expected deliverable
  • unresolved questions

Keep the original request alongside the parsed object. Structured context should not destroy source evidence.

Planner to executor

A plan needs stable step IDs, dependencies, expected effects, completion criteria, and budgets. "Research competitors" is a note. A plan step with required artifacts and exit conditions is executable state.

Agent to tool

Tool arguments need validation, authorization, idempotency, and explicit units. Tool results need a declared success envelope and typed error variants.

Agent to agent

Handoffs need task identity, delegated scope, evidence, status, and response expectations. Without a contract, the receiving agent must reverse-engineer intent from prose.

Runtime to storage

Memory, checkpoints, event logs, and artifacts need versioned formats. Otherwise today's agent cannot reliably resume yesterday's work.

Agent to application

The final response may be conversational, but downstream state should be structured: outcome, evidence, actions taken, unresolved risks, and required follow-up.

If information crosses ownership, time, process, trust, or model boundaries, it needs a contract.

A contract is more than a type

amount: number is syntactically typed and operationally dangerous.

A useful contract defines:

  • Meaning: refund total or line-item adjustment?
  • Units: major currency units or minor units?
  • Range: can it be negative or zero?
  • Authority: who supplied or approved it?
  • Freshness: when was the value observed?
  • Null behavior: unknown, not applicable, or intentionally withheld?
  • Sensitivity: may it appear in logs?
  • Version: which interpretation applies?

Consider an evidence-aware field:

type ObservedValue<T> = {
  value: T;
  observedAt: string;
  sourceId: string;
  confidence?: number;
};

This is more verbose than a primitive. It is also much easier to audit when an agent makes a consequential decision from stale data.

Pair this approach with Evidence Provenance for AI Agents when claims or decisions must remain traceable.

Design the success and error envelopes together

Many tool schemas carefully specify success and throw an unstructured exception for everything else.

That leaves the model with a string such as "request invalid," which invites guessing and blind retries.

Use discriminated result types:

type ToolResult<T> =
  | {
      ok: true;
      data: T;
      receiptId: string;
      committedAt?: string;
    }
  | {
      ok: false;
      error: {
        code:
          | "VALIDATION_FAILED"
          | "PERMISSION_DENIED"
          | "CONFLICT"
          | "RATE_LIMITED"
          | "OUTCOME_UNKNOWN";
        message: string;
        retryable: boolean;
        fieldErrors?: Array<{ path: string; rule: string }>;
        retryAfterMs?: number;
      };
    };

The error tells the runtime what is safe:

  • repair invalid arguments
  • request a missing permission
  • re-read state after a conflict
  • wait before a bounded retry
  • stop and reconcile an unknown outcome

Do not expose stack traces, secrets, or provider internals to the model. Actionable does not mean unrestricted.

Validate on both sides

The caller validates before sending. The receiver validates again.

Why duplicate the check?

  • The caller may have a bug.
  • A stale client may use an old schema.
  • Requests may bypass the expected route.
  • The model may produce values that pass syntax but violate business rules.
  • Authorization depends on server-side state the caller cannot trust itself to evaluate.

Use four validation layers:

  1. Structural: required fields, types, enums, formats.
  2. Semantic: start time precedes end time; totals match line items.
  3. Policy: actor may perform this action on this resource.
  4. State: object version and business preconditions still hold.

Structured output solves only the first layer.

Strict writers, tolerant readers, deliberate exceptions

A common API-evolution rule is "be conservative in what you send and liberal in what you accept." For agents, that needs nuance.

Read-oriented boundaries

Tolerant readers are useful. A consumer can ignore new optional fields while preserving known fields. This allows additive evolution.

Write-oriented boundaries

Unknown fields may be dangerous. A misspelled dryRun field that gets ignored could turn a simulation into a real action. For high-risk writes, reject unknown properties and unsupported enum values.

Model-facing boundaries

Keep schemas compact. Deep unions, dozens of optional fields, and overloaded objects increase tool-selection and argument errors. Split tools when their authorization or effects differ, not merely to satisfy an aesthetic preference for small functions.

The right strictness depends on the consequence of misunderstanding.

Version semantics, not just schemas

Two schemas can be structurally identical and semantically incompatible.

If priority: 1 changes from "highest" to "lowest," the JSON still validates. The contract broke anyway.

Use an explicit version in the envelope:

{
  "contract": "com.agentforgehub.support.case",
  "version": "2.1",
  "payload": {}
}

Apply semantic-versioning ideas to contract meaning:

  • Patch: clarification or validation fix that does not change valid behavior.
  • Minor: backward-compatible addition, such as an optional field.
  • Major: removal, required-field addition, type change, or semantic reinterpretation.

Always identify the JSON Schema dialect with $schema. The JSON Schema project has documented how changing dialect behavior can produce surprising incompatibilities, which is why explicit dialect and compatibility policy matter. (Moving Toward a Stable Spec)

Prefer additive evolution

Suppose an agent handoff currently carries:

{
  "taskId": "task_7",
  "objective": "Resolve the disputed invoice"
}

Adding optional fields is usually safe:

{
  "taskId": "task_7",
  "objective": "Resolve the disputed invoice",
  "deadline": null,
  "evidenceRefs": [],
  "approvalPolicy": "human_before_write"
}

Turning deadline into a required timestamp is not. Neither is changing evidenceRefs from artifact IDs to raw URLs without a major version.

Safe evolution patterns include:

  • add optional fields with documented defaults
  • add enum values only when old consumers handle unknown values
  • create a new field before retiring an old one
  • dual-read during migration
  • preserve original payloads for replay
  • publish a deprecation deadline and consumer inventory

Do not ask the model to translate incompatible versions ad hoc in a prompt. Use a deterministic adapter with tests.

Distinguish missing, null, empty, and unknown

These values are often collapsed and should not be:

  • missing: sender did not provide the field
  • null: sender explicitly has no value
  • empty string: sender provided a value with zero characters
  • empty list: sender knows there are no items
  • unknown: value exists but has not been determined

For consequential fields, model the state explicitly:

type KnowledgeState<T> =
  | { status: "known"; value: T }
  | { status: "unknown"; reason: string }
  | { status: "not_applicable" };

This prevents an agent from interpreting "we do not know the customer's jurisdiction" as "the customer has no jurisdictional restrictions."

Give plans typed completion criteria

Agent plans often fail because completion is expressed as prose.

Instead of:

Review the account and fix any issues.

Use:

{
  "stepId": "review_account",
  "dependsOn": [],
  "allowedTools": ["get_account", "list_open_cases"],
  "outputSchema": "account-review@3",
  "completeWhen": [
    "account_state_verified",
    "open_cases_classified",
    "evidence_refs_present"
  ],
  "maxAttempts": 2
}

The model still performs judgment. The harness gains a deterministic way to detect missing work, enforce budgets, and hand the result to another process.

Context Engineering for AI Agents explains what to put in the context. Data contracts define how that context remains stable when passed around.

Contracts improve agent-to-agent systems

Multi-agent designs multiply interface risk. One agent's elegant narrative becomes another agent's ambiguous input.

A handoff contract should include:

  • globally unique task and run IDs
  • sender and intended recipient
  • objective and non-goals
  • delegated permissions
  • input artifact references
  • assumptions and unresolved questions
  • expected output contract
  • deadline and budget
  • cancellation and escalation behavior

The receiving agent should acknowledge the accepted version and scope. If it cannot satisfy the contract, it should reject the handoff explicitly rather than improvising a partial interpretation.

Protocol standards help with transport, discovery, and message shape, but your business semantics remain your responsibility. The 2026 MCP specification's stronger routing, authorization, task, and deprecation mechanisms are useful infrastructure; they do not define what approve_refund means in your company. (MCP 2026-07-28 release)

Test contracts as products

Contract testing for agents needs more than one valid example.

Positive fixtures

Representative valid payloads, including minimum and maximum shapes.

Negative fixtures

Missing required fields, unknown enums, wrong units, malformed identifiers, extra properties, and impossible combinations.

Property-based tests

Generate many payloads around boundaries: zero, negative, large, Unicode, time zones, empty arrays, and duplicated IDs.

Compatibility tests

Run old consumers against new producers and new consumers against old payloads.

Model-generation tests

Ask the model to produce the contract across realistic phrasings, corrections, adversarial inputs, and tool failures. Validate the actual output, not a cleaned version.

Replay tests

Replay stored runs with the original contract versions. If a schema registry or adapter changed, confirm the historical run remains interpretable.

Observe contract health

Track:

  • validation failures by contract, version, and field
  • repair attempts per request
  • unknown enum and unknown-field rates
  • adapter usage by source and destination version
  • semantic and policy rejection rates
  • tool errors caused by stale contracts
  • percentage of runs with complete version metadata

A sudden rise in one field's failures often reveals a prompt, model, upstream API, or user-interface change before the overall success metric moves.

Emit named state-change events rather than unstructured log sentences. OpenTelemetry recommends stable event names and structured attributes for meaningful lifecycle occurrences. (OpenTelemetry event conventions)

Implementation checklist

  • Inventory every agent boundary.
  • Define structure and semantics.
  • Include units, provenance, freshness, and sensitivity where relevant.
  • Version contracts explicitly.
  • Pin long-running work to accepted versions.
  • Validate on both sides.
  • Use structured success and error envelopes.
  • Reject unknown fields for consequential writes.
  • Prefer additive evolution.
  • Use deterministic version adapters.
  • Test positive, negative, generated, and compatibility cases.
  • Attach contract versions to traces and side effects.

The bottom line

Natural language is where agents become flexible. Contracts are where they become dependable.

You want the model to reason creatively about the customer's problem. You do not want it to reason creatively about whether an amount is measured in dollars or cents.

Make ambiguity a product choice, not an accidental API.


Further reading

Related Tools

Useful tools for this topic

If you want to turn this article into a concrete next step, start with one of these.

Subscribe to AgentForge Hub

Get weekly insights, tutorials, and the latest AI agent developments delivered to your inbox.

No spam, ever. Unsubscribe at any time.

Loading conversations...