← Tidelines/Best practices

Put it back the way it was on Tuesday

Nine checks for the afternoon somebody asks you to undo the week. A rollback feels like the safe option because it only removes things, and that is the exact reason it goes wrong: a revert is an edit, and it has to survive every line that landed after the one you are putting back.

by TypeGlish team12 min read#best-practices
A revert is an edit.

TL;DR Name the rollback target as an artifact hash, never as a day; revert the directory the prompt composes from, because a single-file revert leaves an imported house-rules file at today's version and lands you on a prompt that never existed; then rebuild and require the recorded hash, run check because last week's wording against this week's rules can be a blocking contradiction, and read the artifact diff line by line to see what you are giving back. Two things ride along invisibly: the $CONFIG model row, which no artifact diff can see, and the host's input bag, which the file never held.

It is half past three on a Friday. Containment on the billing queue has been sliding since Wednesday, a team lead has three transcripts that all look wrong in the same way, and the sentence everyone has landed on is that it was fine on Tuesday. The ask is reasonable and the ask is urgent, and both of those things push you toward the same wrong move, which is to open the file, find the line you think it is, and put the old wording back.

Nine checks, run against a small energy-billing prompt. It is not a broken prompt; it is a prompt three people edited in one week. That is the normal case and it is the whole difficulty.

billing.tg - the prompt as it runs now, Friday✓ A (95/100)
<$CONFIG>
  $IMPORT file "house.tg" as house
  $CONFIG model claude-haiku-4-5
    - via:: anthropic
  $REQUIRE variable balance: number
</$CONFIG>

$SERVICE anthropic
  - base:: https://api.anthropic.com

# Role
@@ role: one sentence names the speaker, so every later "you" is the agent
You are Ora, the billing assistant for Fernhill Energy.

@<house.Constraints>

# Constraints
@@ plan_cap: ops doubled the ceiling after the winter bills landed
- You MUST offer a payment plan of at most 24 months.
@@ plan_floor: collections will not take a plan shorter than this
- You MUST offer a payment plan of at least 18 months.
@@ meter: never quote a figure the meter has not confirmed
- You MUST NOT quote a final bill amount.
@@ arrears_route: the arrears team owns a large balance
$IF @{balance} is at least 300:
  - You MUST transfer a customer to an arrears adviser.
$ELSE:
  - You MUST offer a monthly instalment.

$TEST behind
  - input:: I am two months behind on my bill.
  - expect::
    - offers a payment plan
Two files, not one: house.tg carries the brevity rule and the AI-disclosure rule every Fernhill prompt shares. Hold on to that, because it is check 4 and it is the one that catches people.
git log - one week, three hands
$ git log --format='%h  %ad  %s' --date=short
72189ff  2026-09-16  billing: add the 18-month floor, the house complaints rule, and move to haiku
0a4733e  2026-09-16  billing: raise the payment-plan ceiling to 24 months
ff83d07  2026-09-16  billing: the desk prompt as it ran on Tuesday
Three commits, and only the middle one is about the thing anybody suspects. The top one bundles a policy floor, a regulatory rule in the shared file, and a model swap, which is a perfectly normal Thursday and a genuinely awkward thing to roll back.

§1Before you touch the file

Check 1: name the target as a hash, not as a day. "Tuesday" is not an identifier. It is a shared memory, and in a week nobody will agree on it. What you want is the artifact hash your deploy recorded for the build you are going back to, and the build manifest is the shape that record comes in.

the build manifest - one row per build
{
 "source": "billing.tg",
 "sourceSha256": "2b3e35b010cd8ddcfbd5730279504ce4d33e044f726eb09a0e9a1b15504e5b77",
 "artifact": ".typeglish/dist/billing.txt",
 "artifactSha256": "cd9c4b99bc51753a0c9c015c5d985880d68d71efa82045d9fe43e10f8d3ce37b",
 "vars": null,
 "typeglish": "0.9.0",
 "checkMode": "full",
 "report": { "ok": true, "counts": { "error": 0, "warn": 0, "info": 0 }, "strict": false },
 "builtAt": "2026-09-16T09:29:02.017Z"
}
Three fields do work here that a commit sha cannot. artifactSha256 identifies the bytes the model actually read, so it survives a rename, a comment, and a reformat. vars records the input bag the build was pinned to, or null for a template build. And typeglish records the compiler, which is what makes a rebuilt artifact comparable with the one it is replacing. Note what this row is and is not: the manifest holds the latest build per artifact path, so this is cd9c4b99bc51, what is live. Tuesday's hash has to come from wherever the deploy logged it, and in this repo that is 6d35707cb150. If nobody logged it, the fallback is to rebuild Tuesday's commit and take what comes out, while saying out loud that you are trusting the typeglish field to still read 0.9.0.

