← Tidelines/Best practices

Why your agent promises the refund instead of issuing it

The transcript reads fine. Your refund has been approved and will be processed within five working days. Nothing was processed, no tool was called, and the agent was following the prompt exactly: the rule it read describes a refund happening, and never says who makes it happen.

by TypeGlish team8 min read#best-practices
Nobody in this rule acts.

TL;DR A rule with no subject gives the agent nothing to execute, so it narrates the outcome instead: TypeGlish flags the two mechanical shapes as prompt/agentless-passive and prompt/nominalized-process, and the third shape (It is important to ...) produces zero diagnostics while dropping directness from 50 to 15 and enforceability from 100 to 50 on the same instruction. Rewriting five policy sentences as five MUST <verb> rules moved one support prompt from D (54/100) to A (90/100).

Every contact-centre prompt has a Constraints section, and a good share of those sections were pasted from somewhere: a refunds policy, a QA rubric, the page of the ops wiki that describes what is supposed to happen. Those documents are written for humans who already know they are the ones doing it. They describe a process. An agent prompt has to assign one.

§1A prompt with nobody in it

Here is the refunds half of a broadband support prompt. Five rules, all of them true, none of them wrong about policy, and it has a tool wired up so the agent can actually move money.

doer-before.tg - the policy, pasted✓ compiles
<$CONFIG>
  $IMPORT tool issue_refund
</$CONFIG>

# Role
You are a support agent for Northwind Broadband.

# Constraints
- A refund must be approved by a supervisor.
- It is important to keep the customer informed throughout.
- A review of the account notes should take place at the start of every contact.
- Escalation to the retention team is required when a customer asks to cancel.
- Every chat reply must be kept to at most 4 sentences.
Read it as a person and it is clear. Read it as an instruction set and the question who does this has no answer on four lines out of five.

Say each rule out loud with the words and I am the one doing it on the end. A refund must be approved by a supervisor, and I am the one doing it is not a sentence. Neither is the review, nor the escalation. Only the last rule, the one about reply length, actually lands on the agent, and it lands by accident: every chat reply must be kept to at most 4 sentences is about the replies, not about who writes them.

This is the whole failure mode. A model given a description of a process will produce a description of the process. It says the refund will be processed, because that is the register of the text it was handed.

tg check doer-before.tg - output
doer-before.tg:2:3  warn   structure/unused-import  Imported tool "issue_refund" is never used.
doer-before.tg:9:1  info   prompt/agentless-passive  The agent survives only in a by-phrase,
  demoted from the subject seat. Prefer the active: put the doer first.
doer-before.tg:11:1  warn   prompt/nominalized-process  The action is packed into a noun and
  nobody performs it. Use the verb itself with a doer: "Review each refund." instead of
  "A review must take place."
doer-before.tg:11:1  info   prompt/unintroduced-definite  "the account" retrieves something this
  document never introduces - a model must guess which account is meant. ...
doer-before.tg:12:1  warn   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."
doer-before.tg:12:1  info   prompt/unintroduced-definite  "the retention team" retrieves something
  this document never introduces - a model must guess which team is meant. ...
doer-before.tg:13:1  warn   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."

 1 file - 0 error, 4 warning, 3 info
Three doer findings on three different lines, and the language distinguishes them: line 9 keeps its actor in a by-phrase and is info, lines 12 and 13 deleted the actor entirely and are warn, line 11 buried the action in the noun review and gets its own code.

The severity split is the interesting part. A refund must be approved by a supervisor still contains a supervisor, so the checker calls it a demotion rather than a deletion and files it as info: a reader can recover the actor. Escalation to the retention team is required contains nobody at all, and that is a warning. The grammar is doing exactly the work you would want a reviewer to do, and doing it on every line, every commit.

§2The line the checker cannot see

Count the lines in that output and one rule is missing. It is important to keep the customer informed throughout produced nothing. No warning, no info, and nothing under --strict either. It is the emptiest rule in the file and it is the only one the diagnostic list does not mention.

That is worth isolating, because it is the shape people reach for when they want a rule to sound serious. Two files, one rule each, the same instruction:

active-one.tg and expletive-one.tg - the same instruction, twice
# Role
You are a support agent for Northwind Broadband.

# Constraints
- MUST escalate every complaint to the retention team.
And the same line rewritten as - It is important to escalate every complaint to the retention team. Both files check at 0 error and 0 warning. The scores are not close.
tg score - both files
active-one.tg - B (80/100)  proven errors: none  tiers: base+z3
  planes  runtime 90 (what the model reads) · hygiene 50 (source only)
  facets  enforceability 100 x.21 · hardness 100 x.12 · directness 50 x.08
          consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 0 x.12 (hygiene)
          style 63 x.08 · security 100 x.08

