Function Calling Schema Evolution: Versioning and Boundary Adaptation in Practice

Use versioned schemas plus boundary adapters to evolve model contracts without breaking live conversations.
The article addresses schema changes crashing in-flight conversations in Function Calling and Structured Outputs scenarios, proposing a "versioned schema + boundary adapter" solution. The core idea is decoupling the model-visible wire schema from the application-consumed data structure: keep multiple JSON Schema versions coexisting, validate strictly server-side with additionalProperties: false and a required list, then have an adapter translate each version's shape into a unified canonical object. The article stresses that the adapter carries organizational decisions like field renaming, null handling, and default synthesis that must be explicitly defined and tested; unrecognizable shapes should error out. The pattern doesn't provide unlimited backward compatibility and requires actively managing the old-version window and adapter retirement.
Why Schema Changes Break In-Flight Conversations
When you modify a Function Calling or Structured Outputs schema, active conversations don't automatically reset. Old conversation turns and replayed tool calls can still emit the previous data shape. A simple field rename, or adding a new required field, is enough to crash the next conversation turn.
The root of this problem is that the contract the model sees (the wire schema) is coupled to the data structure the application consumes. Once the schema updates, old data can no longer pass the new validation.
Based on developer Chris Eugene Rodriguez's practical approach, this article breaks down how to use a "versioned schema + boundary adapter" pattern to make schema evolution stop breaking live conversations.

