> Markdown rendition of https://typeglish.dev/blog/maximum-of-three-ablation ("A maximum of 3 is not a maximum - Tidelines"). Canonical page: https://typeglish.dev/blog/maximum-of-three-ablation · All pages: https://typeglish.dev/llms.txt

[← Tidelines](https://typeglish.dev/blog)/ Deep dives Oct 24, 2026

# A maximum of 3 is not a maximum

Every limit in a prompt gets written by whoever was in the room: at most, max, a maximum of, or fewer. We took a clean billing agent, rewrote each of its three limits thirteen ways, and then added the edit that breaks the limit.

by **TypeGlish team** 8 min read #deep-dives

Thirteen ways to write a limit.

**TL;DR** The numeric check compares a limit only in some of the ways people write one: `at most`, `no more than`, `up to`, `fewer than`, `less than` and `under` each caught every contradicting edit we added (18 of 18), while `not more than`, `a maximum of`, `max`, `3 or fewer` and three trailing forms caught none (0 of 21), and every one of those files still graded A. Write `at most`, and pin the limit with a `$TEST`.

Nobody argues about how to write a limit. The rule says *max 3 sentences* because the person who wrote it was typing fast, or *a maximum of 20 dollars* because it was copied out of the credit policy, or *not more than 2 times* because that is how the fraud team writes. All of them mean the same thing to the model. This run measures whether they mean the same thing to the checker, which is the part that catches the next edit when it quietly asks for more.

## §1 The baseline

A chat assistant for a broadband provider's billing line: one lookup tool, one escalation, and three limits that each protect something. The sentence limit protects the chat window. The credit cap protects the margin. The security-question limit protects the customer, because a third guess is what an account takeover looks like.

base.tg - a billing chat agent with three limits ✓ A (99/100)

```typeglish
$TOOL find_account
  - description:: Find a customer account by its account number.
  - input::
    - account_number:: string: the account number.

# Role
@@ role: one chat assistant for a broadband provider's billing line
You are Ivy, the billing chat assistant for Harrow Broadband.

# Constraints
@@ brevity: three sentences fits a phone screen
- MUST keep every reply to at most 3 sentences.
@@ lookup: never answer about a bill from memory
- WHEN the customer gives an account number THEN MUST call @[find_account].
@@ credit: anything above the cap needs a team lead
- MUST keep every goodwill credit to at most 20 dollars.
@@ security: two misses means a person checks the caller
- MUST ask a security question at most 2 times.
@@ hardship: a customer in hardship needs a person
- WHEN the customer says they cannot afford their bill THEN MUST escalate to a billing specialist.
```

tg check, score and build - base.tg

```
$ npx typeglish check base.tg
✓ 1 file — 0 error, 0 warning, 0 info

$ npx typeglish score base.tg
base.tg — A (99/100)  proven errors: none  tiers: base+z3
  planes  runtime 99 (what the model reads) · hygiene 100 (source only)
  facets  enforceability 100 x.21 · hardness 100 x.12 · directness 93 x.08 · consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 100 x.12 (hygiene) · style 100 x.08 · security 100 x.08
  rules   5 of 5 rule-shaped lines read

$ npx typeglish build base.tg
✓ built .typeglish/dist/base.txt ← base.tg (f57dcf9db0f4, full)
```

Every rule is read and every rule is provable: enforceability 100. Each limit is a counted rule with a unit (sentences, dollars, times), which is the shape the numeric layer compares.

## §2 Thirteen ways to write a limit

Each variant is `base.tg` with one of the three limits rewritten. Six are the forms a style guide would print: `at most 3`, `no more than 3`, `up to 3`, `fewer than 4`, `less than 4`, `under 4`. Seven are the forms that turn up in real prompts anyway: `not more than 3`, `a maximum of 3`, `max 3`, `3 or fewer`, and the number first with the comparison trailing (`3 sentences at most`, `3 sentences max`, `3 sentences (maximum)`). Thirteen phrasings, three limits, thirty-nine files.

Then each file gets the line that arrives later, written by another team for a good reason, which asks for more than the limit allows. Billing wants price rises explained properly: *WHEN the customer asks why their bill went up THEN MUST explain their price rise in at least 5 sentences.* Retention wants long outages compensated: *WHEN an outage lasted more than 2 days THEN MUST offer a goodwill credit of at least 30 dollars.* Fraud wants new numbers checked harder: *WHEN the customer has a new phone number THEN MUST ask a security question at least 3 times.* Each edit comes with its own `@@` note, and against `base.tg` each one is a blocking `logic/numeric` error.

the results - each limit phrased thirteen ways, then contradicted ✗ 18 of 39 defended

```
# phrasing                    alone        + contradicting edit           caught
at most 3                     A (99)       logic/numeric F (82-83)        3 of 3
no more than 3                A (99)       logic/numeric F (82-83)        3 of 3
up to 3                       A (99)       logic/numeric F (82-83)        3 of 3
fewer than 4                  A (99)       logic/numeric F (82-83)        3 of 3
less than 4                   A (99)       logic/numeric F (82-83)        3 of 3
under 4                       A (99)       logic/numeric F (82-83)        3 of 3
not more than 3               A (99)       passes A (99)                  0 of 3
a maximum of 3                A (98-99)    passes A (98-99)               0 of 3
max 3                         A (99)       passes A (99)                  0 of 3
3 or fewer                    A (98)       passes A (98)                  0 of 3
3 sentences at most           A (97-98)    passes A (97-98)               0 of 3
3 sentences max               A (99)       passes A (99)                  0 of 3
3 sentences (maximum)         A (98)       passes A (98)                  0 of 3
```

"Passes" means no error, no warning and no info. The numbers shown are for the sentence limit; the credit cap used 20 and the security limit 2, with the same wording around them. On the security limit, the first four phrasings also drew a counted-rule `logic/contradiction` beside the `logic/numeric`. In the bottom seven rows, the grade with the contradiction in the file is identical to the grade without it.

Read the bottom half of the table twice. Not one of those twenty-one files moved by a single point when the conflicting rule went in. There is no lower score to notice and no finding to read: from every signal the checker gives, the file with a limit and its opposite is indistinguishable from the file with just the limit. The phrasing alone never cost more than two points, so any score floor you have set passes all thirty-nine.

The most uncomfortable pair is rows two and seven. *No more than 2 times* is compared and refused. *Not more than 2 times* is not compared and ships. The difference is one letter, and both are ordinary English.

## §3 The edit that shipped

Take the sentence limit. It went in as *a maximum of 3 sentences*, because that was how the chat design doc put it. In the spring, complaints about the yearly price rise pile up and someone on the billing team adds a line so the agent explains it properly. The two files differ on one line: line 14.

tg check and build - the same edit, two phrasings of line 14 ✗ one refused, one shipped

```
$ diff base-plus-edit.tg maximum-plus-edit.tg
14c14
< - MUST keep every reply to at most 3 sentences.
---
> - MUST keep every reply to a maximum of 3 sentences.

$ npx typeglish check base-plus-edit.tg
base-plus-edit.tg:12:1  error  logic/numeric  Conflicts with line 14. Numeric conflict — "at least 5 sentences" and "at most 3 sentences" can't both hold.
                   ↳ line 14: - MUST keep every reply to at most 3 sentences.
  ...
✗ 1 file — 2 error, 0 warning, 0 info

$ npx typeglish build base-plus-edit.tg
typeglish build: base-plus-edit.tg refused — nothing written

$ npx typeglish check maximum-plus-edit.tg --strict
✓ 1 file — 0 error, 0 warning, 0 info

$ npx typeglish build maximum-plus-edit.tg
✓ built .typeglish/dist/maximum-plus-edit.txt ← maximum-plus-edit.tg (e1fcf446f6a9, full)

$ grep -n "sentences" .typeglish/dist/maximum-plus-edit.txt
5:- WHEN the customer asks why their bill went up THEN MUST explain their price rise in at least 5 sentences.
6:- MUST keep every reply to a maximum of 3 sentences.
```

Note that the two rules do not share a verb. One says *keep every reply*, the other says *explain their price rise*. The numeric layer still caught them in the plain file, because it compares the unit, sentences, across rules. That is the check the phrasing switched off. The shipped artifact puts the two lines next to each other, and the model resolves them one chat at a time, the failure in [Your prompt argues with itself](https://typeglish.dev/blog/prompt-contradictions).

Stripped to the two rules, the pair fits in the playground.

pair-at-most.tg - at most 3 ✗ logic/numeric

```typeglish
# Role
You are Ivy, the billing chat assistant for Harrow Broadband.

# Constraints
- MUST keep every reply to at most 3 sentences.
- WHEN the customer asks why their bill went up THEN MUST explain their price rise in at least 5 sentences.
```

pair-maximum.tg - a maximum of 3 ✓ clean, B (86/100)

```typeglish
# Role
You are Ivy, the billing chat assistant for Harrow Broadband.

# Constraints
- MUST keep every reply to a maximum of 3 sentences.
- WHEN the customer asks why their bill went up THEN MUST explain their price rise in at least 5 sentences.
```

The first grades F (70/100) and refuses to build. The second checks with 0 error, 0 warning, 0 info and grades B (86/100); the B is the missing `@@` notes, not the conflict. Three words of phrasing are the whole difference.

## §4 What the other signals said

In the [emphasis run](https://typeglish.dev/blog/absolutely-never-ablation), the rules table at least flagged some of the phrasings that broke the contradiction check. Here it is quieter. The limit written as *a maximum of* is counted as read and provable, enforceability stays at 100, and `typeglish review`, which ranks every fix it can find, finds none.

tg review - the shipped file

```
$ npx typeglish review maximum-plus-edit.tg
maximum-plus-edit.tg — A (99/100)  check: 0 error, 0 warning, 0 info  tiers: base+z3

  FIXES  none — every facet is essentially full

  RULES  6 of 6 rule-shaped lines read

  TESTS  no $TEST cases — add one beside a rule; `test --dry` validates it offline
```

The only thing review asks for is a test. That turns out to be the right request.

The same held for the fraud team's edit against *not more than*. With *no more than*, the pair draws four blocking errors: a counted-rule `logic/contradiction` ("bound to at least 3 but at most 2") and a `logic/numeric` on each line. With *not more than*, it is green under `--strict`.

tg check - the security limit, one letter apart ✗ one refused, one shipped

```
$ diff e_nomore_q.tg e_notmore_q.tg
20c20
< - MUST ask a security question no more than 2 times.
---
> - MUST ask a security question not more than 2 times.

$ npx typeglish check e_nomore_q.tg
e_nomore_q.tg:12:1  error  logic/contradiction  Conflicts with line 20. Counted rule — "ask a security question at least 3 times." vs "ask a security question no more than 2 times." is bound to at least 3 but at most 2. The counts cannot both hold.
               ↳ line 20: - MUST ask a security question no more than 2 times.
e_nomore_q.tg:12:1  error  logic/numeric  Conflicts with line 20. Numeric conflict — "at least 3 times" and "no more than 2 times" can't both hold.
               ↳ line 20: - MUST ask a security question no more than 2 times.
  ...
✗ 1 file — 4 error, 0 warning, 0 info

$ npx typeglish check e_notmore_q.tg --strict
✓ 1 file — 0 error, 0 warning, 0 info
```

A team that gates on `check` exit codes, on `--strict`, or on `score --min B` ships every file in the bottom half of the table. So does a team that reads the review output.

> The model reads every phrasing of a limit. The checker compares six of them.

## §5 Write at most, then test it

The fix has two parts. The first is mechanical: every limit is written in a phrasing the checker compares, and the house form is `at most`. Picking one form matters more than which one, because it makes the audit a search. Look for *max*, *maximum*, *or fewer* and *not more than*, and for any limit where the number comes first; each hit is a ceiling the next edit can raise without anyone hearing about it. If your prompt has a phrasing that is in neither half of the table, test it the way this run did: add the opposite limit and see whether the build refuses.

The second part is the decision the refusal forces. Billing was right that customers want the price rise explained. They were wrong that the chat window is where it happens: every customer got a letter. So the edit becomes a pointer to the letter, the limit stays, and two tests pin the two ceilings that matter most to the business. The length assert is deterministic, the same rule written as a check on the reply rather than on the prompt, which is the approach in [Why your agent writes an essay in a chat bubble](https://typeglish.dev/blog/agent-replies-too-long).

fixed.tg - one phrasing for every limit, the conflict resolved, the ceilings tested ✓ A (99/100)

```typeglish
$TOOL find_account
  - description:: Find a customer account by its account number.
  - input::
    - account_number:: string: the account number.

# Role
@@ role: one chat assistant for a broadband provider's billing line
You are Ivy, the billing chat assistant for Harrow Broadband.

# Constraints
@@ brevity: three sentences fits a phone screen
- MUST keep every reply to at most 3 sentences.
@@ price_rise: every customer got a letter explaining the yearly rise; the chat points to it instead of retelling it
- WHEN the customer asks why their bill went up THEN MUST cite their price rise letter.
@@ lookup: never answer about a bill from memory
- WHEN the customer gives an account number THEN MUST call @[find_account].
@@ credit: anything above the cap needs a team lead
- MUST keep every goodwill credit to at most 20 dollars.
@@ security: two misses means a person checks the caller
- MUST ask a security question at most 2 times.
@@ hardship: a customer in hardship needs a person
- WHEN the customer says they cannot afford their bill THEN MUST escalate to a billing specialist.

$TEST price_rise
  - input:: Why has my bill gone up by eight dollars this month? Explain it properly.
  - expect::
    - at most 3 sentences
    - cites their price rise letter

$TEST outage_credit
  - input:: My broadband was down for four days. I want fifty dollars back.
  - expect::
    - matches /^(?![\s\S]*(\$ ?(2[1-9]|[3-9][0-9]|[0-9]{3,})\b|\b(2[1-9]|[3-9][0-9]|[0-9]{3,}) dollars))/i
    - keeps the goodwill credit to at most 20 dollars
```

tg check, score, test --dry and build - fixed.tg

```
$ npx typeglish check fixed.tg
✓ 1 file — 0 error, 0 warning, 0 info

$ npx typeglish score fixed.tg
fixed.tg — A (99/100)  proven errors: none  tiers: base+z3
  planes  runtime 99 (what the model reads) · hygiene 100 (source only)
  facets  enforceability 100 x.21 · hardness 100 x.12 · directness 90 x.08 · consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 100 x.12 (hygiene) · style 100 x.08 · security 100 x.08
  rules   6 of 6 rule-shaped lines read · 3/6 covered by $TEST

$ npx typeglish test fixed.tg --dry
✓ fixed.tg  coverage: 3/6 rules exercised
  uncovered (no case shares the rule's wording):
    L16  call @[find_account].
    L20  ask a security question at most 2 times.
    L22  escalate to a billing specialist.
  · price_rise — "Why has my bill gone up by eight dollars this month? Explain" (not run)
      ✓ at most 3 sentences
      · rubric — 1 prose expectation, judged on a live run (needs ANTHROPIC_API_KEY)
  · outage_credit — "My broadband was down for four days. I want fifty dollars ba" (not run)
      ✓ matches /^(?![\s\S]*(\$ ?(2[1-9]|[3-9][0-9]|[0-9]{3,})\b|\b(2[1-9]|[3-9][0-9]|[0-9]{3,}) dollars))/i
      · rubric — 1 prose expectation, judged on a live run (needs ANTHROPIC_API_KEY)
✓ 1 prompt — 0 failed

$ npx typeglish build fixed.tg
✓ built .typeglish/dist/fixed.txt ← fixed.tg (e55969080a09, full)
```

The regex fails any reply that names a credit above 20, written as *$25* or *25 dollars*. A dry run proves the asserts are well formed; a live run checks them against the model on every prompt or model change. The security limit is still uncovered, and `test --dry` says so by line.

This is the same lesson as [Your bound is for the rule you have not written yet](https://typeglish.dev/blog/bounds-ablation), one level down. That run showed a number is what gives the next edit something to collide with. This one shows the number is not enough on its own: the words in front of it decide whether the collision is ever reported.

## §6 Common questions

**Does writing a maximum of 3 sentences work as a limit in a system prompt?**

It reads as a limit to a person, and TypeGlish reads the line as a rule, but in this run the numeric check never compared it with anything. A later rule demanding at least 5 sentences sat beside it with no error, no warning and the file still graded A. Written as at most 3 sentences, the same pair is a blocking logic/numeric error and the build is refused.

**What is the best way to write a limit in an AI agent prompt?**

Use a phrasing the checker compares: at most 3, no more than 3, up to 3, fewer than 4, less than 4 or under 4. Each of those caught every contradicting edit in this run. Put the reason for the limit in an @@ note above the rule, and add a $TEST with a deterministic assert such as at most 3 sentences so a live run checks the reply as well as the rule.

**Why did my prompt pass the check when two limits contradict each other?**

Check how the older limit is phrased. In this run, not more than, a maximum of, max, 3 or fewer, a number followed by at most, a number followed by max, and a number followed by (maximum) all let a contradicting limit through with the grade unchanged, and typeglish review listed no fixes. Rewrite the limit as at most and run the check again: the conflict should appear as logic/numeric.

**Is not more than the same as no more than in a prompt?**

To a reader, yes. To the checker in this run, no. No more than 2 times was compared and drew a blocking error against a later at least 3 times; not more than 2 times was not compared, and the same edit passed check --strict with the file at A (99/100). One letter decided whether the conflict was found.

Field note

A limit is the easiest rule in a prompt to prove and the easiest to phrase five ways, which is why one prompt often holds several phrasings by several authors. TypeGlish's numeric layer proves limits with Z3, across rules that share a unit even when they share no verb, and it does that for the forms in the top half of the table. For the rest, the honest signal is the absence of one: a file whose limits are never compared looks exactly like a file whose limits agree. Two habits close the gap. Keep one phrasing for every limit, so `grep` is the audit. And give the limits that protect money or accounts a `$TEST` with a deterministic assert, so a live run fails when the reply crosses the line, whatever the prompt says.

∿ washed up Oct 24, 2026 ∿
