← Tidelines/Teardowns

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.

by TypeGlish team9 min read#teardowns
Six lines, added in November.

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.

base.tg - the standing prompt✓ B (84/100)
# 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.
0 error, 0 warning, 0 info at B (84/100), enforceability 85, consistency 100. Nothing to argue with: one schedule, one callback bound, one prohibition that legal asked for.

And here is the block, exactly as the peak-readiness checklist supplies it. On its own it is a perfectly reasonable six lines.

block.tg - the peak-season notice, alone✓ 0 error
# 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.
0 error, 0 warning, 1 info. The single finding is a 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-notice.tg - what shipped on 12 November✗ 4 error, 2 warning
# 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.
Fifteen lines. Nothing was deleted, nothing was edited, and every individual sentence in the file was true when it was written.
tg check peak-notice.tg - output
$ 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
B (84/100) and a clean six-line block go in; F (60/100) with consistency 0 and a refused build comes out. Two files that are each individually fine producing a broken third is the same failure that shows up when a re-org folds two desks into one prompt, except that here nobody thinks of it as a merge. They think of it as a notice.

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:

the finding, in full
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)
The two rules can both hold. An agent that calls back within an hour has satisfied both. That is precisely the complaint: the four-hour rule is now unreachable text, and the checker treats dead weight in front of a model as an error rather than a note.

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.

promise-bare.tg - the same rule, unqualified✗ 2 error
# Role
You are a support agent for Corvid Outdoor.
# Constraints
- You MUST NOT promise a delivery date.
- You MUST promise a delivery date.
The control. Same action, opposite force, and the prover has all it needs.
promise-qualified.tg - one clause added✓ 0 error, 0 warning, 0 info
# 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.
Two words appended, and the file is clean. Before Christmas makes the object of the obligation a different object from the one the prohibition covers, so there is no longer one action with two forces on it.
tg check - all three in one run
$ 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
Three files, one finding, and it is on the version nobody would ever write. The qualifier that makes a seasonal rule feel safe to add is the same qualifier that removes it from the prover's reach, and every seasonal rule carries one, because that is what makes it seasonal.

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.

peak-fixed.tg - the season as a domain member✓ A (93/100)
<$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))/
0 error, 0 warning, 0 info at A (93/100): consistency 100, annotation 100, hygiene 50 to 100. Both callback bounds are still in the source and they no longer conflict, because the compiler will only ever emit one of them. The closure dates are gone entirely, replaced by a rule forbidding the agent from stating them, since a shutdown window is a fact the roster owns and the prompt does not.
tg build peak-fixed.tg - one source, two seasons
$ 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
This is what a seasonal notice should cost: a variable binding at deploy time, not an edit. Coming out of peak is flipping one input, and the artifact hash changes, which is the audit trail the paste never produced.

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.

tg check - after a third member joins the domain
$ 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
The same two-way lock a declared tier set gives you, applied to the calendar. A new season is now a compile error in every prompt that has an opinion about seasons, which is the notification the November checklist was trying to be.

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 8pm beside Support hours ARE 9am to 5pm is 2 blocking logic/time errors 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 to 36c723eac551 for peak and 396f76fdd35d for 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 date beside MUST promise a delivery date is 2 blocking logic/contradiction errors. Add the seasonal qualifier that every peak notice carries and the pair goes quiet: MUST promise a delivery date before Christmas is 0 error, 0 warning, 0 info against the same prohibition, and so is MUST 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 $TEST that pins the absence.
Field note

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.

∿ washed up Sep 5, 2026 ∿