expletive-one.tg - C (70/100)  proven errors: none  tiers: base+z3
  planes  runtime 77 (what the model reads) · hygiene 50 (source only)
  facets  enforceability 50 x.21 · hardness 100 x.12 · directness 15 x.08
          consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 0 x.12 (hygiene)
          style 100 x.08 · security 100 x.08
Ten points of grade for one grammatical frame. Directness 50 to 15, enforceability 100 to 50, and style actually goes up, because there is nothing wordy or hedged about the sentence. It is a perfectly written line that instructs nobody.

There is a smaller detail in the same pair that says more about the mechanism than the numbers do. The active version earns one prompt/unintroduced-definite info, because the retention team is a definite reference to something the file never introduced. The expletive version earns nothing at all, on a line containing the same words. The checker did not decide that reference was fine. It never got far enough to have an opinion, because It is important to ... never registered an action for the reference to attach to.

Deleting the doer does not make the rule vague. It removes the rule from the plane where rules are checked.

So the practical rule for the gate: a clean check proves the absence of provable defects, and this family is mostly not provable. typeglish score --min B is the line of CI that catches it, because directness and enforceability are exactly the facets that ask whether a statement tells somebody to do something.

§3The tool nobody calls

One finding in that first run was not about grammar at all, and it is the loudest thing in the file: structure/unused-import on issue_refund. The tool is declared, the integration exists, the schema is right, and no rule in the prompt ever reaches it.

Of course it does not. A tool call is an action, an action needs a verb, and a verb needs a subject. Four of the five rules deleted theirs. The unused import is not a separate mistake, it is the shadow the missing doer casts, and it is the one finding in this whole family that a CI gate will stop.

tg check doer-before.tg --strict - output✗ blocked
doer-before.tg:2:3  error  structure/unused-import  Imported tool "issue_refund" is never used.
doer-before.tg:9:1  info   prompt/agentless-passive  The agent survives only in a by-phrase ...
doer-before.tg:11:1  warn   prompt/nominalized-process  The action is packed into a noun ...
doer-before.tg:12:1  warn   prompt/agentless-passive  The doer is deleted (agentless passive) ...
doer-before.tg:13:1  warn   prompt/agentless-passive  The doer is deleted (agentless passive) ...

 1 file - 1 error, 3 warning, 3 info
--strict escalates every IMPORTANT finding, and exactly one of these five qualifies. The three doer warnings stay warnings. If your pipeline runs check --strict and nothing else, the only version of this bug it will ever stop is the version where you happened to import a tool.

Which is a useful accident, and worth leaning on deliberately. An agent prompt whose imports are all used is a prompt in which something, somewhere, tells the agent to act. We wrote up the reverse case in why your agent won't use the tool you gave it: there the tool was mentioned but never pointed at, here it is not mentioned at all, and both end at the same place, with an integration your platform bill is paying for and your transcripts never touch.

None of this is resolved at build time either. The prose goes to the model exactly as written:

tg build doer-before.tg - the artifact
 built .typeglish/dist/doer-before.txt ← doer-before.tg (22f75a33e195, full)

# Role
You are a support agent for Northwind Broadband.

# Constraints
- A refund must be approved by a supervisor.
- It is important to keep the customer informed throughout.
- A review of the account notes should take place at the start of every contact.
- Escalation to the retention team is required when a customer asks to cancel.
- Every chat reply must be kept to at most 4 sentences.
The $CONFIG block strips and the five sentences ship byte for byte. Whatever the tool registry says the agent can do, this is the document that decides whether it does anything.

§4Put a doer in every rule

The rewrite is mechanical and it is not a style pass. Take each sentence, ask who performs it, and if the answer is the agent, make the agent the subject. If the answer is somebody else, the rule is not an instruction and it needs to become one: a refund must be approved by a supervisor is a fact about your org chart, and the instruction hiding inside it is ask a supervisor.

That question also finds the missing rule. Approval and payment are two actions in the policy sentence and one event in the reader's head, which is exactly why the tool went uncalled. Written out, they are two lines.

doer-after.tg - every rule has a subject✓ A (90/100)
<$CONFIG>
  $IMPORT tool issue_refund
</$CONFIG>

# Role
You are a support agent for Northwind Broadband.

# Constraints
@@ refund_authority: agents can approve up to the desk limit, above it a supervisor signs
- MUST ask a supervisor to approve a refund when the amount is over 50 dollars.
@@ refund_action: the approval is not the refund, somebody still has to move the money
- MUST call @[issue_refund] when a supervisor approves a refund.
@@ progress_updates: silence on a long lookup reads as a dropped chat
- MUST tell the customer what you are doing when a lookup takes over 30 seconds.
@@ notes_first: the last agent already asked the questions you are about to ask
- MUST read the account notes before you answer the first question.
@@ retention_path: cancellations are the one contact type with a human queue behind them
- MUST escalate to the retention team when a customer asks to cancel.
@@ brevity: chat replies over 4 sentences stop being read
- MUST keep every chat reply to at most 4 sentences.