Check 2: reproduce the complaint as a $TEST before you change anything. This is the check most likely to be skipped and the only one that outlives the afternoon. You have three transcripts. Turn the clearest one into a case with a deterministic assert, and add it to the file you are about to roll back and the file you are rolling back to. If the rollback fixes the behaviour, the test is now a regression test that will fail the day somebody re-lands the change without thinking. If the rollback does not fix it, you have learned that in twenty seconds rather than after a deploy, which is the more valuable outcome and the one nobody plans for. Your agent said the wrong thing, now what is the longer version of this hour; the short version is that the transcript is evidence and a transcript nobody wrote down is a rumour.

§2The revert is an edit

Check 3: run check on the revert, because last week's wording has to live with this week's rules. Here is the move everybody makes under time pressure. The plan ceiling looks like the culprit, so put it back to what it was on Tuesday and leave everything else alone. In isolation that line is unimpeachable: it is the line that was running when the numbers were fine.

the two rules, as they stand on Friday✓ clean
# Role
@@ role: one sentence names the speaker, so every later "you" is the agent
You are Ora, the billing assistant for Fernhill Energy.

# Constraints
@@ plan_cap: ops doubled the ceiling after the winter bills landed
- You MUST offer a payment plan of at most 24 months.
@@ plan_floor: collections will not take a plan shorter than this
- You MUST offer a payment plan of at least 18 months.
A ceiling of 24 and a floor of 18 leave a six-month window, so the prover has nothing to say. Both rules are good policy and neither was written by the person who wrote the other.
the same two rules with the ceiling reverted✗ logic/numeric
# Role
@@ role: one sentence names the speaker, so every later "you" is the agent
You are Ora, the billing assistant for Fernhill Energy.

# Constraints
@@ plan_cap: twelve months is the ceiling collections signed off
- You MUST offer a payment plan of at most 12 months.
@@ plan_floor: collections will not take a plan shorter than this
- You MUST offer a payment plan of at least 18 months.
Nothing was added. One number went back to what it was three days ago, and the window closed.
tg check + score + build - the partial revert✗ F (79/100)
$ npx typeglish check .
billing.tg:19:1  error  logic/time  Conflicts with line 14. Time conflict — "at most 12 months" and "at least 18 months" cannot both bound "you offer a payment · plan": the windows never intersect.
billing.tg:19:1  error  logic/numeric  Conflicts with line 14. Numeric conflict — "at most 12 months" and "at least 18 months" can't both hold.
billing.tg:21:1  error  logic/time  Conflicts with line 13. Time conflict — "at most 12 months" and "at least 18 months" cannot both bound "you offer a payment · plan": the windows never intersect.
billing.tg:21:1  error  logic/numeric  Conflicts with line 13. Numeric conflict — "at most 12 months" and "at least 18 months" can't both hold.

 2 files — 4 error, 0 warning, 0 info
program: 1 root — billing.tg (2 files)
# exit 1

$ npx typeglish score billing.tg
billing.tg — F (79/100)  proven errors — grade capped at F  tiers: base+z3

$ npx typeglish build billing.tg
typeglish build: billing.tg refused — nothing written
Four findings on two planes for a one-number edit: the numeric prover reads it as an empty interval and the time layer reads the same bound as a duration whose windows never intersect. The build writes nothing, which is the behaviour you want from a rollback at half past three on a Friday. Reverting to a wording that was correct in its own week is not a safe operation, and this is the whole reason to put the revert through the compiler instead of straight into the console.

Check 4: revert the directory, not the file. So do it properly: take the whole prompt file back to Tuesday's commit rather than hand-picking a line. This passes, which is the problem.

git checkout - the single-file revert✗ not Tuesday
$ git checkout ff83d07 -- billing.tg

$ npx typeglish check .
 2 files — 0 error, 0 warning, 0 info
program: 1 root — billing.tg (2 files)

$ npx typeglish build billing.tg
 built .typeglish/dist/billing.txt ← billing.tg (7744c9d03bb6, full)
