Nothing in your prompt ends the conversation
Every agent prompt has a beginning: a Role, a greeting, a first move. Almost none of them has an ending. The closing section is four rules about how to be polite on the way out, and not one of them says when the way out is.
TL;DR An ending is a rule with an observable trigger, and most closing sections do not contain one: MUST ask whether there is anything else you can help with is 0 error, 0 warning, 0 info at B (81/100), the highest-scoring closing line we checked and the one that guarantees another turn. Enumerate the endings as a closed set, give each member a trigger you could verify from a transcript, and the section goes to A (94/100) with the one real conflict gone.
Harbour Bank runs a chat agent over its help centre. The prompt is careful about the beginning: who the agent is, how it greets, what it may answer, when it escalates. Then there is a section at the bottom called Closing the conversation, written after the first month of transcripts showed the agent trailing off, and it contains four rules. Three of them are about tone. The fourth contradicts one of the others. None of them is an ending.
§1The section that closes nothing
# Role You are a chat support agent for Harbour Bank, a retail bank. # Answering - MUST answer a question about a current account. - MUST escalate a complaint to a human agent. # Closing the conversation - MUST ask whether there is anything else you can help with. - SHOULD close the conversation when it feels finished. - MUST close the conversation after 3 turns without a reply. - MUST NOT close the conversation until the customer confirms that the issue is resolved.
$ typeglish check closing.tg closing.tg:11:1 warn logic/only-gate Gated here, mandated there - the gate allows this action ONLY under "the customer confirms that the issue is resolved.", but this line orders it unconditionally, dropping the gate. Add the gate's condition here, or loosen the gate. closing.tg:12:1 info prompt/unintroduced-definite "the issue" retrieves something this document never introduces - a model must guess which issue is meant. closing.tg:12:1 info prompt/agentless-passive The doer is deleted (agentless passive) - who performs this? Name the actor or use the imperative. ✓ 1 file - 0 error, 1 warning, 2 info $ typeglish check closing.tg --strict ✗ 1 file - 1 error, 0 warning, 2 info $ typeglish score closing.tg closing.tg - C (72/100) proven errors: none tiers: base+z3+spell planes runtime 80 (what the model reads) · hygiene 50 (source only) facets enforceability 82 x.21 · hardness 92 x.12 · directness 83 x.08 consistency 57 x.17 · structure 100 x.12 · annotation 0 x.12 style 79 x.08 · security 100 x.08 lever annotation 0/100 (up to +12 overall)
logic/only-gate is the timeout walking straight through the confirmation gate. It is a warning by default and a blocking error under --strict, so a team with a strict gate in CI cannot merge this section. A team without one ships it and never learns.§2Seven ways to say it is over
One policy, seven spellings, one rule per file. This is how the same intention comes out of seven different sprint tickets, and what the checker makes of each.
the rule check score MUST end the conversation politely. 0 error, 1 info C (74/100) SHOULD close the conversation when it feels finished. 0 error, 1 info C (74/100) MUST wrap up when the customer is satisfied. 0 error, 1 info C (71/100) MUST close a conversation with a summary of what 0 error, 1 info C (71/100) was agreed. MUST close the conversation after 3 turns without 0 error, 1 info C (74/100) a reply. MUST NOT close the conversation until the customer 0 error, 1 info C (74/100) confirms. MUST ask whether there is anything else you can 0 error, 0 info B (81/100) help with. # and the only pair that draws a finding: MUST close the conversation after 3 turns without a reply. 1 warning C (65/100) MUST NOT close the conversation until the customer confirms.
prompt/unintroduced-definite on the conversation and prompt/agentless-passive on what was agreed, which is to say: grammar. The one line with no findings at all, scoring seven points above everything else at B (81/100), is the line that asks a question. A rule that ends with a question mark is a rule that requires a reply.Politeness at the exit is not an exit. Somewhere a rule has to say: this was the last turn.
§3What the checker can and cannot see
Be precise about the boundary here, because it matters more than the finding. TypeGlish proves conflicts between rules. It does not simulate a conversation, so it has no way to notice that a section describes a process with no terminating state. MUST ask whether there is anything else you can help with, alone in a file, is 0 error, 0 warning, 0 info, and it will stay that way no matter how long you stare at it.
What it does see is the pair. The gate and the mandate are two claims about one action, and when both are in scope the prover can show one of them is being dropped:
$ typeglish --explain logic/only-gate logic/only-gate (warn) An action is gated (`X only when C` / `never X until C` - C is necessary for X) yet mandated elsewhere without the condition: the gate is provably dropped where the two co-serve (THE PROCESS SCOPE). Add the condition to the mandate, or loosen the gate.
So the division of labour is: the loop is yours to notice, the inconsistency is the file's to report. That is the same split as everywhere else in this language, and the reason the reviewing habit matters as much as the tool. It is the maintenance property described in your prompt argues with itself, arriving one closing rule at a time.
§4Enumerate the endings
The fix is not a better adjective. It is to stop describing the feeling of an ending and start listing them. A conversation with this agent ends in exactly one of three states, and that sentence is a thing you can write down:
# Role You are a chat support agent for Harbour Bank, a retail bank. The %closing state% IS one of resolved, handed off, abandoned. The %closing state% IS resolved. The %closing state% IS abandoned.
$ typeglish check closing.tg closing.tg:4:1 error logic/redeclaration Conflicts with line 5. Mutually-exclusive values - this line asserts "resolved" while line 5 asserts "abandoned", and both belong to one closed "one of" set (an object holds exactly ONE member). Keep one assertion, or widen the declared set. closing.tg:5:1 error logic/redeclaration Conflicts with line 4. Mutually-exclusive values - this line asserts "abandoned" while line 4 asserts "resolved", and both belong to one closed "one of" set (an object holds exactly ONE member). ✗ 1 file - 2 error, 0 warning, 2 info
one of set is a real constraint, not a comment. Once the three endings are declared, a fourth one added next quarter by somebody who did not read the file is a compile error rather than a fourth opinion. The quantifier machinery that makes that work is the same one in gold and silver conflict, priority and standard do not.Then one rule per member, and the test each trigger has to pass: could a person reading the transcript say whether it fired? The customer answers no, yes. You escalate a complaint, yes. Ten minutes pass without a reply, yes. It feels finished, no, and that is the whole reason it was never an ending.
§5What ships
<$CONFIG> $IMPORT tool close_conversation </$CONFIG> # Role @@ role: the desk this file speaks for You are a chat support agent for Harbour Bank, a retail bank. @@ states: the three ways this conversation is allowed to end - a fourth one is a compile error The %closing state% IS one of resolved, handed off, abandoned. # Closing the conversation @@ offer: the offer of more help fires once, so it cannot be the thing that keeps the turn alive - MUST ask once whether there is anything else you can help with. @@ resolved: the customer's no is the trigger, not a feeling about the conversation - MUST close a conversation with @[close_conversation] when the customer answers no. @@ handed_off: an escalation ends this conversation too - the human owns the next turn - MUST close a conversation with @[close_conversation] when you escalate a complaint. @@ abandoned: a measurable silence, so the timeout is a rule and not a runtime accident - MUST close a conversation with @[close_conversation] after 10 minutes without a reply. $TEST closes_on_no - input:: No, that is everything, thanks. - expect:: - at most 2 sentences
$ typeglish check closing.tg ✓ 1 file - 0 error, 0 warning, 0 info $ typeglish score closing.tg closing.tg - A (94/100) proven errors: none tiers: base+z3+spell planes runtime 92 (what the model reads) · hygiene 100 (source only) facets enforceability 70 x.21 · hardness 100 x.12 · directness 100 x.08 consistency 100 x.17 · structure 100 x.12 · annotation 100 x.12 style 100 x.08 · security 100 x.08 lever enforceability 70/100 (up to +6 overall) $ typeglish test closing.tg --dry ✓ closing.tg coverage: 0/4 rules exercised · closes_on_no - "No, that is everything, thanks." (not run) ✓ at most 2 sentences ✓ 1 prompt - 0 failed $ typeglish build closing.tg ✓ built .typeglish/dist/closing.txt ← closing.tg (f6e4deac6ccc, full)
coverage: 0/4 rules exercised is the remaining debt and it is honest. One test, and the assertion it makes is about length, not about whether the conversation actually ended.Four lines in, four lines out, and the difference is entirely in what the triggers are made of. The artifact says so too:
# Role You are a chat support agent for Harbour Bank, a retail bank. The closing state IS one of resolved, handed off, abandoned. # Closing the conversation - MUST ask once whether there is anything else you can help with. - MUST close a conversation with close_conversation when the customer answers no. - MUST close a conversation with close_conversation when you escalate a complaint. - MUST close a conversation with close_conversation after 10 minutes without a reply.
§6Common questions
- Why does my AI support agent keep the conversation going?
- Because the only unconditional rule in most closing sections is the one that produces another turn.
MUST ask whether there is anything else you can help withis0 error, 0 warning, 0 infoatB (81/100), the highest score of any closing line we checked, and it has no terminating condition attached to it. Every other rule in the section is scoped to something the model has to judge. Add once to the offer, and give each way out its own rule with a trigger you could check from a transcript. - How do I tell an AI agent when to end a chat?
- Enumerate the endings instead of describing the feeling of one. Declare the closed set with
The %closing state% IS one of resolved, handed off, abandoned, then write oneMUSTrule per member whose trigger is observable: the customer answers no, you escalate a complaint, ten minutes pass without a reply. The set being closed is load-bearing, because asserting two members is a blockinglogic/redeclarationerror reading an object holds exactly ONE member. - Is a timeout rule enough to close a conversation?
- Not beside a confirmation gate, and that pair is the one defect in this section a checker will actually find.
MUST close the conversation after 3 turns without a replybesideMUST NOT close the conversation until the customer confirmsis alogic/only-gatewarning reading the gate allows this action ONLY under that condition, but this line orders it unconditionally, dropping the gate. It escalates to a blocking error under--strict. Either the timeout carries the gate's condition, or the gate was never a gate. - Does TypeGlish catch a prompt that never terminates?
- No, and it is worth being precise about that. The checker proves conflicts between rules, not properties of a conversation over time, so an offer of more help with no stopping condition draws nothing at all. What it catches is the pair that contradicts: the gate dropped by the timeout is 1 warning, and the closed set of endings makes a fourth ending a blocking error. The loop is something you have to notice. The inconsistency is something the file can tell you about.
The reason this section is the last one written and the worst one written is that nobody owns the end of a conversation. Support ops owns containment, and containment is measured on the conversations that never reached a human, which is a statement about the middle. QA owns tone, and tone is easiest to grade at the greeting. Product owns the CSAT prompt, which fires after the end and therefore assumes one. So the ending arrives as an afterthought, in adjectives, written by whoever noticed the transcripts trailing off. It is worth asking, once, who on your team could answer the question what are the three ways this conversation is allowed to finish. If the answer is nobody, that is not a prompt problem yet. It becomes one the moment somebody writes the section anyway.