Skip to content

15 min read

Prompts are behaviour changes, so they need authorship and approval

On this page

The system prompt is the standing block of instructions a service sends with every request, ahead of whatever the user typed: the assistant's role, the rules it must follow, and the tools it may call — the named functions it can ask the application to run on its behalf. The user never sees it, the service assembles it fresh for every call, and on a document-drafting endpoint it might run to 620 tokens of role, house style and clause-handling rules. Whatever text the service reads governs every request until somebody edits it, which makes where that text lives, who may change it and what survives the change an operational question rather than a documentation one.

Prompt versioning is the practice of treating that block as an identified artefact rather than as the current contents of a field, so that every version has an author, a date, a diff against the version before it, a record of who approved it, and a way to put it back. It exists because a prompt edited in place holds one value only. You meet it in one of two arrangements — prompts kept as files in the service repository and reviewed like code, or a prompt platform that versions every save — and which properties each of those gives you is most of what this topic settles. This chapter is about the first arrangement and about the four properties that are the reason to want either.

A wording change is a behaviour change with a bigger blast radius than it looks

Changing forty lines of that block is a smaller edit than most pull requests, and it arrives looking like copywriting, which is why it tends to be handled like copywriting. The effects, though, land in the same places a code change lands. A hedging clause moves refusal rate — the share of requests where the assistant declines the work. A reordered list of tools moves which tool the model reaches for first, and on an agent with six tools that shows up as a shift in the ratio between search_orders and escalate_to_human long before anyone notices a quality problem. An instruction to explain reasoning before answering moves output length, and output length is money.

That last one is worth pricing out, because it is the least intuitive. On a service handling 400,000 requests a month with a mean output of 180 tokens, output billed at $15 per million tokens costs $1,080 a month. Adding "briefly explain your reasoning before giving the answer" to the system prompt raised the mean to 540 tokens in one team's case, which is $3,240 a month for the same traffic — a $2,160 monthly change authored in a text box by someone who was, quite reasonably, trying to improve answer quality. Nothing about the edit signalled that it was a spending decision.

Walking that multiplication is worth the half-minute, because it is the part that makes the figure believable and the part nobody does in the meeting. Four hundred thousand requests at a mean of a hundred and eighty output tokens is seventy-two million tokens a month, and seventy-two million at fifteen dollars per million is the thousand and eighty. The instruction takes the mean to three times what it was, so the monthly token total goes to three times what it was, and so does the bill — which is where the three thousand two hundred and forty comes from, and the gap between the two is the two thousand one hundred and sixty. Two things in that are worth keeping. Cost is linear in the mean output length, so an instruction that changes the mean changes the bill by the same factor with nothing else moving. And it is the mean that does the work, not the longest response anybody has seen, which is why a change that looks harmless across a handful of examples is a spending decision across four hundred thousand of them.

The mean moving at all deserves one more step, because that step generalises. Output tokens are produced one at a time until the model emits a stop, and an instruction to explain reasoning before answering adds a stretch of text that has to be produced before the answer begins. Nothing anywhere removes tokens to compensate. So the new mean is the old mean plus whatever the explanation costs, on every request, including the ones where the question was trivial and the explanation is a sentence observing that the question was trivial. Every instruction of the form before answering, do X has that shape, which is something a reviewer can say out loud before anybody has measured anything.

The tool-selection effect is the one that catches teams by surprise most often, because the wording that causes it is not about tools. On an agent with a handful of functions available, the order in which capabilities are described and the adjectives attached to them shift the distribution of first calls measurably: one team moved a paragraph about escalation from the bottom of the prompt to just after the tool list and watched the share of conversations ending in escalate_to_human go from 6% to 11% over a day, with no change to the escalation instruction itself. The model is reading a single sequence of tokens, and proximity to the instruction that follows is part of what it reads.

That sentence is the mechanism, and it is easy to nod past, so it is worth being concrete about what it denies. A prompt has no sections as far as the runtime is concerned. There is no structure in which the escalation paragraph is a sibling of the tool list; there is one sequence, and every token in it is available to the model as it produces the next one. What a paragraph's position changes is not whether the model can see it but how strongly it bears on what follows, and that relationship was learned from text in which the nearby instructions are usually the ones governing the nearby decision. So moving a paragraph is a behaviour change containing no changed words, and a reviewer reading only the added and removed lines of a diff can miss the entire content of a pull request.

