← Tidelines/Best practices

Rewrite: two clocks on the same promise

The response-time section is written by three people over six months and reread by nobody. Every line in it is measurable, every line is defensible, and half of them quietly cancel the other half.

by TypeGlish team6 min read#best-practices
Pick a clock.

TL;DR A duration in a prompt is a bound, and one action holds exactly one: two response-time promises on the same rule are not belt and braces, they are a blocking logic/time-strength error, because a looser bound beside a tighter one can only give the model permission to miss the tighter one. Separate genuinely different promises by naming different events, delete the rest, and the section goes F (70/100) to A (99/100).

Response times are the part of a support prompt that changes most and gets reviewed least. The SLA moved, so somebody tightened the acknowledgement rule. A customer escalated, so somebody added immediately to the billing line. The QA lead wanted the agent to sound urgent, so somebody added a line about replying as soon as possible. Each edit was a one-line change to a section that already worked, made by a person who read the line above theirs and no further, and none of them were wrong on their own.

§1The section, as it actually exists

support.tg (before)✗ won't build · F (70/100)
# Role
You are a support agent for Acme, a SaaS analytics company.

# Constraints
- MUST acknowledge every new ticket within 2 hours.
- MUST acknowledge every new ticket within 30 minutes.
- MUST escalate a billing dispute immediately.
- MUST escalate a billing dispute within 5 minutes.
- SHOULD reply as soon as possible.
Read it in a review and it looks like a strict prompt. Five hard-edged rules, four of them with a number in them. Nobody reading this in a pull request writes a comment.
tg check - output
support.tg:5:1  error  logic/time-strength  One bound per slot — "within 30 minutes" already
  entails "within 2 hours"; the looser rule on "you acknowledge every new · ticket" is
  dead weight. Keep one. (conflicts with line 5)
support.tg:6:1  error  logic/time-strength  One bound per slot — "within 30 minutes" already
  entails "within 2 hours"; ... (conflicts with line 4)
support.tg:7:1  error  logic/time-strength  One bound per slot — "immediately: MUST escalate a
  billing dispute" already entails "within 5 minutes"; the looser rule on "you escalate a
  billing · dispute" is dead weight. Keep one. (conflicts with line 7)
support.tg:8:1  error  logic/time-strength  One bound per slot — "immediately: MUST escalate a
  billing dispute" already entails "within 5 minutes"; ... (conflicts with line 6)

 1 file — 4 error, 0 warning, 0 info
Four blocking errors, two pairs. The wording is worth keeping: dead weight. The looser rule in each pair is not a second requirement, it is a permission slip, and the model can satisfy the section by honoring only the loose one.
tg score - before
support.tg — F (70/100)  proven errors — grade capped at F  tiers: base+z3
  planes  runtime 76 (what the model reads) · hygiene 50 (source only)
  facets  enforceability 100 x.21 · hardness 90 x.12 · directness 100 x.08 · consistency 0 x.17
          · structure 100 x.12 (hygiene) · annotation 0 x.12 (hygiene) · style 100 x.08 · security 100 x.08
  lever   consistency 0/100 (up to +17 overall) — Fix the ledger rows with logic/ or clarity/
          codes; state each fact in one place.
Look at enforceability 100 sitting next to consistency 0. Every rule in this section is perfectly measurable, and the section is still an F. Measurable is a property of one rule. Consistent is a property of the set, and it is the one that decides what the agent actually does.

§2Immediately is a number

The second pair is the more interesting failure, because on the page it does not look like two numbers at all. Immediately reads as tone. It is the word you add when the existing rule is technically fine and you want the model to feel the urgency.

The compiler does not read it as tone. Delay adverbs lower into the same duration intervals as written durations, and immediately is the zero interval: the tightest bound in the language. So escalate a billing dispute immediately is already stricter than anything you can add underneath it, and the diagnostic says so in as many words: "immediately: MUST escalate a billing dispute" already entails "within 5 minutes".

Which means the five-minute line, added later by someone who wanted to make the rule concrete, made it looser. That is the general shape of this defect, and it is the same mechanism as restating a rule at a weaker strength in say it once, or say it’s optional: two rules pointing the same way do not reinforce, they resolve to the weaker one, because the weaker one is the one the model can always satisfy.

A second deadline on the same promise is not a stronger promise. It is the one you will be held to.

§3The slot is the action plus what it acts on

The obvious objection is that these bounds are not really about the same thing. New tickets get 30 minutes; reopened tickets, which already have an owner, are fine at 2 hours. Both promises are real, and the section has been trying to express both all along.

It just never said so. The checker keys a time bound to the action and the object it acts on, so the fix is not a clever operator, it is a noun. Write down which ticket each clock is for and the same two durations compile side by side.

the same two bounds, on two named events✓ 0 error, 0 warning, 0 info
# Role
You are a support agent for Acme, a SaaS analytics company.

# Constraints
- MUST acknowledge a new ticket within 30 minutes.
- MUST acknowledge a reopened ticket within 2 hours.
Nothing was scoped, guarded, or conditionalized. The two rules stopped colliding because they stopped being about the same object. Worth knowing the limit here: a prose WHEN guard on its own does not separate them, because both guarded rules still ship to the model and both still speak about one it. The distinction has to live in the words the reader gets.

§4The other species: two times for one event

Durations are one half of the time layer. The other half is clock times, and it fails differently: not redundant, flatly incompatible. This is the line that arrives when the phone hours change and the second mention of them does not.

hours.tg✗ won't build
# Role
You are a support agent for Acme, a SaaS analytics company.

