The prompt engineering patterns that survive a model update are the ones carrying information the model can't infer: a role that sets the audience, context it doesn't have, a task with a decision rule, and an output contract enforced outside the prompt. Everything else is folklore with a shelf life.
You can see the split most clearly in JSON. Asking a model nicely for JSON leaves roughly 5-10% of outputs malformed, JSON mode gets you to about 95-99% valid, and schema-constrained decoding is effectively 100% (Ashvara). Same intent, three enforcement layers, wildly different failure rates on release day.
This playbook covers four patterns that keep working across Claude, GPT and Gemini, the brittle tricks worth deleting from your library, and a small eval suite that turns the next model release into a diff instead of an incident.
Why prompts rot: the failure mode nobody versions for
Prompts don't decay at random. They decay along a predictable seam: the parts that lean on how a specific model behaved get invalidated by the next checkpoint, and the parts that state what you want survive it.
Two kinds of prompt: the ones that describe intent and the ones that exploit a quirk
An intent prompt says what the output has to be, who reads it, and what counts as wrong. A quirk prompt says whatever happened to work last Tuesday. All-caps ONLY RETURN JSON. Three repetitions of the same instruction because two weren't enough. A magic preamble copied off a forum thread. Those tricks were tuned against one checkpoint's decoding behavior, and nothing in them tells a future model what you actually need.
Naive "please return JSON" prompting still produces an estimated 5-10% malformed outputs (Ashvara), and that number is a property of the model, not of your prompt. Swap the model and the number moves. You never wrote down the contract, so you have nothing to hold the new model to.
What actually breaks on release day
The breakage is rarely loud. Your parser starts throwing on a trailing comma (roughly 40% of JSON errors in one analysis come from exactly that, Flying Fish Space), or the model gets more conversational and wraps clean output in a sentence of preamble. Meanwhile the pace of new model releases means the checkpoint you tuned against may not be the one serving traffic next quarter.
Compare that to a schema-constrained call, where generation is restricted to the shape you supplied and syntactic validity is essentially 100% (Ashvara). The enforcement lives outside the prompt text. A model update can change tone, verbosity, and reasoning depth without touching it.
The durability test: would this prompt still make sense to a smarter model?
One question, asked of every prompt you own: if the model got twice as capable overnight, would this instruction still be doing useful work?
"Return an object with keys id, status, and confidence, where status is one of three literal values" passes. RFC 8259 already pins the vocabulary you're borrowing: four primitive types, two structured types, and exactly three lowercase literal names (RFC 8259). That instruction is legible to any model, now or later. "Take a deep breath and think step by step" fails, because it's compensating for a weakness the next release may not have. Delete the compensations and keep the contracts.
Prompt engineering patterns that transfer: role, context, task, format
Restructure every ad-hoc prompt into four slots, and put only information the model can't infer into each one. Role, context, task, format. The shell survives model updates because each slot carries facts about your problem, not folklore about how last quarter's checkpoint responded to flattery.
The four slots and what belongs in each
Role is who the output is for and what expertise the answer assumes. "You are a world-class expert" sets a mood and carries no information. "You're writing for a payments engineer who already knows what an idempotency key is" tells the model which explanations it can skip.
Context is everything the model has no way of knowing: the schema, the upstream system, the edge cases you've already hit in production, the fact that your parser rejects a UTF-8 BOM. Task is the single verb and its object. Format is the output contract, and it should be specific enough to validate mechanically.
A format slot that says "return JSON" is a wish. A format slot that names the keys, their types, and what happens when a value is unknown is a contract you can test. JSON gives you four primitive types (string, number, boolean, null) and two structured types, objects and arrays (RFC 8259), so there's a small finite vocabulary to be precise in. Say null rather than "leave it blank", because the literal names true, false and null are lowercase and nothing else is legal (RFC 8259).
Why the shell transfers across Claude, GPT and Gemini
Nothing in the four slots depends on a tokenizer, a system-prompt quirk, or a provider feature flag. Every model has to be told which fields you want and what your downstream code does with them, so the same shell drops into Claude, GPT and Gemini with no rewrite. That portability is also what makes it upgradeable: when a new checkpoint ships, the context slot is still true and the format slot is still the contract your validator enforces. You swap the model, rerun the evals, and the diff is empty.
Most of the 212 prompt engineering tools that template this structure in our directory are selling you the slots as a form. You can get the same effect with a heredoc and four comments.
Writing constraints as facts, not incantations
There's a test for whether a line belongs in your prompt: could a competent contractor act on it without asking a follow-up question? "Be thorough" fails. "Property names are double-quoted, no trailing comma after the last element" passes, and it maps to real failure modes, since trailing commas alone account for roughly 40% of JSON errors in one analysis (Flying Fish Space).
An incantation stops earning its tokens without ever failing loudly, while a stated fact keeps its meaning across every checkpoint you point it at.
A worked before/after rewrite
| Before (ad-hoc) | After (four slots) |
|---|---|
| "You are an expert data analyst. Carefully extract the invoice details and return JSON. Be accurate!" | Role: output is consumed by a Python json.loads call, no human reads it. Context: invoices are OCR'd PDFs; vendor names are often truncated; amounts may carry a currency symbol. Task: extract vendor, invoice_number, total_cents, issued_date. Format: one JSON object, keys exactly as listed, total_cents an integer with no leading zeros, unknown values as null, no prose before or after. |
The after version says nothing about the model's personality and everything about your data. Note that null and {} are both valid JSON but mean different things (Jsonic), so pick one and write it down. Leading zeros aren't legal in JSON numbers either (MDN), which is why the format slot spells out the integer rule instead of trusting the model to remember the grammar.
Few-shot scaffolds tuned for transfer
Pick examples for the ambiguity they resolve. A few-shot block that demonstrates four cases where a human would hesitate teaches something the next model still needs. A block that shows four easy cases in a house style teaches tone, and tone is the thing every checkpoint gets better at guessing on its own.
Examples that teach the decision boundary, not the vocabulary
Before you paste an example in, ask what would change if you deleted it. If the answer is "the output sounds slightly less like us," delete it. If the answer is "the model would classify a refund-with-partial-shipment as a return instead of a dispute," keep it, because that call isn't derivable from the task description.
The same test applies to output shape. One example showing an empty result as [] rather than null is worth more than five examples of populated results, since an empty array and null are both valid JSON and mean different things (Jsonic). Models guess differently on that, and one example settles it for good.
Edge cases and negatives earn their token cost
Two or three of your shots should be cases you got wrong in production. The missing field. The input that's already in the target format. The record where the correct answer is "insufficient data" and a helpful model will invent a value instead.
Negatives work when you pair them with the correction rather than stating a prohibition. Show the malformed output and the fixed one side by side, and the boundary is concrete. Bare "don't use single quotes" instructions age badly, and single quotes are one of the recurring offenders behind malformed JSON, alongside trailing commas, which account for roughly 40% of errors in one analysis (Flying Fish Space).
How many shots, and when to drop to zero
Start at zero. Add shots only when an eval case fails, and add the smallest example that fixes that case. Most classification and extraction prompts stabilize between three and six; past eight you're usually compensating for a task description you never wrote properly.
Drop to zero whenever a schema does the job. Constrained decoding against a supplied schema gets you essentially 100% syntactically valid JSON (Ashvara), so format examples are dead weight there. Keep the shots for judgment, spend the schema on structure.
The overfitting smell: examples the next model will imitate too literally
Watch for shots whose surface features are accidental. If every example input runs about 40 words, a stronger model may treat length as a signal. If all four examples land on the same label, you've biased the prior. If your examples use placeholder names like Acme Corp, expect those names to show up in real output eventually.
Rerun your few-shot set against a new checkpoint the week it ships and diff the outputs on cases the examples don't cover. That's where imitation leaks. Teams publishing real deployment write-ups tend to keep the example set under version control for exactly this reason: an example you can't diff is an example you can't retire.
Output contracts that outlive the model
Push enforcement down the stack until format stops depending on how a checkpoint happens to feel that day. Asking politely for JSON is the weakest tier available, and it's the one most production code still runs on.
Three tiers of reliability: prose request, JSON mode, constrained decoding
| Tier | How you ask | What comes back |
|---|---|---|
| 1 | "Please return JSON" in the prompt text | An estimated 5-10% of outputs are malformed (Ashvara) |
| 2 | Provider JSON mode switched on | Roughly 95-99% syntactically valid in production observations (Ashvara) |
| 3 | Generation constrained to a supplied schema | Essentially 100% syntactically valid (Ashvara) |
Take tier 3 wherever your provider supports it. Validity then comes from the decoder instead of the weights, so a model swap can't regress it. Keep the prompt-level contract anyway, because constrained decoding guarantees shape and says nothing about whether the values are right. If you'd rather not write the plumbing, the frameworks that wrap schema enforcement in our directory number 128 listings.
What a contract must specify beyond "return JSON"
Name the keys, the type behind each key, and the behavior when the model has nothing to put there.
- Every key spelled exactly as your parser expects, with a type drawn from the six JSON types: four primitives (string, number, boolean, null) and two structured types, object and array (RFC 8259).
- Lowercase literals only. The grammar allows exactly three of them: false, null, true (RFC 8259).
- Unique key names inside each object, which RFC 8259 recommends so that every parser agrees on the same name-value mapping.
- Whether optional keys get omitted or emitted with a null value, and any enum you expect, written out as literal strings.
The failure modes worth encoding: trailing commas, single quotes, unescaped strings
One analysis puts trailing commas at about 40% of all JSON errors, with single quotes, unescaped quotes inside strings, missing commas, and hidden UTF-8 BOM characters covering most of the rest (Flying Fish Space). Those five failures cost you maybe 25 tokens to forbid explicitly in the format slot, and the ban stays correct across every model you'll ever point it at. Pair it with a validate-then-format step on your side rather than trusting the string (QuickTinyData).
Empty object, empty array, null: three different answers
This is where contracts leak between model versions without anyone noticing. An empty object and an empty array are both valid JSON, and both mean something different from null (Jsonic). One checkpoint returns [] for no matches, the next returns null, and your downstream code treats one of them as an error.
Pick the representation, state it in the contract, and validate for it. Your parser also needs to survive a bare value at the top level, since any single JSON value counts as a complete document, including a standalone string or the number 42 (Jsonic).
The brittle tricks that die with every release
Open your prompt library and search for these four patterns. Every hit is a candidate for deletion, because each one was compensating for a model weakness that either got fixed or moved.
Generic 'think step by step' as a bolt-on
Appending "think step by step" to a prompt made sense when models jumped straight to an answer. Current reasoning models already decompose by default, so the phrase adds tokens and sometimes drags a short classification task into three paragraphs of narration you then have to strip.
Keep reasoning instructions only when they're task-specific: "list the conflicting clauses before you pick one" tells the model what to reason about. The durable replacement is the task slot of your role-context-task-format shell, spelling out the intermediate artifact you want. Generic incantation goes in the bin.
Threats, bribes and roleplay pressure
"You will be fired if you get this wrong." "I'll tip you $200." "You are the world's greatest analyst." These leaned on quirks of specific RLHF checkpoints, and quirks don't survive retraining. Worse, they're unfalsifiable: you can't write a test that proves the tip is what fixed your output, so the line stays in the prompt forever, uncontested.
Replace pressure with constraint. A rubric the model grades itself against, or an explicit list of what counts as failure, does the same job and keeps working when the checkpoint changes.
Formatting hacks that fight the tokenizer
Padding prompts with ALL CAPS demands, triple exclamation marks, or long runs of delimiters like ##### is folklore. The delimiter part had a kernel of truth (clear section boundaries help), but the escalation doesn't. Two newlines and an XML-ish tag beat forty hash marks.
Same for "no code fences, no preamble, no explanation, output ONLY JSON" stacked three ways. Say it once in the format slot and then put the guarantee where guarantees actually live: constraining generation to a supplied schema is what takes you to essentially 100% syntactically valid JSON (Ashvara), and no pile of prompt-side prohibitions gets close to that number.
Prompt-level pleading where a parser belongs
The failure modes are boring and structural: trailing commas after the last item, unquoted keys, invalid quote characters, missing commas, mismatched braces (QuickTinyData). Trailing commas alone account for roughly 40% of JSON errors in one error dataset (Flying Fish Space), and they're forbidden by the format itself (MDN). No amount of asking nicely closes that gap.
Delete the pleading. Put a schema and a validator there instead, and let the prompt say what the fields mean.
Eval loops treat prompts as versioned artifacts
Build twenty test cases before you build anything clever. A prompt without an eval set is a prompt you can't upgrade, because you have no way to tell whether the new model made it better or broke the one case that matters to your biggest customer without telling you.
Twenty is not a compromise number. It's enough to catch the failure classes you already know about, small enough that you'll write it in an afternoon, and cheap enough to rerun on every checkpoint without thinking about the bill.
The minimum viable eval: 20 cases, one assertion each
One assertion per case, and make it a boolean: did the output parse, did it contain the required field, did it refuse when it should have refused. Rubrics, judge models and similarity scores can come later, once the boolean is green. Cases with three assertions turn into cases you can't debug, and a red run tells you nothing about which of the three broke.
Pick your twenty from real traffic, weighted toward the ugly end. Five happy paths, five inputs that are ambiguous, five that are adversarial or empty, five that broke in production at some point. Store them next to the prompt in the same repo, in the same commit. If the prompt changes and the cases don't, that's a review comment.
Validate first, then format: borrowing the JSON debugging workflow
The JSON world settled this argument years ago. QuickTinyData's troubleshooting guide recommends validating first and formatting second, because pretty-printing a broken document hides the exact structural error you're hunting: trailing commas, unquoted keys, wrong quote characters, missing commas, mismatched braces (QuickTinyData).
Run your eval the same way. Assert validity before you assert quality. A model output that fails json.loads shouldn't advance to the semantic check, and it shouldn't get partial credit either. Your eval suite needs two columns, parse rate and pass rate, and the second one only counts rows where the first one succeeded.
Knowing the error distribution tells you what to assert. One analysis puts trailing commas at roughly 40% of JSON errors, with single quotes, unescaped quotes inside strings, missing commas, and hidden UTF-8 BOM characters making up the bulk of the rest (Flying Fish Space). That BOM one is worth a dedicated assertion, since it's invisible in every editor you'll use to inspect the output.
Regression runs on release day
A new checkpoint ships. You run the twenty, you get a diff, you decide. That's the whole procedure, and it takes about four minutes if you built the suite right.
- Pin the old model and rerun the suite to confirm your baseline still reproduces. If it doesn't, the problem is in your test setup rather than the release.
- Run the suite against the new checkpoint and record parse rate and pass rate separately.
- Read every case that flipped, in both directions. A case that started passing can be luck, and it's worth as much attention as a regression.
- Ship, roll back, or patch the prompt. Then commit the new baseline numbers next to the prompt file.
This matters most in agent stacks where a single bad parse cascades, because the failure surfaces three tool calls downstream as something that looks nothing like a formatting bug.
Pinning versions, and what to do when you can't
Pin to dated model IDs everywhere you can, and treat an alias like a floating dependency you've chosen not to lock. Some providers won't give you a pin, or will deprecate the one you're on with a short window. When that happens, your eval suite is the thing standing between a silent behavior change and a support ticket you can't reproduce.
Run the suite on a schedule against the unpinned endpoint. Weekly is fine. You'll find the drift before your users narrate it back to you in a bug report.
Porting one prompt across Claude, GPT and Gemini
Roughly 80% of a well-built prompt ports unchanged. The rest is an adapter layer you write once per provider and then mostly forget about. If you're rewriting the whole thing for each vendor, your prompt was carrying provider-specific behavior it never needed to carry.
What stays identical: the shell, the examples, the contract
The four-slot shell moves across vendors with zero edits. Role, context, task, format describe the job, and the job doesn't change when you swap checkpoints. Same for your few-shot block: examples that resolve genuine ambiguity in your domain teach every model the same thing, because the ambiguity lives in your data, not in the decoder.
Your output contract stays identical too, and it has to. Whatever a provider returns has to satisfy the same parser: double-quoted property names, no trailing commas, no NaN or Infinity, and only four legal whitespace characters (space, tab, line feed, carriage return) (MDN). Write the schema once. Validate all three outputs with the same validator, and diff the failures.
Keep your eval set vendor-neutral as well. Twenty cases that pass on Claude and fail on Gemini tell you something useful. Twenty cases written against Claude's quirks tell you nothing.
What you re-tune per provider: system-message weight, delimiters, enforcement API
Three things get an adapter. How much of your instruction goes in the system message versus the user turn, since providers weight those differently. What you use to fence blocks, XML-ish tags or markdown headers. And which enforcement API you call.
That last one is the fiddly part. JSON mode of any flavor buys you syntax and stops there: production observations put it around 95-99% syntactically valid, while constraining generation to a supplied schema is essentially 100% (Ashvara). Neither tier says anything about whether the keys are the ones you asked for, so the schema check stays in your code no matter which vendor you're on. If you're picking targets, it's worth a pass to compare the models themselves before you commit, and the multi-provider platforms in our directory will absorb some of this adapter work for you.
A portability checklist before you ship
- Strip every sentence that names a model, a version, or a known behavior of one. Those are the lines that break first.
- Run the same eval set against all three providers and record pass rates per case, not just an average.
- Confirm the schema validator runs on every response regardless of whether the provider claims constrained decoding.
- Re-read each provider's enforcement docs when you wire the adapter, and keep whatever wording or flag it requires inside the adapter rather than in the shared prompt.
- Log which adapter fired. When a checkpoint ships and quality moves, you want to know whether the prompt or the adapter changed underneath you.
If a prompt fails this checklist on one vendor only, the bug is almost always in the adapter, not the shell.
Frequently Asked Questions
Do I need to rewrite my prompts every time a new model ships?
No, and if you do, your prompt was probably carrying model-specific hacks rather than instructions. The parts that survive updates are the ones tied to something outside the model: a task description, input data, and an output contract like a JSON schema. Constrained decoding against a supplied schema produces essentially 100% syntactically valid JSON regardless of which model is behind it, because the constraint lives in the decoder. What you should rewrite on a model change is nothing. What you should re-run is your eval set.
Does 'think step by step' still work on current models?
It's mostly dead weight now. That phrase was a workaround for models that would jump straight to an answer, and current models already decompose multi-step work without being told. Worse, appending it to a prompt that demands strict JSON output invites the model to emit reasoning prose around the object, which is exactly the class of failure that pushes naive prompts to an estimated 5–10% malformed JSON rate. If you want reasoning, give it a named field in your schema and let the parser keep it separate from the payload.
Is JSON mode enough, or do I need a schema?
Use the schema. JSON mode gets you to roughly 95–99% syntactically valid output, which sounds fine until you're running ten thousand calls a day and eating a hundred failures. Schema-constrained decoding takes syntax validity to essentially 100%, and it also pins down your key names, which matters because RFC 8259 treats a JSON object as an unordered collection of name–value pairs and only recommends unique names. Syntax validity is not semantic correctness, so keep validating the parsed object against your own rules either way.
How many few-shot examples should a durable prompt include?
Two to four, and pick them for edge coverage rather than for volume. Examples that show the same happy path repeatedly teach a model nothing it doesn't already do; examples that pin down the awkward cases are the ones that transfer across models. For structured output, spend at least one example on the distinction between an empty container and a missing value, since [an empty object {} and an empty array [] are both valid JSON but semantically different from null](https://jsonic.io/guides/json-examples). If your examples contain formatting that a strict parser would reject, you're teaching the failure: trailing commas alone account for about 40% of JSON errors in one analysis.
How big does a prompt eval set need to be before it's useful?
Thirty to fifty labeled cases will catch most regressions, and twenty is better than the zero most teams run with. Size matters less than composition: weight the set toward the failure modes you've actually seen in production, like unquoted keys, single quotes, unescaped quotes inside strings, missing commas, and hidden UTF-8 BOM characters, all of which show up in documented JSON error breakdowns. Run validation before formatting so you catch structural breakage rather than masking it, which is the workflow QuickTinyData recommends. Version the set alongside the prompt and re-run it the day a new model lands.
Can the same prompt really run unchanged on Claude, GPT and Gemini?
The instruction body ports cleanly. The output-enforcement layer does not, because JSON mode and schema-constrained decoding are configured differently per provider, so plan on one prompt and three thin adapters. Keeping the contract in a format every provider already agrees on helps: RFC 8259 is language-independent and defines four primitive types plus objects and arrays, with lowercase true, false, and null as the only literal names. If you're shopping for tooling to manage prompts across providers, our directory lists 212 prompt engineering tools and 324 entries under AI models.







