AI agents

Versioning an Approval Schema Without Breaking Queues

Antonio Evans8 min read

A split-screen diagram: on the left, an approval card with an older set of fields and a small version tag 'v1'; on the right, a newer card with an extra required field and tag 'v2'.
Key points

Key points

  1. 1Add a new field as optional first, dual-write it on every approval created after the change, and backfill open approvals in batches before making the field required.
  2. 2Treat additive edits (new optional fields, wider enums, new render branches) as safe to deploy immediately and breaking edits (new required fields, removed fields, changed types) as needing a compatibility window.
  3. 3Tag every approval card with the schema version it was written under, so the reviewer UI can pick a reader instead of guessing from the payload shape.
  4. 4Run two readers side by side: one for old cards, one for new cards, until the count of in-flight cards without the field reaches zero and every reviewer client can render the new shape.

Classify the edit before you touch the schema

Most approval queues break not because the change was large but because it was applied to records that were already mid-flight. The first step is to name the edit. Schema evolution guidance splits changes into two families: compatible (backward and forward) changes such as adding or removing optional fields, and breaking changes such as adding a required field or removing one that consumers rely on Confluent's compatibility tables and Data Expert's schema evolution guide.

The practical rule for an approval schema:

EditSafe to deploy immediately?Why
Add an optional field with no validationYesOld cards remain valid; new cards carry extra data
Add a new enum valueYes, if readers default unknown valuesOld readers must not crash on it
Add an optional nested objectYesAbsent object reads as empty
Add a required fieldNoEvery in-flight card fails validation on next save
Remove a field readers still renderNoUI shows blanks or throws
Change a field's type (string to number)NoOld values fail the new validator
Rename a field without an aliasNoBoth readers and writers see missing data

The reason required fields are the dangerous case is structural. A schema's required list applies to the whole document, so marking a field required retroactively invalidates every card that predates the field the JSON Schema reference. You cannot grandfather old documents with the required keyword alone; you need either a compatibility window or conditional validation.

If you truly cannot wait, JSON Schema offers conditional tools. dependentRequired makes one field mandatory only when another is present, and if/then/else lets you require different fields under different conditions the JSON Schema reference, a Stack Overflow walkthrough of four approaches. A common pattern in practice is to require the new field only when a version marker says the card is new, which is the topic of the next section.

Tag every card with the schema version it was written under

A version tag is what turns "which shape is this?" from a guess into a lookup. Store it as an immutable field on the approval record, written once at creation and never updated by edits:

{
  "id": "apr_88213",
  "schema_version": 1,
  "status": "pending",
  "requester": "j.alvarez",
  "amount": 4200,
  "cost_center": null,
  "created_at": "2026-02-11T09:14:00Z"
}

When you introduce the new field (cost_center above), bump to schema_version: 2 for cards created after the change and dual-write: the new field is populated, the old representational fields stay populated too. Cards already in the queue keep schema_version: 1 and remain valid under the version 1 schema file, which you retain alongside version 2 rather than overwrite.

Two rules make this hold:

  • The validator is selected by the tag, not by the current deploy. A card at version 1 is validated against the version 1 schema for as long as it exists, even after the code no longer writes that shape.
  • Edits do not change the tag. If a reviewer edits a version 1 card, it stays version 1 and simply cannot supply the new field. Only a deliberate migration step (covered below) moves a card forward.

This is the same separation that business process tooling makes between a process definition and a running instance: existing instances continue on their original definition while new instances pick up the latest one, and migration is a deliberate operation rather than a side effect of deployment Camunda versioning guidance, summarized. Approval rules changing mid-flight is not hypothetical; the same source lays out the three explicit treatments for in-flight documents (continue, migrate, withdraw and resubmit) and warns against silently re-routing them.

One useful side effect of the tag: your approval-time metric becomes sliceable by version. If cycle time jumps the week version 2 ships, you can tell whether the new field slowed reviewers down or whether the queue simply got longer, which is the kind of distinction approval-time reporting is meant to surface the Atlassian approval-time discussion.

