How to write the AI disclosure rule your agent will actually follow
Every support agent gets asked whether it is a person, and most prompts answer that with three lines that check clean and still hand the decision to the model. Here is the shape that does not.
TL;DR Write the disclosure as a declaration (You are an AI assistant.), the mode as a typed input ($REQUIRE variable disclosure_mode: one of proactive, on_request) resolved by a $SWITCH ON so the losing wording never reaches the model, and the customer-facing sentence as a quoted literal; keep exactly one unscoped rule, NEVER state that you are a human. That file is 0 error, 0 warning, 0 info at A (90/100) and builds to a different artifact per mode, while the usual three-line version is equally clean at B (82/100) and leaves both the trigger and the wording to be improvised on the turn.
Somewhere in the first four turns, a customer types are you a real person? - and in a regulated industry the answer to that is not a tone decision. Disclosure obligations now sit in a dozen statute books and about as many contact-center policies, they differ on whether the agent must volunteer it or only confirm it, and they land in your system prompt as two or three sentences somebody added the week before launch. This is a guide to the shape those sentences should have, and it is short because most of the work is deciding which plane each part belongs in.
§1The three lines everybody writes
Halloway Savings runs a chat support agent called Ada. Its prompt handles disclosure the way almost every prompt handles it: a prohibition, a conditional, and a hope.
# Role You are Ada, a chat support agent for Halloway Savings. # Constraints - NEVER say that you are a bot. - IF a customer asks whether they are talking to a person THEN tell them that you are an AI assistant. - MUST keep every response to at most 3 sentences. - ALWAYS ask for an account number.
$ typeglish check disclosure.tg ✓ 1 file - 0 error, 0 warning, 0 info $ typeglish check disclosure.tg --strict ✓ 1 file - 0 error, 0 warning, 0 info $ typeglish score disclosure.tg disclosure.tg - B (82/100) proven errors: none tiers: base+z3 planes runtime 92 (what the model reads) · hygiene 50 (source only) facets enforceability 77 x.21 · hardness 100 x.12 · directness 88 x.08 consistency 100 x.17 · structure 100 x.12 · annotation 0 x.12 style 100 x.08 · security 100 x.08 lever annotation 0/100 (up to +12 overall)
--strict. Read the two disclosure lines as a pair, though. One forbids the word bot; the other requires the words AI assistant, but only on a turn where a customer thought to ask. Nothing in the file says what Ada does on turn one, and nothing says what Ada does when the customer asks obliquely (is this a chatbot thing?). Both of those are now the model's call.The near-miss version of this is worse and it is very common: if a customer directly asks, you can mention that you are an AI. That sentence has a lowercase if with no THEN and a can where a modal belongs, which is to say it is a permission rather than an obligation, and a permission is a thing a model is free to decline. You said only. Your agent escalated anyway. is the long version of why; for a disclosure line the short version is that can is never the right word in a sentence a regulator might read.
§2Disclosure is a fact about the agent, not a sentence it says
Here is the move that fixes most of this. A disclosure rule is a rule about output: it fires on a condition, it produces a sentence, and it can be missed. A disclosure declaration is a fact about the addressee, and in this language a declaration both binds a name and renders as an instruction, so the fact is in front of the model on every single turn without anybody having written a rule about it.
Put it in the Role block. The disclosure is not something your agent does. It is something your agent is.
The reason to prefer the fact is that the rule plane and the declaration plane do not meet. Watch what a prompt gets away with.
# Role You are Ada, an AI chat support agent for Halloway Savings. # Constraints - NEVER state that you are an AI. - MUST keep every response to at most 3 sentences. $ typeglish check disclosure-blind.tg --strict ✓ 1 file - 0 error, 0 warning, 0 info $ typeglish score disclosure-blind.tg disclosure-blind.tg - B (83/100) proven errors: none tiers: base+z3 planes runtime 95 (what the model reads) · hygiene 50 (source only) facets enforceability 85 x.21 · hardness 100 x.12 · directness 88 x.08 consistency 100 x.17 · structure 100 x.12 · annotation 0 x.12
What the checker will hold you to is the rule plane on its own, and it is exact about it. Two rules with the same verb, neither of them scoped, is a proof.
$ typeglish check disclosure-flat.tg disclosure-flat.tg:5:1 error logic/contradiction Conflicts with line 5. Logical conflict - "say that you are a bot" is both required and forbidden. Keep one, or scope the two rules so they cannot both apply (IF <condition> THEN ...). disclosure-flat.tg:6:1 error logic/contradiction Conflicts with line 4. Logical conflict - "say that you are a bot" is both required and forbidden. Keep one, or scope the two rules so they cannot both apply (IF <condition> THEN ...). ✗ 1 file - 2 error, 0 warning, 0 info # exit 1, build refused $ typeglish score disclosure-flat.tg disclosure-flat.tg - F (62/100) proven errors - grade capped at F tiers: base+z3 facets enforceability 70 x.21 · hardness 100 x.12 · directness 75 x.08 consistency 0 x.17 · structure 100 x.12 · annotation 0 x.12
IF clause makes the two rules claims about different occasions, which is exactly what the message tells you to do when the conflict is deliberate.The other proof worth knowing about is the one that catches a prompt trying to have it both ways on frequency. A team that cannot decide between volunteering the disclosure and confirming it on request often writes both, at two strengths, one under the other.
- ALWAYS state that you are an AI assistant. - Usually state that you are an AI assistant. $ typeglish check disclosure-strength.tg disclosure-strength.tg:5:1 error logic/quantifier-strength Conflicts with line 5. One rule, two strengths - "state that you are an AI assistant." (required on every occasion) already entails "state that you are an AI assistant" (required on most occasions); a model may read the weaker as "not always". Keep the stronger line. disclosure-strength.tg:6:1 error logic/quantifier-strength Conflicts with line 4. One rule, two strengths - "state that you are an AI assistant." (required on every occasion) already entails "state that you are an AI assistant" (required on most occasions); a model may read the weaker as "not always". Keep the stronger line. disclosure-strength.tg:6:3 error logic/force-subsumption Conflicts with line 4. Subsumed on the occasion axis: the stronger rule already guarantees this one ("always" entails "usually" for the same action), so it adds nothing but waver. Delete it, or give it a genuinely distinct scope. ✗ 1 file - 3 error, 0 warning, 0 info
§3The mode is a setting, so give it a domain
Whether Ada opens with the disclosure or waits to be asked is decided by a jurisdiction, a channel, or a legal review, and it is decided before anybody starts a conversation. That makes it a compile-time input, and the point of typing it is that the two wordings then live in a table where the compiler can check that every case has an answer.
# untyped: $REQUIRE variable disclosure_mode disclosure-open.tg:7:1 error structure/opaque-switch @{disclosure_mode} has an open domain (untyped) - a $SWITCH on it needs a "- otherwise::" row (members can't cover an open domain). # a third member arrives: one of proactive, on_request, suppressed disclosure-third.tg:7:1 error structure/non-exhaustive-switch @{disclosure_mode} can be suppressed, but no arm handles it - add a "- <member>::" row for each (or a deliberate "- otherwise::" fallback).
- otherwise:: catch-all, which absorbs new members silently instead, is the subject of a catch-all arm vs. one arm per member.§4Quote the wording, then watch it strip
The disclosure sentence itself is not yours. It has been through compliance, and it must go to the model as the words that were approved rather than as a description of them. Straight double quotes are the escape hatch for that: inside them, nothing is parsed as an operator or a declaration, and the quotes themselves come off at compile time. Here is the whole file.
$REQUIRE variable disclosure_mode: one of proactive, on_request # Role @@ role: the addressee every rule below is about You are Ada, a chat support agent for Halloway Savings. @@ identity: the disclosure is a fact about you, not a sentence you sometimes remember to say You are an AI assistant. # Constraints @@ disclosure: one arm compiles in - the jurisdiction picks it, not the model $SWITCH ON @{disclosure_mode} - proactive:: ALWAYS open your first message with "I'm Ada, an AI assistant for Halloway Savings." - on_request:: WHEN a customer asks whether you are a person THEN reply with "I'm Ada, an AI assistant for Halloway Savings." @@ no_denial: the one rule that holds on every turn, in both modes - NEVER state that you are a human. @@ handover: a colleague taking over ends the disclosure, so name the change - WHEN you transfer a conversation to a colleague THEN state that a colleague is taking over. @@ brevity: three sentences keeps a chat reply scannable - MUST keep every response to at most 3 sentences. $TEST asks_if_human - input:: Am I talking to a real person? - expect:: - contains "AI assistant" - at most 3 sentences $TEST does_not_claim_human - input:: You sound human. Are you a human being? - expect:: - matches /^(?!.*I am a human)/
$ typeglish build disclosure-shipped.tg --vars '{"disclosure_mode":"proactive"}' ✓ built .typeglish/dist/disclosure-shipped.txt ← disclosure-shipped.tg (1c259462a997, full) # Role You are Ada, a chat support agent for Halloway Savings. You are an AI assistant. # Constraints ALWAYS open your first message with I'm Ada, an AI assistant for Halloway Savings. - NEVER state that you are a human. - WHEN you transfer a conversation to a colleague THEN state that a colleague is taking over. - MUST keep every response to at most 3 sentences. $ typeglish build disclosure-shipped.tg --vars '{"disclosure_mode":"on_request"}' ✓ built .typeglish/dist/disclosure-shipped.txt ← disclosure-shipped.tg (783120381ece, full) # Constraints WHEN a customer asks whether you are a person THEN reply with I'm Ada, an AI assistant for Halloway Savings. - NEVER state that you are a human.
--vars at all and you get the 543-byte template instead, carrying If disclosure_mode is proactive: ... If disclosure_mode is on_request: ... as conditional prose - useful for review, never the thing you deploy.One detail to notice in the artifact: the switch arm renders without its list bullet, as a bare line under # Constraints. The bullets in the source are list syntax the compiler reads, not punctuation the model needs, and an arm body is a statement rather than a member of a list.
§5What to assert, and what the score still cannot see
Two things are worth an offline test here, and they are not the same thing. One is that asking produces the disclosure. The other is that no wording ever produces a claim to be human, which is the assertion a negative lookahead exists for.
$ typeglish test disclosure-shipped.tg --dry ✓ disclosure-shipped.tg coverage: 0/2 rules exercised · asks_if_human - "Am I talking to a real person?" (not run) ✓ contains "AI assistant" ✓ at most 3 sentences · does_not_claim_human - "You sound human. Are you a human being?" (not run) ✓ matches /^(?!.*I am a human)/ ✓ 1 prompt - 0 failed $ typeglish score disclosure-shipped.tg disclosure-shipped.tg - A (90/100) proven errors: none tiers: base+z3 planes runtime 86 (what the model reads) · hygiene 100 (source only) facets enforceability 55 x.21 · hardness 100 x.12 · directness 88 x.08 consistency 100 x.17 · structure 100 x.12 · annotation 100 x.12 style 100 x.08 · security 100 x.08 lever enforceability 55/100 (up to +9 overall)
enforceability 55 as a description rather than a complaint. Only one rule in this file carries a number; the rest are speech acts, and state that you are a human has no bound to check. Read coverage: 0/2 the same way: an offline assert can prove the shape of a reply, and neither of these cases exercises a rule the checker can watch. That is the honest ceiling on a disclosure spec, and it is why the fact belongs in the declaration plane where nothing has to enforce it.The last thing to do is the one no command will do for you: read the artifact and count how many things it tells the model Ada is. A persona name, a role, and an identity are three separate claims, and a prompt assembled by three people usually has at least one of them contradicting the disclosure it is trying to make. Your agent has two names is the version of that problem where the claims are both about the name; disclosure is the version where one of them is about the species.
§6Common questions
- Should my AI agent tell customers it is an AI?
- Assume yes, and make it a fact rather than a behaviour. Write
You are an AI assistant.as a declaration in the Role block, where it binds and renders in one statement, so the disclosure is part of what the agent is instead of a sentence it has to remember to produce. Then keep exactly one rule about denial,NEVER state that you are a human., which holds on every turn regardless of what the customer asked. Whether the agent volunteers it or waits to be asked is a separate decision and it belongs in a typed input, not in a paragraph. - How do I write an AI disclosure rule in a system prompt?
- Three lines and one input. Declare the identity in the Role block (
You are an AI assistant.), declare the mode as a closed domain ($REQUIRE variable disclosure_mode: one of proactive, on_request), and put the two wordings in a$SWITCH ON @{disclosure_mode}block with one arm each, with the customer-facing sentence inside straight double quotes so the compiler treats it as copy and not as a rule. AddNEVER state that you are a human.outside the switch, because that one is not mode-dependent. That file is0 error, 0 warning, 0 infoatA (90/100), and it builds to a different artifact per mode:1c259462a997at 367 bytes for proactive,783120381eceat 393 bytes for on_request. - Does TypeGlish catch a prompt that both forbids and requires disclosure?
- Only when the two rules use the same verb and neither one is scoped.
NEVER say that you are a bot.againstALWAYS say that you are a bot.is 2 blockinglogic/contradictionerrors reading say that you are a bot is both required and forbidden, atF (62/100)withconsistency 0and the build refused. Put aWHENor anIFon either side and the same pair is0 error, 0 warning, 0 info, because a scoped rule and an unscoped rule are not claimed to hold on the same occasions. And nothing at all fires across the plane boundary: a Role line readingYou are Ada, an AI chat support agent for Halloway Savings.withNEVER state that you are an AI.underneath it is clean atB (83/100), even under--strict. - Should the disclosure be in every message or only when the customer asks?
- That is a jurisdiction and channel question, so stop answering it in prose and answer it in the type. A
$REQUIRE variable disclosure_mode: one of proactive, on_requestdeclares the closed set, a$SWITCH ONblock gives each member its own arm, and the losing arm never reaches the model, so the two wordings can never be weighed against each other at runtime. Coverage is proven by construction: drop the untyped version and the switch is a blockingstructure/opaque-switch, and the day somebody adds a third member the same switch fails withstructure/non-exhaustive-switchnaming it, which is the loud failure you want on a compliance-relevant setting.
The tell that a prompt has never had this conversation is a disclosure line written in the negative. Never claim to be human, do not say you are a bot, avoid discussing your nature: three prohibitions and no statement of fact, which is what you get when the sentences were added by somebody worried about a transcript rather than somebody deciding what the agent is. Prohibitions cost you twice. They put nothing in front of the model on the turn where the customer actually asks, and they are the shape most likely to collide with the rule somebody else adds next quarter, because two prohibitions about one action are exactly what the prover is built to find. Write the fact, keep one prohibition, and put the mode in the type. It reads shorter, it builds smaller, and the version of Ada in the on_request market cannot accidentally ship the proactive market's greeting, because that sentence is not in its artifact.