Why your agent writes an essay in a chat bubble
The customer asks whether they can send a tent back. The agent replies with seven sentences, two apologies and the whole returns policy. Your prompt says to be concise, and it also says four other things that cost more sentences than that.
TL;DR A length rule with no number loses to every content rule beside it, so write the limit as a count of sentences, give any rule that costs length a count too so the checker can compare them, move the long detail out of the reply, and assert the length in every $TEST case.
Tamsin Outdoor sells tents and waterproofs, and its chat assistant is called Pip. The brand team asked for short replies. The QA scorecard marks down incomplete answers. The CSAT review asked for more empathy. Legal wants every return condition stated. Each of those asks became a line in the prompt, each line is reasonable on its own, and the chat widget now shows a scroll bar on a yes-or-no question.
§1The prompt that asks for both
Here is the constraints section as it stands after a quarter of feedback rounds. Nobody wrote anything wrong, and that is the point.
# Role @@ role: one desk, one name, so every later "you" is the agent You are Pip, the chat assistant for Tamsin Outdoor. # Constraints @@ brevity: the brand team asked for short replies - MUST keep every reply concise. @@ thorough: the QA scorecard marks down incomplete answers - MUST give the customer a thorough answer. @@ empathy: the CSAT review asked for this - MUST acknowledge the customer's feelings. @@ returns: legal wants every condition stated - WHEN the customer asks about returns THEN MUST explain the return conditions.
$ typeglish review pip.tg pip.tg - A (100/100) check: 0 error, 0 warning, 0 info tiers: base+z3 FIXES none - every facet is essentially full RULES 4 of 4 rule-shaped lines read TESTS no $TEST cases - add one beside a rule; `test --dry` validates it offline
check --strict is just as quiet. Every rule is read. None of them is in conflict, because none of them commits to an amount the others could contradict.That is not the checker being lenient. Concise and thorough genuinely do not contradict each other: a short, complete answer satisfies both. The trouble is what happens at runtime, when the model has to satisfy all four at once on a real message. Acknowledging a feeling is a sentence. Explaining the return conditions is several. A thorough answer is whatever the model decides thorough means. Each content rule has a definite cost, and the length rule is the only one of the four with no edge, so it is the only one that can be bent without visibly breaking. The model is not ignoring your brevity rule. It is doing the only trade the prompt left open.
§2Give the length an edge
The first fix is the obvious one, and it is worth doing even though it does not finish the job. Replace the adjective with a count. Sentences are the right unit for a chat bubble: a customer skims them, a reviewer can count them, and a test can assert on them.
# Role @@ role: one desk, one name, so every later "you" is the agent You are Pip, the chat assistant for Tamsin Outdoor. # Constraints @@ brevity: a chat bubble past three sentences gets scrolled, not read - MUST keep every reply to at most 3 sentences. @@ empathy: the CSAT review asked for this - MUST acknowledge the customer's feelings. @@ returns: legal wants every condition stated - WHEN the customer asks about returns THEN MUST explain the return conditions.
A (100/100) with nothing flagged.Be honest about what that bought. The model now has an edge it can aim at, which matters more than it sounds: at most 3 sentences is a target, concise is a mood. But the returns rule still asks for every condition, and there are five. The checker cannot see that collision, because explain the return conditions carries no count. It has no way to know the conditions take five sentences; only the person who pasted them from the policy page knows that. A bound can only be compared with other bounds, the same lesson as try again is not a number.
§3When the other rule has a number too
So write down what legal actually asked for. Five conditions, one sentence each, is five sentences, and once that is in the file the checker has two intervals to intersect:
# Role @@ role: one desk, one name, so every later "you" is the agent You are Pip, the chat assistant for Tamsin Outdoor. # Constraints @@ brevity: a chat bubble past three sentences gets scrolled, not read - MUST keep every reply to at most 3 sentences. @@ empathy: the CSAT review asked for this - MUST acknowledge the customer's feelings. @@ returns: legal wants every condition stated, and there are five - WHEN the customer asks about returns THEN MUST explain the return conditions in at least 5 sentences.
$ typeglish check pip.tg pip.tg:7:1 error logic/numeric Conflicts with line 11. Numeric conflict - "at most 3 sentences" and "at least 5 sentences" can't both hold. ↳ line 11: - WHEN the customer asks about returns THEN MUST explain the return conditions in at least 5 sentences. pip.tg:11:1 error logic/numeric Conflicts with line 7. Numeric conflict - "at most 3 sentences" and "at least 5 sentences" can't both hold. ↳ line 7: - MUST keep every reply to at most 3 sentences. ✗ 1 file - 2 error, 0 warning, 0 info
typeglish review puts this at F (83/100), capped until the conflict is resolved, and the build refuses to write an artifact.This is the prompt you actually had in section 1. The conflict was always there; writing both sides as numbers is what made it a proven error instead of a scroll bar. It is the same move as writing a spending limit as an obligation in your agent can now do something it cannot undo: the policy does not change, the claim becomes comparable.
Now resolve it, and resist the first instinct, which is to raise the bound to six. The bound is the customer's experience; the five conditions are a document. The CX answer is the one a good human agent gives: state the fact the customer needs, and link the page for the rest. That splits one rule into two, each with a trigger you can write a test for.
One trap on the way. It is tempting to budget the empathy line as well, in at most 1 sentence, so the three sentences are shared out explicitly. The checker pushes back:
# Role @@ role: one desk, one name, so every later "you" is the agent You are Pip, the chat assistant for Tamsin Outdoor. # Constraints @@ brevity: a chat bubble past three sentences gets scrolled, not read - MUST keep every reply to at most 3 sentences. @@ empathy: one sentence of acknowledgement, not a paragraph of it - MUST acknowledge the customer's feelings in at most 1 sentence.
$ typeglish check pip.tg pip.tg:7:1 warn prompt/count-alignment Count alignment - "sentence" carries two different count rules (line 9): one names a unit, one does not. Two spellings of one limit read as drift - state ONE bound with ONE unit. ↳ line 9: - MUST acknowledge the customer's feelings in at most 1 sentence. ✓ 1 file - 0 error, 1 warning, 0 info
The model is not ignoring your brevity rule. It is doing the only trade the prompt left open.
§4What ships, and the assert that holds it
Here is the file with the bound, the budgeted empathy line, the fact in the bubble and the detail behind a link. Every case carries the same deterministic length assert, because the reply that runs long is rarely the one answering a question about length.
# Role @@ role: one desk, one name, so every later "you" is the agent You are Pip, the chat assistant for Tamsin Outdoor. # Constraints @@ brevity: a chat bubble past three sentences gets scrolled, not read - MUST keep every reply to at most 3 sentences. @@ empathy: one acknowledgement, not a paragraph of them - MUST acknowledge the customer's feelings once. @@ returns: the window is the fact most customers need - WHEN the customer asks about returns THEN MUST state a return window of 30 days. @@ returns_detail: the full conditions live on the policy page, not in the bubble - WHEN the customer asks for the full return conditions THEN MUST send a link to our returns policy. $TEST returns - input:: Can I send back a tent I bought last week? It leaks. - expect:: - states a return window of 30 days - at most 3 sentences $TEST full_conditions - input:: What are all the conditions for a return? I want the full list. - expect:: - sends a link to our returns policy - at most 3 sentences - matches /https?:\/\// $TEST upset - input:: This is the second jacket that has ripped. I am really fed up. - expect:: - acknowledges the customer's feelings - at most 3 sentences
full_conditions case is the one that used to produce the essay. It now has to answer in three sentences and contain a URL, and both of those are checked without a model.$ typeglish test pip.tg --dry ✓ pip.tg coverage: 4/4 rules exercised · returns - "Can I send back a tent I bought last week? It leaks." (not run) ✓ at most 3 sentences · rubric - 1 prose expectation, judged on a live run (needs ANTHROPIC_API_KEY) · full_conditions - "What are all the conditions for a return? I want the full li" (not run) ✓ at most 3 sentences ✓ matches /https?:\/\// · rubric - 1 prose expectation, judged on a live run (needs ANTHROPIC_API_KEY) · upset - "This is the second jacket that has ripped. I am really fed u" (not run) ✓ at most 3 sentences · rubric - 1 prose expectation, judged on a live run (needs ANTHROPIC_API_KEY) ✓ 1 prompt - 0 failed
typeglish review on this file reports A (100/100), nothing to fix, and 4/4 rules covered. The grade is the same as section 1. What changed is that the length is now a claim the checker can defend and a test can fail.Two habits keep it that way. When someone adds a content rule, ask what it costs in sentences and, if the answer is more than one, whether the detail belongs in the reply at all. And when someone wants the bound raised, raise it in one place, then run check: every counted rule that disagreed with the old number will now say so. The companion post on asking one question at a time uses the same kind of per-reply bound for a different budget.
§5Common questions
- Why are my AI support agent's replies so long?
- Usually because the length rule is the only rule in the prompt without a number. Every content rule (acknowledge the feeling, explain the conditions, give a thorough answer) adds sentences, and a word like concise claims nothing that any of them can be measured against. When the model has to satisfy all of them at once, the one rule with no edge is the one that gives.
- Does keep replies concise work in a system prompt?
- It reads as a rule and it scores.
MUST keep every reply concisesits besideMUST give the customer a thorough answerin a prompt that gradesA (100/100)with nothing flagged, because neither line commits to an amount. WriteMUST keep every reply to at most 3 sentencesinstead: the model gets an edge it can hit, a$TESTgets an assert it can count, and the checker gets a bound it can compare against every other count in the file. - Can TypeGlish tell me which rule makes my agent's replies too long?
- Only when that rule carries a count of its own. Next to
at most 3 sentences, a rule demandingat least 5 sentencesis a blockinglogic/numericerror. A rule that saysexplain the return conditionswith no number stays clean, because the checker cannot know how many sentences the conditions take. Put numbers on the rules that cost length, or move the detail out of the reply, and test the reply length directly. - How do I test that my agent's replies are short?
- Put a deterministic length assert in each
$TESTcase. An expect line ofat most 3 sentencesis checked without a model or an API key, andtypeglish test --dryvalidates it offline and reports which rules the suite covers. Add it to every case, not just the brevity case, because the reply that runs long is the one answering something else.
Length is the prompt property that every team downstream of the agent notices and nobody upstream owns. The brand team sees it in screenshots, the floor sees it in handle time, and the customer sees it as a scroll bar on a phone. Meanwhile each rule that made the reply longer came from a legitimate ask, reviewed on its own, in a pull request that did not touch the brevity line. That is why the fix is structural rather than editorial: one counted bound per reply, a count on anything that competes with it, and an assert in every test. After that, the next team's reasonable request arrives as a logic/numeric error in review, which is a much cheaper place to have the conversation than the CSAT dashboard. typeglish --explain logic/numeric is the place to start.