●NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaply●OMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflows●AGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactions●MEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuously●THROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and region●DEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20●NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaply●OMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflows●AGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactions●MEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuously●THROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and region●DEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20
Recording Provenance for Gemini Output — Designing for Reproducibility and Audit
Before you lose track of which model and prompt produced an output months later: how to stamp provenance metadata onto Gemini generations so quality investigations and model migrations stay reproducible.
About six months after I started using Gemini to auto-classify wallpapers in one of my apps, a user reported that an image had been filed under "abstract" instead of "night view." I wanted to fix it — but I couldn't tell which model had made that call, or what prompt I had sent. The output was there in my logs; the provenance was not.
I have run apps as a solo developer since 2014. Across more than 50 million cumulative downloads, my wallpaper, relaxation, and mindfulness apps now lean on Gemini under the hood. Once you put generative AI into production, these "explain yourself after the fact" moments are inevitable. And what saves you then is not the output itself, but the record of when, with which model, prompt, and settings it was born. Here is a design — at the implementation level — for stamping provenance onto generated output so that, even half a year later, you can reconstruct the exact conditions.
The minimal fields worth stamping
Provenance bloats your storage if you record too much, and becomes useless for reproduction if you record too little. The minimal set that proved neither excessive nor lacking in my classification and summarization features is this.
For generation conditions: the model name (an ID like gemini-2.5-flash), the model generation at that point in time, and the sampling parameters — temperature, thinking_budget, and seed. For content fingerprints: the prompt template ID and its canonical hash, a hash of the full input, and a hash of the full output. For environment: the SDK version and the generation timestamp. With just these you can answer both "can I rebuild the same conditions?" and "has the output drifted from what it was?"
One thing matters here: do not store the full prompt text. Prompts often mix in user input, which risks carrying PII. Keep a hash rather than the body, and version the template separately.
The heart of provenance is the prompt hash. But naively running the raw prompt string through SHA-256 invites a subtle bug: the same meaning produces a different hash. Variables in a different order, one extra space, a stray newline character. To absorb this drift, insert a canonicalization step before hashing.
The canonical_prompt above joins the template ID with a key-sorted JSON of the variables. That makes {"style": "night", "lang": "en"} and {"lang": "en", "style": "night"} hash identically. Keep the template body under separate management, and adopt the rule that any change to the body bumps the template ID. That single discipline avoids the worst case: "the prompt body changed but the hash stayed the same."
I embed a version number in the template ID, like wallpaper-classify-v3, and version the body in the repository. Change one character of the body, bump the version. My grandfathers were temple carpenters, and they left ink-marked guidelines so the next generation could trace the same work. A prompt hash feels like exactly that kind of traceable ink mark.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦The minimal set of provenance fields to stamp on every generation: model version, prompt hash, temperature, seed, and input/output hashes
✦Where to draw the line between what is and is not reproducible on Gemini, even at temperature 0, based on real measurements
✦How a provenance sidecar let me reproduce a wallpaper-classification misfire in three minutes
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
If you treat provenance as something to "add later," you will forget it. The reliable approach is to wrap the generation call itself so that output and provenance always come back together.
The caller simply receives output, prov = generate_with_provenance(...). So that a provenance-less path can be caught in review, I keep a strict rule — even as a team of one — that no code touches the raw API anywhere except inside this wrapper.
Even at temperature 0, you can't fully reproduce it
This is the most misunderstood point. People assume that fixing temperature=0 and a seed yields the identical output every time. On Gemini, it does not. Temperature 0 strengthens the tendency to pick the highest-probability token, but it guarantees no determinism. Backend model updates, floating-point non-determinism, and hardware differences all let the same input drift.
In my own wallpaper-classification measurements, even at temperature 0 with a fixed seed, sending the same image 100 times produced a few results in a different category. The agreement rate sat around 97% — roughly 3% drifts. That is exactly why a provenance design must separate "what can be reproduced."
Reproducible are the generation conditions. Which model, which prompt, which settings — with provenance, you can rebuild them exactly. Not reproducible is bit-for-bit output equality. Give that up, and instead use output_hash as a comparison key for "is today's output the same as the one from back then?" If the hash matches, it is identical; if not, use the other provenance fields to separate "did it drift, or did a condition change?" Framing reproducibility as "condition reproduction" plus "output comparison" keeps the design from wobbling.
Where to store provenance — embedded, sidecar, or database
There are three main options for storage, each with a different character. Here are situational recommendations.
The first is embedding it into the output: add a _provenance key inside the generated JSON, so output and provenance never physically separate. The downside is that it pollutes the output schema, so it is a poor fit when you want to keep it apart from what you return to users.
The second is a sidecar: place provenance in a separate file or record from the output body, linked by a shared ID. This is what my wallpaper classification uses. It avoids polluting the output schema and makes it easy to later compress or delete provenance on its own.
The third is centralizing into a dedicated table. If you need cross-cutting queries — "pull only generations with a specific prompt hash from last month" — this is the strongest option.
import sqlite3def save_provenance(db, record_id: str, output: str, prov: Provenance): db.execute( "INSERT INTO generations(id, output, prov) VALUES(?, ?, ?)", (record_id, output, json.dumps(asdict(prov), ensure_ascii=False)), ) db.commit()def replay_conditions(db, record_id: str) -> dict: # Pull the provenance needed to rebuild the original generation conditions row = db.execute( "SELECT prov FROM generations WHERE id = ?", (record_id,) ).fetchone() prov = json.loads(row[0]) return { "model": prov["model"], "prompt_template_id": prov["prompt_template_id"], "prompt_hash": prov["prompt_hash"], "temperature": prov["temperature"], "seed": prov["seed"], }
At indie scale, I'd recommend starting with a sidecar and moving to a dedicated table only once the need for cross-cutting investigation appears. There is no need to build heavy infrastructure up front.
The moment audit paid off — reproducing a misfire in three minutes
Back to that misclassification report. After provenance was in place, a similar incident played out completely differently. From the reported image's ID I pulled the provenance, rebuilt the conditions with replay_conditions, and re-ran with the same prompt template and settings. The whole thing took about three minutes.
When I reproduced it, the provenance's template ID immediately revealed that the misclassification was not model drift — it came from dropping a category description when I bumped the prompt template from v2 to v3. Without provenance, I would have spent hours staring at the output, wondering whether the model or the prompt was at fault.
The speed of that investigation matters most for the features tied directly to AdMob revenue. Classification quality shapes the experience, and the experience shapes retention. When something breaks in production, the time it takes to isolate the cause is the scarcest resource a solo developer has. I treat provenance as an investment to buy that time back.
Pitfalls that are easy to hit
Finally, here are the points where adding provenance to production tends to trip people up, each paired with how to handle it.
Not stripping PII before hashing. Hashing the full input does not let anyone recover PII, but if you dump the prompt body into logs alongside it, it leaks. Keep only the hash in provenance, and separate the body logs.
Changing the prompt body while leaving the template ID untouched. Do this and a different prompt carries the same hash — your provenance now lies. Avoid it with the discipline of always bumping the version on a body change.
Not recording the SDK version. When an SDK update shifts a default, behavior changes silently. Keeping sdk_version lets you suspect environment drift before you suspect the model.
Recording provenance only on success. Errors and empty outputs are exactly what you want to reproduce. Stamping provenance on failures too raises the resolution of your investigations by a level.
Every one of these is preventable with a single line of care in the initial design. Try to bolt it on afterward, and your past provenance is locked in incomplete.
The next step
Pick the single generation path that causes the most trouble, and route only that one through a thin wrapper like generate_with_provenance. You do not need to replace every path at once. Once you have felt, on a single path, what it is like to "rebuild the original conditions in three minutes," the value of recording provenance becomes a concrete, tangible thing.
If you are running generative AI in production too, I hope this helps a little with isolating causes when something goes wrong.
Share
Thank You for Reading
Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.