A prompt change is a deploy
Editing a prompt changes the behaviour of your product for every user immediately. That is the definition of a deploy, and it deserves the same machinery: a diff, a review, a version number, a test run, and a way back.
What happens instead, almost everywhere, is that prompts live as string literals scattered through the codebase, or in a spreadsheet, or in a vendor’s web console where there is no history at all. Then quality drops, nobody can say what changed, and the honest answer to “what was the prompt on Tuesday?” is that it is gone.
Files, not literals
Put each prompt in its own file, with a version in the name or in front matter:
prompts/
support_reply.v4.md
classify_ticket.v2.md
extract_invoice.v7.mdA markdown file diffs cleanly, reviews properly in a pull request, and can carry a short header saying what changed and why. Load it at startup, cache it in memory, and pin the version in your configuration so a rollback is a one-line change.
The pin matters. support_reply.latest is how you get a change you did not intend on a Friday evening. An explicit v4 means the version in production is a fact recorded in the repository.
Templating, with the escaping done properly
Use a real template engine (Jinja, Handlebars, whatever your stack has) rather than f-strings and concatenation.
{% raw %}You answer strictly from the passages below.
<passages>
{% for p in passages %}[{{ p.id }}] {{ p.text }}
{% endfor %}</passages>
<question>{{ question }}</question>{% endraw %}Two real dangers this addresses.
Injection at the template layer. If a user’s message itself contains </passages> or {{ something }}, naive substitution lets them close your structure or reach your template variables. Templating with autoescaping off is fine for prompts, but you must still strip or escape your own delimiter strings from user input. It is a two-line function and it removes the cheapest attack there is.
Silent empty variables. An f-string that receives None produces the word None in the middle of your prompt. A template engine configured to be strict raises instead. A prompt that silently renders Customer name: None will produce answers addressed to a customer called None, and it will do it in production for a week before anyone notices.
Log the rendered prompt, not the template
When an answer is wrong, the template tells you almost nothing. What you need is the exact bytes that were sent.
Store, per call: the prompt version, the model string returned by the API, the rendered prompt (or a hash of it plus the variables), the response id, and token usage. That record is what turns “the assistant said something strange yesterday” from a shrug into a five-minute investigation.
One caution: rendered prompts contain user data, so they inherit your retention and privacy rules. Truncate, redact identifiers, and set an expiry. A prompt log that keeps customer messages forever is a data-protection problem wearing a debugging costume.
Changing a prompt safely
The procedure is short and almost nobody follows it:
- Copy
v4tov5and editv5. Never edit a version that is live. - Run both versions over the same fixed set of cases.
- Compare on the metrics you defined, plus cost and latency, plus the specific case you were trying to fix.
- Look at the cases that changed from correct to incorrect. There are always some.
- Ship behind a flag, at a small percentage, with the old version one config change away.
Step four is the one that pays. A prompt edit that fixes eight failures and breaks five is a change most teams would ship without knowing, because they only looked at the eight.
Two habits that compound
Never edit a prompt in the vendor console. A playground is for exploring. The moment a prompt is used by anything, its home is the repository, and the console version is a copy that will silently diverge.
Write the reason in the commit message. support_reply v4: refuse to quote prices not on the site, after ticket #8812. Six months later, the person wondering why that odd line exists is you, and the alternative to a commit message is deleting it and finding out.
None of this is sophisticated. It is the ordinary discipline of treating a prompt as source code, which it is: it is the part of your program that determines what your product says.
Every lesson here is open. Your progress is recorded once you pass the Python, from Zero, for AI exam.
Check your understanding
Answers degrade after a prompt edit, and the team cannot reproduce the old behaviour. Which practice would have prevented this most directly?
Pick the one you would defend. Your answer stays private.
Stuck on something?Ask Sayali, your AI tutor