Topic 4.9, on how user input is interpolated into a template without breaking it, covers the safety of a single render. What is at stake here is the lifetime of the template itself: who may change it, what the change looked like, who agreed, and whether any of that is still knowable in six weeks.

What a repository gives a prompt that a text box does not

Putting the prompt in the application's version control system is a small mechanical step with four consequences, and it is worth naming them separately because teams often adopt the step while only getting two of them.

The first is attribution — the authorship of each line, recoverable long after everyone has forgotten it. Every line of the prompt has a commit behind it, and every commit has an author, a timestamp and a message. When refusal rate moves, the question "what changed in this file and when" has a mechanical answer:

bash
$ git log --oneline -L 18,26:prompts/drafting/system.md
8f3c1a2 Tighten hedging language for legal review (Priya Raman, 2 days ago)
e10b447 Add certainty guidance to reduce hallucinated clauses (Tom Beck, 6 weeks ago)
2c94ef0 Initial extraction of drafting prompt from app.py (Priya Raman, 4 months ago)

Three commits, three authors, three dates, scoped to the nine lines that matter. On the admin-page version of this system, the equivalent query has no answer at all.

The second is a diff. The prompt is a text file, so the same tooling that shows you a one-line change in a function shows you a one-line change in an instruction. The middle commit above, the one that added certainty guidance, looks like this:

diff
--- a/prompts/drafting/system.md
+++ b/prompts/drafting/system.md
@@ -18,7 +18,8 @@ When drafting a summary:
 - Quote clause numbers exactly as they appear in the source document.
 - If a clause is ambiguous, summarise both readings and say which is more likely.
-- If the source document does not cover a question, say so and continue.
+- If you are not fully certain the answer is correct, do not answer. Tell the
+  user you cannot help with this and suggest they contact a human specialist.

That is an eight-point move in refusal rate, visible as three lines. A reviewer looking at it does not need to be an expert in language models to notice that "if a clause is ambiguous, summarise both readings" and "if you are not fully certain, do not answer" pull in opposite directions, and that the second will fire far more often than the author probably intends, because a model asked whether it is "fully certain" will frequently decide that it is not.

Two separate things are happening in those three lines, and pulling them apart is most of what a prompt review is for. The first is a contradiction. The file now holds a rule telling the assistant to summarise both readings of an ambiguous clause and a rule instructing it to decline whenever it is not fully certain, and an ambiguous clause is precisely the case both rules are about. Nothing resolves that. The model is not consulting a precedence table; it is producing whichever continuation the prompt as a whole makes likely, and a refusal is the more available continuation because declining is the more cautious reading of a conflict. The second thing is the quantifier. Fully certain has no threshold behind it — the model is not computing a confidence and comparing it to a number — so what the phrase actually does is invite the cautious reading of every case, and very few cases survive it. Seeing the contradiction takes no knowledge of language models at all. Seeing what the quantifier does is the part that comes with practice, and it is the reason the routing entry below sends this directory to somebody who has read a few of these.

The third is an approval record. Branch protection turns the diff into something that cannot reach the main branch without a named human signing off, and a CODEOWNERS entry decides which humans:

text
# .github/CODEOWNERS
/prompts/                 @acme/ai-platform
/prompts/drafting/        @acme/ai-platform @acme/legal-ops

The legal-ops entry is doing real work here. The people who understand whether a drafting assistant should decline when uncertain are not the same people who understand the retry logic around it, and routing the prompt directory to both means the review is done by someone who can evaluate the instruction rather than only its syntax.

What a reviewer of a prompt diff is actually looking for takes a little defining, since it is not the same list as for code. The recurring failures are contradictions between an added instruction and an existing one, as in the certainty clause above; negations the model handles unevenly, where "never mention pricing unless the customer asks" quietly becomes a rule about mentioning pricing; examples further down the file that now demonstrate the opposite of the new rule; and instructions that describe an outcome without describing the condition that triggers it. A reviewer who checks those four things on a three-line diff has done a useful review in about two minutes, which is the point of keeping diffs small enough that two minutes is enough.

The commit message carries weight here too, more than it usually does. A prompt change that says "improve tone" gives the next investigator nothing, while one that says which behaviour is meant to move and how you would notice — "expect refusal rate up 1–2 points on ambiguous clauses; watch drafting.refusal_rate" — turns the history into a set of predictions that can be checked against what actually happened. When a recurrence arrives in September and someone reads back through six months of commits, the ones with a stated expectation are the only ones that answer questions.