7744c9d03bb6. The target was 6d35707cb150 and the thing you are replacing was cd9c4b99bc51, so this is a third prompt: not the one that shipped on Tuesday, not the one running now, and one that has never been in production or in front of a reviewer. house.tg is still at Thursday, because a revert of one file is a revert of one file, and the shared file is part of the deployed prompt.

Check 5: rebuild and require the recorded hash. This is the null test, and it is four seconds. Revert everything the prompt composes from, rebuild, and demand the twelve characters the manifest wrote down for the day you are going back to. Equality is the whole claim of a rollback, and until this command passes it is an assertion.

git checkout - the directory revert, and the null test✓ 6d35707cb150
$ git checkout ff83d07 -- .

$ npx typeglish check .
 2 files — 0 error, 0 warning, 0 info
program: 1 root — billing.tg (2 files)

$ npx typeglish build billing.tg --bundle
 built .typeglish/dist/billing.txt ← billing.tg (6d35707cb150, full)
Byte for byte the prompt that ran on Tuesday. That is a rollback. The two commands above it, which produced a clean check and a successful build on a prompt nobody has ever run, are what a rollback looks like when nobody asks for the hash. The same $IMPORT mechanics are the point of the paragraph that lives in three prompts, and this is their bill coming due.

§3Read what you are giving back

Check 6: diff the live artifact against the one you are about to deploy, and read every line. A rollback is a change with a direction, and the direction does not make it small. Three days of decisions come out in one command, and the useful discipline is to say out loud, for each line, who decided it and whether they know it is going.

diff - what the rollback gives back✗ 3 rules
$ diff live/billing.txt .typeglish/dist/billing.txt
5d4
< - WHEN a customer raises a complaint THEN you MUST transfer them to a complaints handler.
9,10c8
< - You MUST offer a payment plan of at most 24 months.
< - You MUST offer a payment plan of at least 18 months.
---
> - You MUST offer a payment plan of at most 12 months.
Two of those three lines are the thing you meant to undo. The first is not. A complaints rule with an eight-week regulatory clock behind it went into the shared file on Thursday, and rolling the billing prompt back to Tuesday removes it from the deployed prompt, on a Friday afternoon, as a side effect of a containment decision. Nobody chose that. It is in the diff because the diff is the artifact rather than the file, and it is the reason this check is not optional.

Check 7: read the bundle, because the model rides along and the artifact cannot see it. Thursday's commit moved the prompt to a smaller model in the same breath as the rules. Rolling the file back rolls that decision back too, and there is no trace of it in the prompt the model reads.

the --bundle sidecar - the only place the model appears
$ cat .typeglish/dist/billing.agent.json
{
 "model": {
  "id": "claude-opus-4-8",
  "service": { "name": "anthropic", "base": "https://api.anthropic.com", "headers": [] }
 }
}
Build the identical prompt against claude-haiku-4-5 and against claude-opus-4-8 and both come out at 6d35707cb150. A $CONFIG row is control plane, stripped before a byte is emitted, so the artifact hash you have been leaning on for the last four checks is blind to it by design. That is correct behaviour and it is a hole in your rollback: an artifact-only review cannot tell a prompt rollback from a prompt rollback plus a model change. If the swap is the thing you actually wanted to keep, somebody wants the model bill smaller is the list for unpicking the two.

Check 8: the host's input bag does not roll back, because it was never in the file. Half the deployed prompt arrives at request time, and a git history has no opinion about it. The same reverted source, built against two bags, is two different prompts.

tg build --vars - one source, two host bags
$ npx typeglish build billing.tg --vars '{"balance": 250}' --out-dir v250
 built v250/billing.txt ← billing.tg (fb15eae9d05e, full)

$ npx typeglish build billing.tg --vars '{"balance": 400}' --out-dir v400
 built v400/billing.txt ← billing.tg (dfe052b5cadd, full)

$ diff v250/billing.txt v400/billing.txt
10c10
< - You MUST offer a monthly instalment.
---
> - You MUST transfer a customer to an arrears adviser.
Two hashes from one reverted file. If the incident was a routing problem rather than a wording problem, rolling the prompt back changes nothing about it, because the threshold that chose the arm is in the prompt and the value that met it came from the host. Before you roll back, check whether whatever feeds @{balance} also changed this week. Your prompt cannot tell a missing input from a real one is the same seam, seen from the other side.

§4Landing it

Check 9: land the rollback as a commit forward, with the tests from check 2 still in the file. Not a force push, not a reset, not a deploy from a detached head. The prompt is source and its history is the audit trail: somebody will ask in October why the complaints rule disappeared for four days, and the answer needs to be a commit message rather than a recollection. Put the hash in it, both of them, the one you left and the one you landed on.