Core Approach: Four Steps to Decouple the Model Contract from Application Logic
1. Version the Model-Visible Wire Schema
Keep multiple versions of the JSON Schema definition—v1, v2, and so on—instead of overwriting the old version. The model is guided to output the latest version in new conversations, but the system still holds the old version definitions for validation and adaptation.
In the original example, v1 uses orderId as the field name, while v2 renames it to order_id and adds a new required priority field (allowing boolean or null). Both versions coexist without interfering with each other.
JSON Schema is the standard specification for describing JSON data structures. In Function Calling and Structured Outputs scenarios, it is used to tell the model "what tool arguments or outputs should look like." Common keywords include type (data type), properties (object field definitions), required (list of required fields), and additionalProperties (whether undeclared fields are allowed). Different model vendors support different subsets of JSON Schema; Structured Outputs typically supports only a restricted subset—for example, prohibiting root-level anyOf or limiting regex complexity. Understanding this constraint matters: a schema is not just documentation but a contract that participates in validation and constrains generation at runtime, so every modification is equivalent to an interface change and must be handled as carefully as an API version.
2. Enforce Strict Validation at the Server Boundary
The validation strategy uses additionalProperties: false plus an explicit required list. This way any output deviating from the schema fails immediately, and the reason for failure can be pinpointed precisely—whether the model emitted a field outside the schema or omitted a required field for that version.
The original article emphasizes: failures should be "loud and explainable." Don't let malformed payloads silently flow into downstream logic.
additionalProperties: false is the keyword in JSON Schema that controls "extra fields." By default, JSON Schema is lenient toward fields not declared in properties, and extra fields emitted by the model are silently ignored. Setting it to false causes any undeclared field to fail validation. Combined with an explicit required list, it can catch both kinds of deviation at once: "extra fields that shouldn't be there" and "missing fields that must be there." The value of this strict mode is exposing problems at the boundary rather than letting malformed data flow into downstream business logic and cause failures that are harder to trace. In practice, validation libraries like Ajv and Zod are commonly used; they return precise paths and reasons, making it easy to distinguish model overreach from version mismatch.
3. Normalize to a Single Canonical Object via an Adapter
Write an adapter that recognizes known wire versions and translates them into a canonical object that the application consumes uniformly.
Taking the original code as an example: the adaptToCanonical function checks whether the input contains orderId (v1) or order_id (v2), handles each accordingly, and outputs a unified { orderId, items, priority } structure. For priority, which doesn't exist in v1, the adapter fills in the default value false.
This adapter is the single point of landing for the compatibility strategy.
The Adapter Pattern is a classic structural pattern in software engineering: inserting a layer of translation code between two incompatible interfaces so that the caller doesn't need to know the concrete shape of the callee. Here, the adapter receives wire schema data from different versions and outputs a canonical object that is uniform inside the application. Its key design principle is "single point of landing"—all compatibility judgments happen only in this one function, and downstream code always faces only one data structure. The benefit is that when an old version needs to be retired in the future, you only need to delete the corresponding branch in the adapter, keeping the blast radius controllable; at the same time, tests can be concentrated on the adapter, covering every known version and unrecognizable shapes.
4. Make Adaptation Strategy Explicit
The adapter hides all compatibility decisions: how to handle field renames, whether null is equivalent to optional, whether missing data should be synthesized into default values. The original article points out that these are organizational choices that must be explicitly defined and tested.
For unrecognizable payload shapes, the adapter should throw an error (the original code throws unrecognized argument shape) rather than handling them speculatively.
Expected Behavior and Boundaries
What the System Should Do
- New conversations are guided to use the latest schema without immediately invalidating old conversations.
- During the compatibility window, when replayed or still-in-progress conversations emit old shapes, the adapter can recognize and convert them into canonical objects.
- Invalid payloads fail clearly at the validation boundary, and the error message is sufficient to distinguish: a model proposal exceeding the schema, a required field missing for that version, or a completely unrecognizable shape that needs additional adaptation strategy.
What This Pattern Does Not Do
The original article explicitly lists the limitations:
It does not provide unlimited backward compatibility. You need to decide the time window for accepting old versions and when to remove the adaptation logic. This is technical debt that must be actively managed.
It is constrained by the schema capabilities of Structured Outputs. Official guidelines restrict certain JSON Schema features (such as root-level anyOf and lenient keywords). If compatibility needs depend on unsupported constructs, put the logic in the adapter rather than the schema itself.
Strategic decisions cannot be automated. Renaming, null handling, default value synthesis—these are organizational choices that need to be explicitly defined and tested.
The backward compatibility window refers to the period during which the system still accepts old-format input after an upgrade. In schema evolution, this window must be explicitly defined: from when the new version goes live until the old version adaptation logic is removed. If the window is too short, it may interrupt still-active user sessions or historical replays; if too long, adapter code and technical debt keep accumulating. In engineering practice, the retirement timing is usually decided based on monitoring data—for example, tracking the frequency of old-version payloads and removing the adaptation branch once it approaches zero and stays there for a period. This is essentially the same as the deprecation process in API version management, except the object has changed from an HTTP interface to a model output contract.
Why This Pattern Is Worth Adopting
Separating the contract the model faces from the server-side adapter keeps downstream code simple and stable while allowing the model-facing schema to evolve independently. The original article summarizes three benefits: releases can be rolled back, migration windows are made explicit, and failures are actionable.
From an engineering perspective, this essentially transforms "schema migration" from a one-time breaking change into a controllable, windowed, gradual process. The adapter plays the role of the translation layer, allowing the old and new data shapes to coexist during the transition period.
For any team using Function Calling or Structured Outputs in production, this pattern provides a practical reference framework—especially in conversational applications, where user sessions often span multiple deployments.
One-line Summary
Versioned schemas plus boundary adapters let model contracts evolve without breaking live conversations.
Paragraph Summary
For Function Calling and Structured Outputs scenarios where schema changes crash in-flight conversations, the article proposes a "versioned schema + boundary adapter" solution. The core idea is to decouple the wire schema the model sees from the data structure the application consumes: keep multiple JSON Schema versions (v1, v2) coexisting, perform strict server-side validation with additionalProperties: false and a required list, then have an adapter translate each version's shape into a unified canonical object. The article stresses that the adapter carries organizational decisions such as field renaming, null handling, and default value synthesis, which must be explicitly defined and tested; unrecognizable shapes should error out directly. This pattern does not provide unlimited backward compatibility and requires actively managing the old-version acceptance window and adapter retirement timing.
Related articles

Chain of Thought Isn't Just for AI: How Humans Can Use CoT to Improve Thinking Quality
Chain of Thought isn't just a prompt trick for large models. Starting from a Hacker News discussion, this article analyzes why CoT works for human thinking too, breaks down actionable ways to transfer it, and highlights the key differences between human CoT and machine CoT.
Cloudflare Teams Up with OpenAI: Closi…
Cloudflare Teams Up with OpenAI: Closing the Loop from Vulnerability Discovery to Defense with AI
Cloudflare uses OpenAI Daybreak to investigate security vulnerabilities and generate reviewable remediation plans, while developers use Codex to boost coding efficiency and quality.

Orca Multi-Agent Orchestration in Practice: A Guide to Parallel Collaboration with Claude and Codex
Orca is Stable AI's free open-source agent workbench supporting 34 agents like Claude Code and Codex in parallel. Learn its Git worktree isolation, multi-model orchestration, and mobile management.