# Constraints
- MUST tell the customer that the phone line closes at 9pm.
- MUST tell the customer that the phone line closes at 8pm.
tg check - output
hours.tg:5:1  error  logic/time  Time conflict — "at 9pm" and "at 8pm" put two times on
  one "you tell the customer that the phone line · close" slot; a slot holds one time per
  scope. Guard one with IF (different days or conditions) or remove one. (conflicts with line 5)
hours.tg:5:1  info   prompt/naive-time  This prompt states clock times but never fixes a
  timezone — the model will guess one at runtime.
hours.tg:6:1  error  logic/time  Time conflict — "at 9pm" and "at 8pm" ... (conflicts with line 4)

 1 file — 2 error, 0 warning, 1 info
Two errors and a free reminder: a bare clock time never says whose clock, which is its own class of wrong answer, taken apart in why your agent gets the time zone wrong. Fix the conflict first. A time that is wrong in every zone does not get better once you name the zone.

§5The rewrite

Four decisions, none of them about policy. Keep the tighter bound in each pair and delete the looser one. Split the acknowledgement rule into the two events it was always trying to be. Give as soon as possible a number, because a promise nobody can measure is a promise nobody can keep. Then pin the expensive one with a $TEST, so the next person to touch this section has to notice.

support.tg (after)✓ compiles · A (99/100)
# Role
@@ role: the addressee, so every clock below belongs to somebody
You are a support agent for Acme, a SaaS analytics company.

# Constraints
@@ ack_new: the first-response clock is the one the CSAT survey is really measuring
- MUST acknowledge a new ticket within 30 minutes.
@@ ack_reopened: a reopened ticket already has an owner, so its clock is a different clock
- MUST acknowledge a reopened ticket within 2 hours.
@@ billing: a money dispute left waiting becomes a chargeback
- MUST escalate a billing dispute within 5 minutes.
@@ reply_bound: "as soon as possible" is not a promise anyone can be held to
- MUST send every reply within 15 minutes of the customer's last message.

$TEST billing_dispute
  - input:: You charged my card twice this month and nobody has replied.
  - expect::
    - contains "5 minutes"
The billing rule kept 5 minutes rather than immediately, which is the honest call: a five-minute escalation is a thing an operations team can staff and measure, and immediately was aspiration written into a spec. Choosing between them is the actual work, and the checker is what forced the choice to be made once, in the file, instead of a thousand times at runtime.
tg score & tg test - after
$ npx typeglish score support.tg
support.tg — A (99/100)  proven errors: none  tiers: base+z3
  planes  runtime 98 (what the model reads) · hygiene 100 (source only)
  facets  enforceability 93 x.21 · hardness 100 x.12 · directness 100 x.08 · consistency 100 x.17
          · structure 100 x.12 (hygiene) · annotation 100 x.12 (hygiene) · style 100 x.08 · security 100 x.08

$ npx typeglish test support.tg --dry
✓ support.tg  coverage: 2/4 rules exercised
  · billing_dispute — "You charged my card twice this month and nobody has replied." (not run)
      ✓ contains "5 minutes"
✓ 1 prompt — 0 failed
F (70/100) to A (99/100), and consistency from 0 to 100 carrying almost all of it at weight x.17. The --dry run is fully offline: it validates the case and reports coverage without sending anything to a model, so this belongs in the same CI step as check. Coverage is 2 of 4 rules, which is an honest number and a to-do list.

The whole edit took four lines out and put two nouns and a test in. No new policy, no new operator, no cleverness. The section now says once, in a form somebody can be held to, what it was previously saying four times in a form the model got to choose between.

Field note

Time bounds are proven the same way money and length bounds are: lowered into intervals and handed to a solver, which is the machinery described in the arithmetic in your prompt. The difference is that a duration hides better than a count. Nobody writes at most 3 sentences twice in one file without noticing, but immediately, within 5 minutes and as soon as possible read like three different registers of the same instruction rather than three claims on one slot. Every figure on this page is re-run against the real compiler by CI, so the codes, the counts and the grades are the checker's, not ours.

FAQCommon questions

Should I put our SLA response times in the agent's system prompt?
Put in the times the agent itself acts on or quotes to a customer, and put each one in exactly once. The failure is not having response times in the prompt, it is having the same promise written twice at two durations because two people edited two sections. A tighter bound and a looser bound on one action do not average out at runtime: the model has written permission to satisfy either, so the looser one is what you have actually shipped.
What is logic/time-strength in TypeGlish?
It is a blocking error that fires when one action carries two duration bounds and the tighter one already entails the looser one. Acknowledge within 30 minutes plus acknowledge within 2 hours is the canonical case: anything inside 30 minutes is inside 2 hours, so the second rule adds no requirement and only adds waver. The sibling code logic/time covers the incompatible case, two clock times claiming one event slot, such as closes at 9pm against closes at 8pm.
Does the word immediately count as a deadline?
Yes. TypeGlish lowers delay adverbs into the same duration intervals as written durations, and immediately is the zero interval. That makes it the tightest bound you can write, which is why escalate immediately followed by escalate within 5 minutes is a proven conflict rather than an emphasis: the five-minute line can only loosen a rule that already demanded zero delay. Say immediately when you mean it, and then do not qualify it.
How do I keep two different response times for two different cases?
Name the two cases as different things. The checker keys a time bound to the action plus the object it acts on, so acknowledge a new ticket within 30 minutes and acknowledge a reopened ticket within 2 hours compile clean together while the same two bounds on a bare it are a blocking error. A prose guard is not enough on its own, because both rules still ship to the model. Making the distinction explicit in the noun is what makes it real for the reader and for the prover.
∿ washed up Jul 30, 2026 ∿