tg test --dry - the coverage denominator moves with the rules
$ npx typeglish test billing.tg --dry          # before the rollback
 billing.tg  coverage: 5/6 rules exercised

$ npx typeglish test billing.tg --dry          # after the rollback
 billing.tg  coverage: 4/5 rules exercised
The suite did not change; both halves of the fraction did. Coverage counts the rules the cases reach over the rules that exist, so a rollback moves the denominator under a fixed suite and the two readings are not comparable across the versions. Do not gate a rollback on it. What the suite should hold after this afternoon is one more case than it held before: the transcript from check 2, pinning the behaviour the rollback was for.

And the part a checklist cannot do for you. Every command above answers the question "did I land where I meant to", and none of them answers "should I have". A rollback trades a known-bad week for a known-good one and pays for it in whatever was genuinely better about the week you are deleting. In this file that is a ceiling ops raised for a reason, a floor collections asked for, and a regulatory rule with a clock on it, all of which will have to be re-landed on Monday by somebody reading a diff. The narrower fix, reverting one rule with its guard intact, is usually the right one, and check 3 exists because the narrow fix is the one the compiler has the most to say about.

§5Common questions

How do I roll back an AI agent system prompt?
Treat it as an edit, not an undo. Name the target as the artifact hash the build manifest recorded for the day you want back, rather than as a day. Revert the directory the prompt composes from rather than the one file, because an imported house-rules file is part of the deployed prompt and a single-file revert leaves it at today's version. Rebuild and require the hash to equal the recorded one, which is the null test that proves you landed where you meant to. Then run check, because a revert composes last week's wording against every rule added since and can be a blocking contradiction. Finally diff the live artifact against the one you are about to deploy and read every line you are giving back.
Why does reverting one line of my prompt break the build?
Because a revert is an edit, and the line you are putting back has to live with the lines added after it left. A billing prompt holding a payment plan of at most 24 months and a payment plan of at least 18 months is clean. Restore the ceiling to the 12 months it was on Tuesday and the same file is 4 blocking errors, two logic/time and two logic/numeric, graded F (79/100), and the build refuses to write anything. This is the rollback working. A partial revert that reintroduces an old bound under a new floor is exactly the change no reviewer catches by eye, and it is the one the prover catches every time.
Does a git revert of my prompt file roll back the deployed prompt?
Not by itself, if the prompt composes from more than one file. Reverting only billing.tg while the imported house.tg stays at today's version checks clean, builds, and produces an artifact that is neither the prompt that shipped on Tuesday nor the one running now. The two differ by a regulatory complaints rule that was added to the shared file and that a single-file revert silently keeps. Revert the directory, rebuild, and compare the hash against the manifest. And one thing a git revert can never bring back at all is the host's input bag, because it arrives at request time and nothing in the file pins it.
What does a prompt rollback silently change besides the rules?
The model, and no artifact diff can see it. A $CONFIG model row is control plane: it is stripped before a byte is emitted, so the same prompt built against claude-haiku-4-5 and claude-opus-4-8 produces the identical artifact hash. If the model was swapped in the same week as the rules, a rollback of the file swaps it back, and the only place that shows is the build --bundle sidecar, which names the model id and the service it requests through. Read the sidecar as part of the rollback, and log the compiler version from the build manifest too, so a rebuilt artifact is an apples-to-apples comparison with the one it is replacing.
Field note

The reason a rollback is worth nine checks rather than one command is that it is the only prompt change routinely made by somebody who is not trying to change anything. Every other edit on this blog arrives with an intent you can hold the result against: a rule to add, a bound to move, an arm to scope. A rollback arrives with an intent of "less", and "less" is not a specification, so the thing that decides what actually ships is whatever the tooling happens to reach. A single-file git checkout reaches one file and produces a prompt that has never existed. A hand-edited line reaches the wording and not the week it has to live in, which is 4 error and a refused build if you run the compiler and a silent policy contradiction if you do not. What holds it together is the same discipline as the rest of the pipeline and the same one nobody enjoys: the artifact is the deliverable, its hash is its name, and the diff against the live artifact is the only honest statement of what a rollback does. Get those three in the ticket and the October question answers itself. If you are building the gate, the prompt is frozen, the artifact is not is what counts as a change, and we moved eleven lines, six shipped a different prompt is why the source diff is not the document to review.

∿ washed up Sep 16, 2026 ∿