Run two readers side by side in the reviewer UI

The reviewer UI is where schema changes actually strand work, because a reviewer looking at ten queued cards does not care which version each one is. The fix is a reader registry: a small map from version number to a render function, with a single dispatcher in front of it.

const readers = {
  1: renderCardV1,   // no cost_center field
  2: renderCardV2,   // cost_center present and shown
};

function renderCard(card) {
  const reader = readers[card.schema_version] ?? readers[highestSupportedVersion()];
  return reader(card);
}

Three details matter more than the code itself:

  • The fallback is explicit and logged. If an unknown version arrives (a client cached an old bundle, or a producer shipped ahead of consumers), render with the highest supported reader and emit a counter. Silent fallbacks hide the exact drift you are trying to detect.
  • The new reader degrades gracefully on old cards. renderCardV2 should display "not provided" rather than an empty box or a validation error when cost_center is missing, so a reviewer can still approve a version 1 card correctly.
  • The dispatcher is the only place that branches on version. Scattering if (version === 2) through components is how you get a UI that half-renders the new shape.

This mirrors the guidance that consumers should be upgraded before producers for backward-compatible changes Data Expert's compatibility guide: ship the UI that can read both shapes first, then start writing the new shape, then worry about enforcement. Retool's guardrail write-up makes the same point at a higher level, noting that agents which can take real actions need layered validation and human oversight rather than a single check Retool on AI guardrails, and the human-in-the-loop queue design described for service desks is the same loop you are versioning Autommate's review queue guide.

Backfill open approvals in batches, without touching decisions

Backfilling means writing the new field onto cards that already exist. Do it in bounded batches with a dry run first, and never touch the decision history. Approval workflows depend on traceability of who approved what and why, so the backfill must add data rather than rewrite it StackAI's approval workflow design.

A workable sequence:

  1. Measure the gap. Count open cards by schema_version where the new field is null. That number is your compatibility window in units of work, not days.
  2. Dry run on a sample. Take 100 cards, derive the field from existing data if you can (for example, map an existing free-text department string to cost_center), and check how many resolve cleanly.
  3. Batch the write. Update in chunks, keyed by card ID, with the version bump applied in the same transaction. Log the before and after value per card.
  4. Re-run the gap count. Repeat until zero.
  5. Freeze writes for the last batch only. Cards created during the backfill already carry the field through dual-write, so the only risk is a card edited mid-batch; a short write lock on the batch range closes it.

Worked example. Suppose 3,400 approvals are open, 2,100 of them at version 1. A nightly batch of 300 cards takes seven nights at roughly 15 percent of the queue per pass. If each pass derives cost_center correctly for 92 percent of cards and flags the rest for reviewer input, you need a small reviewer task for about 168 cards, which is far cheaper than blocking the queue for a week while every card is rewritten by hand. Compare that against the alternative of forcing the field immediately: all 2,100 version 1 cards fail validation on the next save, and the queue stops.

The reversibility test from technology planning applies directly: a backfill you can rerun is a two-way door, while a schema flip that invalidates data is closer to a one-way door, so plan the batch process before you need the escape hatch PTG on reversible decisions. The same discipline that firewall change management applies to rollback plans (write the rollback before approval, not after the outage) applies to your backfill script the firewall change management guide.

Set the gate that makes the field required

The field becomes required only when two independent conditions hold, and both should be checked automatically rather than asserted in a meeting:

GateTestPass condition
DataCount of open cards with a null new fieldZero
ClientsShare of reviewer sessions on a build that renders version 2100 percent, measured from your own telemetry
ValidationVersion 1 schema retired from the active dialectNo writer can produce version 1
RollbackReverting the required flag restores the previous behaviorVerified in staging