$TEST refund_reaches_the_tool
  - input:: My supervisor already approved a 80 dollar refund on order 4412.
  - expect::
    - at most 4 sentences
$TEST cancel_goes_to_retention
  - input:: I want to cancel my broadband today.
  - expect::
    - at most 4 sentences
Five sentences became six rules, and the sixth is the one the policy document assumed. Note what did not change: no new policy, no firmer tone, no added qualifiers. Every rule now starts with a modal and a verb the agent performs.
tg score and tg test --dry - output
doer-after.tg - A (90/100)  proven errors: none  tiers: base+z3
  planes  runtime 90 (what the model reads) · hygiene 93 (source only)
  facets  enforceability 85 x.21 · hardness 100 x.12 · directness 75 x.08
          consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 86 x.12 (hygiene)
          style 68 x.08 · security 100 x.08
  lever   enforceability 85/100 (up to +3 overall) - Write rules as MUST / NEVER <verb> ... with
          concrete bounds ("at most 3 sentences"), not vague qualities.

 doer-after.tg  coverage: 2/6 rules exercised
  · refund_reaches_the_tool - "My supervisor already approved a 80 dollar refund on order 4" (not run)
       at most 4 sentences
  · cancel_goes_to_retention - "I want to cancel my broadband today." (not run)
       at most 4 sentences
D (54/100) to A (90/100). The two facets carrying it are the two the missing doer was suppressing: directness 23 to 75 and enforceability 50 to 85. structure/unused-import is gone, not because it was silenced but because a rule now calls the tool.

The score is the smaller half of the result. The larger half is that six rules with subjects are six things a $TEST can be pointed at, and coverage can report on. Escalation to the retention team is required was never testable, because there was no actor whose behaviour a case could observe.

§5The sentences to grep for

You do not need the compiler to start on an existing prompt. Four patterns account for nearly all of it, and three of them are literally searchable:

  • It is important / essential / critical to ... - the dummy subject. Delete the frame, keep the verb, add a modal. This is the one no diagnostic will find for you.
  • ... is required / is expected / should be ... - the deleted actor. Ask who, and put them in front.
  • A review / a check / verification of ... takes place - the action packed into a noun. The verb is right there inside the noun; use it.
  • ... must be approved by a supervisor - the demoted actor, surviving in a by-phrase. Softest of the four, and still worth flipping, because the rule is usually not about the supervisor at all. It is about what the agent does next.

The reason this concentrates in pasted text is that policy documents genuinely should be written that way. A refunds policy is not addressed to anybody in particular, which is what makes it a policy. The policy doc somebody pasted into your agent arrives with that voice intact, and the paste is a translation job nobody bills for: from a document that describes what happens, into a document that tells one party what to do.

§6Common questions

Why does my agent say it will do something instead of doing it?
Because the rule it is following does not say who acts. A refund must be approved by a supervisor names an event with no agent in it, so the model has nothing to execute and reports the event instead. The fix is not a firmer modal, it is a subject: MUST ask a supervisor to approve a refund tells the agent what to do, and MUST call the refund tool when a supervisor approves tells it that approval and payment are two separate actions. TypeGlish flags the deleted doer as prompt/agentless-passive and the noun-packed action as prompt/nominalized-process.
Should I avoid passive voice in a system prompt?
Avoid the agentless passive specifically, which is the passive with no by-phrase at all. The problem is not the grammar, it is the missing subject: Escalation to the retention team is required when a customer asks to cancel does not say that the agent escalates, so it reads equally well as background about how the company works. A passive that keeps its doer is far less dangerous, and TypeGlish grades it that way, reporting the by-phrase form as info rather than warn.
Does the checker catch every rule with no doer in it?
No, and the gap is worth knowing. It is important to keep the customer informed throughout produces zero diagnostics on typeglish check, including under --strict. The score is where it shows: that single line as a whole file scores C (70/100) with directness 15 and enforceability 50, against B (80/100) with directness 50 and enforceability 100 for the same instruction written as MUST. Gate on typeglish score --min B, not on the diagnostic list alone.
Why is my agent tool never called?
Check whether any rule contains a verb whose subject is the agent. A tool is wired up by a rule that tells the agent to call it, so a Constraints section written entirely in policy prose imports a tool that nothing reaches. TypeGlish reports this as structure/unused-import, and it is the one finding in this family that typeglish check --strict escalates to a blocking error, which makes it the cheapest CI signal that your prompt describes work rather than assigning it.
Field note

The doer family is the reason a brief and a prompt are different documents, which is the thing we kept running into while building a cancellation-save agent, prompt-first: typed straight in, the head of CX's five sentences landed on prompt/agentless-passive at the cancellation should always be honoured in the end, a line everybody in the room agreed with and nobody could execute. Briefs describe outcomes. Prompts assign actions. typeglish --explain prompt/agentless-passive is a fast way to see which of yours is which.

∿ washed up Aug 13, 2026 ∿