← Tidelines/Guides

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.

by TypeGlish team10 min read#guides
Say what you are.

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.

disclosure.tg - the version in the file today✓ B (82/100)
# 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.
tg check and score - disclosure.tg✗ nothing to report
$ 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)
Clean, and clean under --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.

tg check --strict - the plane boundary✗ B (83/100), clean
  # 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
The Role line publishes the fact to the model and the rule forbids stating it, and the toolchain has nothing to say, at a grade one point above the file in §1. The declaration is a binding, the prohibition is about an emission, and no proof in the compiler crosses between them. So do not write the prohibition. There is nothing to prohibit once the fact is stated: an agent whose Role block says it is an AI assistant has no denial to make.

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.

tg check and score - disclosure-flat.tg✗ F (62/100)
$ 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
Same verb, same object, no scope on either line, and it is a blocking error rather than a style note. The version in §1 escapes this because the 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.

tg check - always plus usually, one action✗ 3 error
  - 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
Three blocking errors for two lines, and the last sentence of the third one is the instruction: delete it, or give it a genuinely distinct scope. Which is the whole design problem. There is no distinct scope to give, because proactive and on request are not two conditions inside one conversation. They are two different deployments of the same agent.

§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.

the two failures a typed switch gives you for free✗ 1 error each
  # 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).
The second one is the reason to bother. The day a market is added where disclosure is handled by a banner instead of the agent, somebody adds a member to the domain and every switch that has not been updated stops compiling and names the member it cannot answer. On a compliance-relevant setting, that is the failure mode you want. The trade-off against a - 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.

disclosure-shipped.tg - one fact, one setting, one denial rule✓ A (90/100)
$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)/
tg build --vars - one prompt, two deployments✓ two artifacts
$ 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.
367 bytes and 393 bytes, two hashes, and neither artifact contains the other mode's wording anywhere. The quotes are gone, which is the point of them: they were instructions to the compiler about what not to parse, and the model gets the sentence compliance signed. Build with no --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.

tg test --dry and score - disclosure-shipped.tg✓ A (90/100)
$ 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)
Read 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. Add NEVER state that you are a human. outside the switch, because that one is not mode-dependent. That file is 0 error, 0 warning, 0 info at A (90/100), and it builds to a different artifact per mode: 1c259462a997 at 367 bytes for proactive, 783120381ece at 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. against ALWAYS say that you are a bot. is 2 blocking logic/contradiction errors reading say that you are a bot is both required and forbidden, at F (62/100) with consistency 0 and the build refused. Put a WHEN or an IF on either side and the same pair is 0 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 reading You are Ada, an AI chat support agent for Halloway Savings. with NEVER state that you are an AI. underneath it is clean at B (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_request declares the closed set, a $SWITCH ON block 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 blocking structure/opaque-switch, and the day somebody adds a third member the same switch fails with structure/non-exhaustive-switch naming it, which is the loud failure you want on a compliance-relevant setting.
Field note

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.

∿ washed up Sep 9, 2026 ∿