The fourth is durability. Commits are immutable and the history outlives everyone involved: when the same symptom recurs and the author has left the company, git log still answers. This is the property that separates version control from an audit log bolted onto an admin page, because an audit log records that a change happened while the repository records what the artefact was before and after, which is the thing you need in order to put it back.

text
prompts/
  drafting/
    system.md          # 620 tokens, owned by ai-platform + legal-ops
    few_shots.yaml
  classification/
    system.md
  CHANGELOG.md

The belief worth naming under that fourth property is that an audit trail and a version history are the same thing bought in different shops, and the difference between them is exactly what a recovery needs. An audit event is a record of an action: an actor, a timestamp, an object, sometimes a verb. Restoring a prompt requires none of those and requires one thing absent from the list, which is the bytes that were there before. A system emitting an event on every save while storing only the current value can therefore say precisely when a regression was introduced and by whom, and cannot say what to put back. That is a complete answer to when sitting beside no answer at all to what, and a team in that position has bought the paperwork of governance without the part that ends an incident.

Why prompts leak out of the code path

That arrangement is not unusual, and the reason it is common is that every hop out of the repository is chosen for a good local reason. The prompt reads like product copy, and the person with the strongest opinion about the wording is often a product manager, a support lead or a domain specialist who does not have a checkout of the service and would need three days and a laptop setup to open a pull request. Moving the text to a database row served through an admin page removes that friction entirely, and it is the kind of change that gets made in an afternoon and celebrated as an unblocking.

What each hop silently trades away is the record. A spreadsheet cell has no author. An environment variable set through a cloud console has a value and nothing else. A vendor's playground has whatever text is in the box right now. In every case the system keeps working, the iteration loop gets faster, and the cost of the trade only appears the first time behaviour regresses and somebody asks what changed.

Infrastructure teams went through the same argument about feature flags roughly a decade earlier, and settled it the same way. A flag flip changes production behaviour without a deploy, which is exactly what makes it useful and exactly what makes an unattributed flip dangerous, so flag platforms grew change history, required approvals on production environments, and emitted audit events. Prompts are the same class of change — a small edit that alters what the system does for real users — arriving by default with none of that tooling, because they look like text.

Attribution is not judgement

Version control records changes; it has no opinion about whether a change is good. The certainty clause would have been committed, reviewed and merged just as happily as anything else, and the repository would have faithfully preserved an eight-point refusal regression with an author's name attached. Judging the change is the job of an evaluation suite run on the pull request, which topic 3.16, on gating a merge behind an eval score, works through, and the review discipline here assumes that machinery exists alongside it. Attribution tells you where to look; it does not tell you what you are looking at.

Nor does a commit id make a production output reproducible. Knowing that request req_8812 ran against prompt revision 8f3c1a2 narrows the search, and it will still leave you replaying a complaint and getting a different, entirely reasonable answer, because the prompt is one of several inputs that moved. A later chapter in this topic, on the request-level record that pins the model version, the decoding parameters and the retrieved context, works through the rest of them.

Review also does not automatically produce a readable diff. The three-line change above was legible because the author touched three lines; a pull request that reflows the whole file shows sixty modified lines and hides one rule change inside them, at which point the approval record is a signature on something nobody actually read. Separating a reformat from a rule change is its own chapter later in this topic.

The four properties are worth holding as four rather than as one habit, because a team can have any subset of them. A prompt committed to the repository and merged without review has attribution and durability and no approval record. A prompt reviewed as a sixty-line reflow has an approval record and no readable diff. A prompt exported nightly from a vendor console into a Git repository has a durable trail with nobody's name on any of it. Each of those is a defensible place to be for a week and none of them is what "we keep our prompts in Git" usually means when it is said out loud.

The most immediate gap is elsewhere, and it is not about the record at all. Putting the prompt in prompts/drafting/system.md and loading it at process start means the prompt now ships when the service ships, and it changes when the service changes. On the Thursday in question, the team knew by 16:00 exactly which sentence to remove, and removing it still meant a branch, a review, a build and a deploy while the refusal rate sat at 9.4%. The repository fixed the forensics and left the recovery time untouched, which is the subject of the next chapter.

Prompt versioning, storage and review — read the current version at https://daviddominguez.dev/learn/en/prompt-versioning-storage-and-review/prompts-are-behaviour-changes-so-they-need-authorship-and-approval

Back to top