Only then change the schema: remove the field from the nullable path, add it to required, and retire the version 1 reader from the registry (keep the file for archival validation). If you want a softer landing, keep the field optional in the stored schema and enforce it in the write path for new cards only, which is exactly what conditional keywords like dependentRequired are built for the JSON Schema reference. Many teams never flip required at all; they enforce at creation time and let the historical cards stay as they are, which is a legitimate end state.

Two failure modes to plan against. First, agents and automation writing into the approval system can produce version 1 cards long after humans stopped, so check the producer list, not just the UI. Second, mobile or embedded reviewer clients update on their own schedule; counting sessions rather than installs is the honest measure, and the same "old client, old schema" problem is called out as a standard feature of schema evolution ByteByteGo on schema evolution and Simon Späti's notes on the same.

If the queue is fed by an AI agent that drafts or triages approvals, the schema change is also a guardrail change: the agent's output must validate against whichever version it is writing, and anything it cannot populate should escalate rather than guess Sweet Security's guardrail taxonomy, Aembit on runtime controls, Salesforce's guardrail guide. Note how ITIL-style change classification maps onto your schema edits: an additive optional field is a standard change, a new required field is a normal change with approval, and a coordinated cutover across validators, UI and producers is closer to a major change Virima's ITIL change types.

When the next schema revision touches several model outputs at once (a triage label, an amount extract, a risk score), routing all of those calls through one endpoint keeps the version contract in one place; Quanta Inference exposes text, image, video, audio and embedding models behind one OpenAI-compatible key, which simplifies writing a single validated card shape regardless of which model produced the fields.

FAQ

FAQ

Why can't I just make the new field required and let old cards fail?

Because "fail" in an approval queue means stalled work, not a clean error. Old cards will fail on next save or next validation pass, and reviewers see broken screens rather than a prompt for the missing value. The window exists to convert a hard failure into a gradual fill-in.

How long should the compatibility window last?

Long enough to cover the open queue plus any clients that update slowly. Measure it in work: batches of your backfill times the number of passes, plus the time for reviewer sessions to reach 100 percent on the new build. A day count chosen in advance is a guess; a zero count of null fields is a fact.

Do I need a schema registry to do this?

No, but you need the registry's two behaviors: version-pinned validation and compatibility checks before a new version is accepted. A folder of versioned schema files plus a validator that selects by tag reproduces the essentials Confluent's schema evolution docs.

What happens to cards a reviewer edits during the window?

They stay at their original version tag. Editing does not promote a card, which keeps its decision history and audit trail intact; promotion is a separate, deliberate migration step. If the edit supplies the new field, store it but do not bump the version.

Should the reviewer UI ever show both card shapes in one list without labels?

Yes, show them together but mark the version visibly, for example a small "legacy" tag on version 1 cards. Reviewers need to know that a missing value is expected rather than a data problem, and the label doubles as your signal that the window is still open.

Sources

Sources

  1. Schema Evolution and Compatibility for Schema Registry ...
  2. Backward Compatibility in Schema Evolution: Guide
  3. Conditional schema validation - JSON Schema
  4. jsonSchema attribute conditionally required - Stack Overflow
  5. When approval rules change, should in-flight documents ...
  6. How to Measure Approval Time in Jira
  7. AI guardrails: Building safer AI with prompts & observability - Retool
  8. Human-in-the-Loop AI: Running the Service Desk Review Queue
  9. Human-in-the-Loop AI Agents: How to Design Approval ...
  10. Make More IT Decisions Reversible | PTG
  11. Firewall Change Management: Process, Tools & Best ...
  12. Schema Evolution: Changing the Contract Without ...
  13. Schema Evolution - Data Engineering Blog & Second Brain
  14. Guardrails for AI Agents: Types, Benefits & How to Implement
  15. Agentic AI Guardrails: What They Are and How to Implement Them - Aembit
  16. AI Guardrails: A Guide to Responsible AI - Salesforce
  17. ITIL Types of Changes: Standard, Normal, Major, and ...
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles