How to keep your AI support agent on topic
Support agents drift off topic for one structural reason: a prompt with no scope treats everything as fair game. The fix is not a longer list of things to refuse. It is declaring what the agent covers, so everything else is out by default.
TL;DR To keep an agent on topic, don't list what to refuse, declare what it covers. Scope it with an allowlist (ONLY answer questions about ...), give the off-topic case a concrete THEN action, and pin a refusal with a $TEST. A blocklist leaves everything you forgot to ban wide open.
Every team that ships a support agent eventually watches it cheerfully answer something it had no business touching: a tax question, a recipe, a competitor comparison, a request to write someone's cover letter. The reflex is to add a rule: never discuss X. Then X happens again in a new costume, and you add another. You are playing whack-a-mole against an infinite board, and the board always wins. The problem is not that you missed a topic. It is that the prompt's default is "answer," and you are trying to fix a default with a list.
§1Why "stay on topic" does nothing
The first thing most prompts say about scope is some version of stay on topic and don't answer unrelated questions. It feels like a rule. It compiles without complaint. And it constrains almost nothing, because "on topic" and "unrelated" are defined nowhere the model can point to. On topic according to whom? Related to what? The sentence outsources the entire decision back to the model on every turn, which is exactly the decision you were trying to make for it. A rule the checker cannot parse is a rule it cannot help you keep, and neither can the model.
So the goal of this guide is to replace one vague sentence with a small structure that actually pins the scope: an allowlist, an off-topic action, and a refusal the model has seen. Three pieces, each of which the checker can hold.
§2Invert the default: allowlist, not blocklist
A blocklist is the natural first draft, and it looks responsible. Here is one that compiles perfectly.
# Role You are a support agent for Acme, a subscription-box service. # Constraints - NEVER discuss competitors. - NEVER give legal or financial advice. - NEVER discuss unreleased products.
The allowlist inverts the default. Instead of enumerating the forbidden, which is infinite, you name the covered, which is finite, and let everything else fall out of scope automatically. The ONLY quantifier is the tool: it closes the set of things the agent will answer to exactly what you list.
You cannot enumerate everything to refuse. You can enumerate what you cover.
This is the same lesson a closed-set $SWITCH teaches in the multichannel bake-off: name the finite thing and the infinite complement takes care of itself. A blocklist asks you to be vigilant forever; an allowlist asks you to be complete once.
§3Give "off topic" a THEN
An allowlist decides what is in scope. It does not, by itself, say what to do with everything else, and "decline" is not as obvious to a model as it is to you. The instinct is to write a condition for the off-topic case and trust the rest to be understood. TypeGlish will stop you, because a condition with no consequence is not an instruction.
# Role You are a support agent for Acme, a subscription-box service. # Scope - WHEN a question is not about Acme.
scope.tg:5:1 warn typeglish/if-then WHEN needs a THEN - write WHEN <condition> THEN <action>. ✓ 1 file - 0 error, 1 warning, 0 info
Put the three pieces together and the scope section reads like a decision, not a hope. The allowlist sets the default, a finished WHEN ... THEN handles the miss with a bounded refusal, and a short NEVER list guards the topics no support agent should touch no matter how they are phrased.
# Role @@ role: an Acme support agent, scoped to its own product You are a support agent for Acme, a subscription-box service. # Scope @@ scope: name what the agent covers, so everything else is out by default - ONLY answer questions about Acme orders, billing, shipping, and account settings. @@ offtopic: give the off-topic case a concrete action, not just a prohibition - WHEN a question is outside that scope, THEN decline in at most 2 sentences and link the help center. @@ sensitive: a hard stop for topics no support agent should touch - NEVER give medical, legal, or financial advice. # Examples $EXAMPLE off_topic - input:: What stock should I buy with my refund? - good:: I can only help with Acme orders and account questions, so I can't advise on investments. Here is our help center: https://help.acme.com $TEST declines_off_topic - input:: Can you write my history essay? - expect:: - declines the request - contains "help"
THEN is the action, and the NEVER list is the floor.§4Show the refusal, then pin it
The last two blocks above are the part builders skip, and the part that decides whether scope holds under pressure. A refusal is a tone problem as much as a policy one: an agent that declines curtly reads as rude, one that over-apologizes invites a second attempt. So do not describe the refusal, demonstrate it. An $EXAMPLE puts one ideal decline in front of the model as a real exchange, which anchors the register better than any adjective. Then a $TEST turns the behavior into something CI can check: the off-topic input should be declined and should hand the user somewhere useful, and contains "help" is a deterministic assert that runs offline.
That combination, an example the model reads plus a test the pipeline enforces, is what makes the scope a property of the build rather than a preference of whoever last edited the file. It is the same move the escalation guide makes for handoffs: state the behavior once, then pin it so a future edit cannot quietly delete it.
Scope is not a warning you add. It is a default you choose.
None of this makes an agent refuse things it should answer, which is the real fear behind a loose scope. An allowlist that names your actual coverage plus a bounded, friendly decline is stricter and kinder than a wall of prohibitions: the customer gets a fast, clear "not my area, here is where to go" instead of a confident wrong answer about their taxes.
§5Common questions
- Why does my AI agent answer questions it shouldn't?
- Because a prompt with no scope has an open default: anything you did not forbid is fair game, and you cannot enumerate the whole internet of off-topic questions. Declare what the agent covers with an allowlist (ONLY answer questions about X) so everything else is out of scope by default.
- Is an allowlist or a blocklist better for keeping an agent on topic?
- An allowlist. A blocklist of NEVER rules only blocks the topics you thought to list, and it grows every time the world adds one you forgot. An allowlist names the finite set the agent handles and declines the rest, so the default is safe rather than open.
- How should an agent respond to an out-of-scope question?
- Give the off-topic case a concrete action, not just a prohibition: WHEN a question is outside scope, THEN decline in at most 2 sentences and point to the help center. Write the refusal as an $EXAMPLE so the model sees the exact tone, and note that a bare WHEN with no THEN is a warning (typeglish/if-then) precisely because a condition is not an instruction.
- Does a scoped prompt still need a hard block for sensitive topics?
- Yes. Keep a short NEVER list for topics no support agent should touch (medical, legal, and financial advice) on top of the allowlist. The allowlist sets the safe default; the NEVER rules are the non-negotiable floor beneath it.
The typeglish/if-then check that flags a condition with no consequence, and the $EXAMPLE and $TEST blocks that pin a refusal, all ship in the core toolchain from TypeGlish 0.1.0. For the broader habit of writing rules a model can actually follow, one meaning at a time, see say what you mean: eight prompting rules that survive production.