Teardown: the peak-season block pasted above your Role
Somebody adds six lines to the top of the agent prompt in the second week of November, and nobody takes them out. Line by line, here is what those six lines did to the file underneath them, and which of the damage a compiler can see.
TL;DR A standing support prompt at 0 error, 0 warning, 0 info and B (84/100) plus a peak-season notice that is also clean on its own becomes 4 error, 2 warning, 2 info at F (60/100) with the build refused the moment they are taped together, and the four blocking findings are all about time, while the line that will actually cost you money, a Christmas delivery promise sitting under a rule forbidding delivery promises, is 0 error because the seasonal qualifier moved it out of the prover's reach.
This one is seasonal in the calendar sense and permanent in every other sense. The peak block goes in during the pre-Christmas freeze, when the change window is narrow and the safest-looking edit is the one that touches nothing: paste the new rules above everything, leave the working prompt alone. It is the right instinct about a text file and the wrong instinct about a spec.
The subject is a real shape rather than a real company: an outdoor-gear retailer's chat agent, with the standing prompt written in the spring and the notice added by the peak-readiness checklist in November. Both halves were reviewed. Neither was reviewed against the other.
§1Two clean files, taped together
Start with the two halves, because the audit trail will only ever show you one of them at a time. Here is the prompt as it stood in October.
# Role You are a support agent for Corvid Outdoor. # Constraints Support hours ARE 9am to 5pm. Our phone lines close at 5pm ET. - You MUST NOT promise a delivery date. - You MUST offer a callback within 4 hours.
And here is the block, exactly as the peak-readiness checklist supplies it. On its own it is a perfectly reasonable six lines.
# PEAK SEASON - READ FIRST Support hours ARE 8am to 8pm. Our phone lines close at 20:00 GMT. We are closed from December 24 until January 2. - You MUST promise delivery before Christmas. - You MUST offer a callback within 1 hour.
prompt/agentless-passive on We are closed, which is fair and not the interesting part. Every operational claim in here is true, signed off, and correct for December.Now paste the block above the file, which is what happened, and run the checker on the result.
# PEAK SEASON - READ FIRST Support hours ARE 8am to 8pm. Our phone lines close at 20:00 GMT. We are closed from December 24 until January 2. - You MUST promise delivery before Christmas. - You MUST offer a callback within 1 hour. # Role You are a support agent for Corvid Outdoor. # Constraints Support hours ARE 9am to 5pm. Our phone lines close at 5pm ET. - You MUST NOT promise a delivery date. - You MUST offer a callback within 4 hours.
$ npx typeglish check peak-notice.tg peak-notice.tg:2:1 error logic/time Conflicts with line 10. Time conflict — "8am to 8pm" and "9am to 5pm" give "support hours · be" two different windows; one schedule per slot per scope. peak-notice.tg:3:1 warn prompt/mixed-timezone Mixed timezones — this prompt qualifies times in GMT and ET. Unify on one zone, or make the zone a variable ($REQUIRE variable timezone: string default to UTC) and state times against it. peak-notice.tg:4:1 info prompt/agentless-passive The doer is deleted (agentless passive) - who performs this? Name the actor or use the imperative: "Verify the order." instead of "The order is verified." peak-notice.tg:5:3 info prompt/declared-later `you` is declared on line 8, after its first use here: declare the identity before the rules that use it (the model reads top-down). peak-notice.tg:6:1 error logic/time-strength Conflicts with line 13. One bound per slot — "within 1 hour" already entails "within 4 hours"; the looser rule on "you offer a · callback" is dead weight. Keep one. peak-notice.tg:12:1 error logic/time Conflicts with line 2. Time conflict — "8am to 8pm" and "9am to 5pm" give "support hours · be" two different windows; one schedule per slot per scope. peak-notice.tg:13:1 warn prompt/mixed-timezone Mixed timezones — this prompt qualifies times in GMT and ET. Unify on one zone, or make the zone a variable ($REQUIRE variable timezone: string default to UTC) and state times against it. peak-notice.tg:15:1 error logic/time-strength Conflicts with line 6. One bound per slot — "within 1 hour" already entails "within 4 hours"; the looser rule on "you offer a · callback" is dead weight. Keep one. ✗ 1 file — 4 error, 2 warning, 2 info # exit 1
Eight rows of output for six pasted lines. Take them in the order the checker reports them, because the reporting order happens to be the order of increasing consequence.
§2Line 5 talks to somebody who is not there yet
The finding almost nobody expects is prompt/declared-later on line 5, and it is a finding about the paste, not about the prose.
`you` is declared on line 8, after its first use here.
The block contains two rules addressed to you. The declaration that establishes who you is sits three lines below them, because the block was pasted above the Role section. A model reads top-down: the first two instructions in the document are commands issued to an addressee the document has not introduced. This is not fatal on its own, and the checker rates it info rather than error, which is the right call. It is diagnostic of something else.
Look at what the paste position tells you about the review. The block went at the very top because that is where a notice goes, and a notice is a thing you read before you start. But a system prompt is not read in order by a person, it is a specification consumed whole, and the top of it is structural: it is where identity and configuration live. Anything landing above the Role section is, by construction, a section that was not integrated with the file. The declared-later row is the compiler noticing the seam.
The practical version of this is a rule of thumb worth stealing. If your prompt has any content above # Role that is not a <$CONFIG> block, somebody pasted something, and it is worth an afternoon finding out who and when.
§3Two schedules, one slot, two clocks
The two logic/time errors are the ones the file deserves and the ones a person would eventually catch. What is worth reading closely is the slot key in the message: "support hours · be". The prover has not concluded that a prompt mentions two sets of hours. It has concluded that one named object, support hours, has been bound to two different windows, and a schedule slot holds one. One schedule per slot per scope.
The scope qualifier is the entire seasonal problem stated in three words. There is a scope in which 8am to 8pm is correct and a scope in which 9am to 5pm is, and the file does not contain the concept. It contains two unconditional claims sitting nine lines apart.
Beside them, the prompt/mixed-timezone warnings, which fire twice because both halves qualify a time and they qualify it differently: 20:00 GMT in the block, 5pm ET in the standing prompt. That is not a peak-season defect, it is a peak-season exposure. The standing prompt was in ET and nobody in the November edit thought about zones at all, because when you are writing about your own contact centre the zone is invisible to you. It becomes visible when a second author writes about the same contact centre from a different desk. The zone problem has its own set of failure modes, and pasting is a reliable way to import them.
§4The rule the peak block repealed without deleting
The two logic/time-strength rows are the subtle finding and the reason this teardown is worth your time. They are not an impossibility claim. Read the message carefully:
peak-notice.tg:6:1 error logic/time-strength Conflicts with line 13. One bound per slot — "within 1 hour" already entails "within 4 hours"; the looser rule on "you offer a · callback" is dead weight. Keep one. # line 6: - You MUST offer a callback within 1 hour. (the November block) # line 13: - You MUST offer a callback within 4 hours. (the standing prompt)
What has happened operationally is that the peak block silently replaced the standing callback SLA, and did it without an edit anybody can point at. There is no diff line reading callback SLA: 4h to 1h. There is a diff that adds six lines, four of which are about hours and closures, and one of which quietly tightened a service commitment by four hundred percent.
Then consider January. Somebody removes the peak block, because that part of the checklist does work. The four-hour rule comes back to life, correctly, and nobody notices that it was ever gone. Now consider the other January, the more common one, where the block is not removed until March because the person who added it has moved teams. The agent spends three months promising hour-long callbacks that the standard rota cannot staff, and the only evidence in the repository is a block whose heading says PEAK SEASON.
That is a retired policy still selling itself with the polarity reversed: not an old rule that outlived the product, but a temporary rule that outlived the temporary.
§5The line the checker cannot save you from
Eight rows of output, and not one of them is about what line 5 says. The only row that mentions line 5 is the declared-later note about where it sits. Line 5 is this:
- You MUST promise delivery before Christmas.
Nine lines below it is the rule legal wrote: You MUST NOT promise a delivery date. Those two rules are the reason the standing prompt has a prohibition in it at all, and the prover says nothing about the pair. Three one-rule files against the same prohibition, checked in one invocation, show exactly where the line is.
# Role You are a support agent for Corvid Outdoor. # Constraints - You MUST NOT promise a delivery date. - You MUST promise a delivery date.
# Role You are a support agent for Corvid Outdoor. # Constraints - You MUST NOT promise a delivery date. - You MUST promise a delivery date before Christmas.
$ npx typeglish check promise-bare.tg promise-qualified.tg promise-reworded.tg promise-bare.tg:4:1 error logic/contradiction Conflicts with line 5. Logical conflict — "promise a delivery date" is both required and forbidden. Keep one, or scope the two rules so they cannot both apply (IF <condition> THEN ...). promise-bare.tg:5:1 error logic/contradiction Conflicts with line 4. Logical conflict — "promise a delivery date" is both required and forbidden. Keep one, or scope the two rules so they cannot both apply (IF <condition> THEN ...). ✗ 3 files — 2 error, 0 warning, 0 info # promise-reworded.tg (MUST promise delivery before Christmas) 0 error, 0 warning, 0 info
This is the honest limit of the whole exercise and it should shape how you use a checker on a peak block. The provers are strongest on the claims that are unconditional, which is to say the ones a careful author is least likely to get wrong. The seasonal exception, written under time pressure, in a hurry, by someone reading only the last edit, is exactly the shape they cannot reach. Machine checking narrows the class of defect a human review has to look for. It does not empty it.
§6Make the season an input, not a paste
The fix is not to write the block more carefully. It is to stop the block from being a block. A season is a fact that varies, and a fact that varies belongs on the machine plane as a typed input, with the seasonal text on the arms of a switch. Then there is exactly one place per slot, and the compiler picks.
<$CONFIG> $REQUIRE variable season: one of standard, peak </$CONFIG> # Role @@ role: outdoor-gear retail support, one agent for both seasons You are a support agent for Corvid Outdoor. # Constraints @@ no_dates: a quoted date binds the company and the carrier does not honour it in December either - You MUST NOT promise a delivery date. @@ no_closure_claims: the shutdown dates change every year and a prompt is not where they live - You MUST NOT name a date on which Corvid Outdoor closes. # Service levels @@ seasonal: one slot, one bound, picked at compile time rather than argued at runtime $SWITCH ON @{season} - standard:: @@ standard_hours: the published schedule, stated once, in one zone Support hours ARE 9am to 5pm UTC. @@ standard_callback: four hours is what the rota can staff outside peak You MUST offer a callback within 4 hours. - peak:: @@ peak_hours: the extended schedule operations signs off each November Support hours ARE 8am to 8pm UTC. @@ peak_callback: one hour is the peak commitment, and it replaces the standard one You MUST offer a callback within 1 hour. $TEST no_christmas_promise - input:: Will this arrive before Christmas? - expect:: - matches /^(?!.*(guarantee|will arrive))/
$ npx typeglish build peak-fixed.tg --vars '{"season":"peak"}'
✓ built .typeglish/dist/peak-fixed.txt ← peak-fixed.tg (36c723eac551, full)
# Constraints
- You MUST NOT promise a delivery date.
- You MUST NOT name a date on which Corvid Outdoor closes.
# Service levels
Support hours ARE 8am to 8pm UTC. You MUST offer a callback within 1 hour.
# 258 bytes. The four-hour rule is not in this document.
$ npx typeglish build peak-fixed.tg --vars '{"season":"standard"}'
✓ built .typeglish/dist/peak-fixed.txt ← peak-fixed.tg (396f76fdd35d, full)
# 259 bytes, and the one-hour rule is not in this one
$ npx typeglish build peak-fixed.tg
✓ built .typeglish/dist/peak-fixed.txt ← peak-fixed.tg (49b64244c62d, full)
# 380 bytes: the unbound template, which ships both arms as conditional prose
There is a second payoff, and it arrives the year somebody invents a third season. Add clearance to the domain and every switch that was not updated fails loudly.
$ npx typeglish check peak-clearance.tg peak-clearance.tg:17:1 error structure/non-exhaustive-switch @{season} can be clearance, but no arm handles it — add a "- <member>::" row for each (or a deliberate "- otherwise::" fallback). ✗ 1 file — 1 error, 0 warning, 0 info # exit 1
One honest note on the test. typeglish test peak-fixed.tg --dry validates the case and reports coverage: 0/4 rules exercised: the negative-lookahead assert pins the absence of a delivery promise in the response, but it is not attached to any of the four rules, so it contributes nothing to coverage. It is still the right test. It is the only artefact in the file that would fail if somebody put the Christmas line back.
§7Common questions
- Why does my AI agent give customers the wrong opening hours?
- Almost always because the prompt states them twice. A seasonal notice pasted above the standing prompt gives one slot two windows, and TypeGlish proves it:
Support hours ARE 8am to 8pmbesideSupport hours ARE 9am to 5pmis 2 blockinglogic/timeerrors reading one schedule per slot per scope, and the build refuses. If your prompt does not go through a checker, grep it for the word hours before you grep the transcripts. The second copy is usually in a block somebody added for a specific week. - Where should a seasonal or holiday notice go in a system prompt?
- Nowhere, as a block. Make the season an input and the notice a branch: declare
$REQUIRE variable season: one of standard, peak, then put each season's hours and callback bound on its own arm of a$SWITCH ON @{season}. The compiler resolves one arm and deletes the other, so the peak commitment is in the artifact during peak and absent from it in January without anyone editing a file. That version is 0 error, 0 warning, 0 info at A (93/100), and it builds to36c723eac551for peak and396f76fdd35dfor standard from the same source. - Why does pasting a block at the top of a prompt cause problems?
- Because the top of the file is where the identity is declared, and a block pasted above it contains rules addressed to a you the document has not introduced yet. The checker names it:
prompt/declared-later, you is declared on line 8, after its first use here. A model reads top-down, so the first instructions it meets are commands with no established addressee, and they are also the ones a reader skims past because they are visually a banner rather than a rule. Paste below the Role section if you must paste at all. - Will a checker catch a seasonal delivery promise that contradicts my no-dates rule?
- Only if the two rules name the same action.
MUST NOT promise a delivery datebesideMUST promise a delivery dateis 2 blockinglogic/contradictionerrors. Add the seasonal qualifier that every peak notice carries and the pair goes quiet:MUST promise a delivery date before Christmasis 0 error, 0 warning, 0 info against the same prohibition, and so isMUST promise delivery before Christmas. The qualifier that makes the line feel safe to add is the same qualifier that hides it from the prover, so seasonal exceptions need a human reading the standing rules, or a$TESTthat pins the absence.
The detail that stayed with us is the reporting order. The checker walks the file top to bottom, so its output opens with the two lines about opening hours and closes with the callback SLA, and the rule that promises a customer their parcel will arrive by the 25th appears nowhere in it. A prompt engineer working the list from the top would fix four time errors, tidy two zones, ship a green build, and leave in place the only line in the file with a refund attached to it. That is not an argument against the list. It is an argument for reading a pasted block against the file it landed on, once, with your own eyes, before you run anything, and for spending the ten seconds it takes to write the $TEST that pins the absence of the promise. Seasonal edits get made at the worst time of year by the person with the least context. Give them a variable to flip instead of a paragraph to paste, and the January cleanup stops being a thing anyone has to remember.