Every team that stores structured LLM output, such as extracted fields, classifications, or generated plans, eventually changes the prompt or schema that produced it. Then they discover the database now contains rows written by three different versions of that prompt, and the code that reads them quietly assumes only the latest.
Stamp each record with the version that made it
The cheapest habit with the best payoff: store, next to every generated record, the identifiers of what produced it.
{
"id": "rec_8841",
"data": { "category": "billing", "urgency": "high" },
"meta": {
"schema_version": 3,
"prompt_version": "triage-2026-09-14",
"model": "model-name-as-configured"
}
}With this in place, questions that are otherwise unanswerable become simple queries: how many rows came from the old prompt, did the quality complaints start after a specific version, which records need reprocessing.
Separate schema version from prompt version
These change at different rates. A prompt tweak that improves accuracy does not change the shape of data; adding a field does. Keep schema_version for shape, which readers must understand, and prompt_version for provenance, which only analysts care about. Readers should branch on schema version only.
Migrate on read, backfill deliberately
When the shape changes, resist rewriting every row immediately. A small upcaster lets the reader handle old records:
def upcast(rec):
v = rec["meta"]["schema_version"]
data = rec["data"]
if v < 2:
data["urgency"] = data.pop("priority", "unknown") # renamed field
if v < 3:
data.setdefault("language", None) # new optional field
return dataEach version bump adds one step, so any old record can be brought forward in order. Then backfill on your own schedule: reprocess the records where the new fields actually matter, and leave the rest alone.
A field that was *added* needs special care. Old rows do not hold "no value"; they hold "never asked". Use an explicit unknown value rather than a default that looks like a real answer, or downstream analysis will treat missing data as a measurement.
Test the upcasters
Keep one frozen fixture per historical schema version and assert that each upcasts to the current shape. These tests are cheap, and they are the only thing protecting you from the day someone deletes a branch that looks dead.
The closing point: generated data outlives the prompt that generated it. Record the version at write time, upcast at read time, and decide deliberately when a backfill is worth paying for.
Comments (0)
No comments yet — be the first.
Sign in to leave a comment.