Your agent has two names
A rebrand, a template, and a persona block written by somebody else. Three names for one bot, in one file, and a green check, because most of the ways you can write an agent's identity never land in the same place.
TL;DR A possessive binds a property of its owner, so Your name is Dana writes to you.name and nothing else in the file writes there: Name IS Aria, You are Aria and The assistant's name is Aria all check at 0 error, 0 warning, 0 info beside it, while a second Your name is is 2 blocking logic/contradiction errors naming single-valued "you.name". The pointer is the test that answers: after Your name is Dana, @{you.name} compiles to Dana, and after You are Aria the same pointer stays clean and ships {you.name} to the model.
Nobody sits down and gives a support bot three names. What happens is that the bot gets one name at launch, marketing changes it eleven months later, and the change lands in the line whoever did it happened to be looking at. The other two lines are in a section nobody opens. All three ship, together, in the document the model reads, and the model picks.
The reason a review misses it is that the three lines do not look like the same claim. You are Aria reads as the role. Your name is Dana reads as the name. The assistant's name is Northbeam Assistant reads as the formal one for the disclosure. To a person that is three registers of one fact. To the compiler it is three facts about three different things, which is why none of them is ever compared with the others.
§1Three names, and a tick
Here is the top of a chat support prompt for a mobile network, in the state it reaches you after one rebrand.
# Role You are Aria, a customer support agent for Northbeam Mobile. # Persona Your name is Dana. The assistant's name is Northbeam Assistant. # Constraints - You MUST introduce yourself by name in your first reply. - You MUST keep every reply to at most 4 sentences.
✓ 1 file — 0 error, 0 warning, 0 info
check --strict has nothing to escalate because there is no finding to escalate.The score is the usual second opinion, and consistency is the facet whose whole job is measuring the rules against each other.
names.tg — B (84/100) proven errors: none tiers: base+z3
planes runtime 96 (what the model reads) · hygiene 50 (source only)
facets enforceability 85 x.21 · hardness 100 x.12 · directness 100 x.08 · consistency 100 x.17
structure 100 x.12 (hygiene) · annotation 0 x.12 (hygiene) · style 100 x.08 · security 100 x.08
lever annotation 0/100 (up to +12 overall) — Put a "@@ why" note directly above each
statement ("@@ name: why" also names it).
consistency 100 on a file with three names in it. The only complaint the score has is that nobody wrote any @@ notes. B (84/100) ships.And the build settles what the model gets, which is all three of them, side by side, in one paragraph.
$ npx typeglish build names.tg ✓ built .typeglish/dist/names.txt ← names.tg (eb63621adb81, full) # Role You are Aria, a customer support agent for Northbeam Mobile. # Persona Your name is Dana. The assistant's name is Northbeam Assistant. # Constraints - You MUST introduce yourself by name in your first reply. - You MUST keep every reply to at most 4 sentences.
§2The owner is the address
A possessive does not declare an object. It declares a property of an owner, and the pair is the address: Your name is Dana writes the value dana to the slot you.name. Anything that writes to a different slot is a different claim, and two different claims are not a conflict however much they look like one.
Six files, identical except for the line under Your name is Dana, each one a way somebody has actually written an agent's identity.
owner/possessive.tg:3:1 error logic/contradiction Conflicts with line 4. Contradiction — single-valued "you.name" is assigned 2 different values ("dana", "aria"). Keep one, or hold several with a list ("… is dana and aria"). owner/possessive.tg:4:1 error logic/contradiction Conflicts with line 3. Contradiction — single-valued "you.name" is assigned 2 different values ("dana", "aria"). Keep one, or hold several with a list ("… is dana and aria"). ✗ 6 files — 2 error, 0 warning, 0 info program: 6 independent files — no $IMPORT compositions
bare.tg says Name IS Aria. copula.tg says You are Aria. assistant.tg says The assistant's name is Aria. agent.tg says The agent's name is Aria. bot.tg says The bot IS Aria. None of the five appears above.Read that as a map rather than a scoreboard and it is coherent, which is the uncomfortable part.
- The same possessive on the same owner is caught, hard. Two
Your name islines are 2 blockinglogic/contradictionerrors, one report per participating line, and the message quotes the slot rather than either sentence:single-valued "you.name". The file is capped at F (50/100). This is the one shape of the bug that a review does not need, because the build refuses. - A bare declaration is a different object. Name IS Aria binds
name, full stop, with no owner in front of it. It is a perfectly good declaration and it has nothing to do with yours. - The role sentence is not a name. You are Aria is the most common way a persona gets written and it registers a claim about you, not a value in
you.name. That distinction is invisible in the prose and decisive in the file, and §3 is how you see it. - A third-person owner is a third party. The assistant's name, The agent's name and The bot each introduce an owner the file has not connected to you. Every one of them reads, to a person, as the same bot. Every one of them is a separate address, which is the same failure that makes half your rules about somebody else exempt from comparison with the other half.
Two claims are only in conflict if they were about the same thing. Grammar decides what the same thing is.
§3The pointer is the question that gets an answer
You cannot read a slot off the page, and you should not try. There is a mechanical way to ask what a line bound, and it takes one edit: point at it. A pointer resolves against what was actually declared, so it either inlines a value or it fails.
ptr/bare-name.tg:6:30 error structure/undefined-ref Dangling pointer — @{name} names nothing. Declare it with "$REQUIRE variable name" (a runtime variable), or "name IS …" (a fixed value). ✗ 4 files — 1 error, 0 warning, 0 info program: 4 independent files — no $IMPORT compositions
you-name.tg declares Your name is Dana and points at @{you.name}: clean. assistant-name.tg declares The assistant's name is Aria and points at @{assistant.name}: clean, and it is a different owner. bare-name.tg declares Your name is Dana and points at @{name}: dangling, because that slot was never written. copula-name.tg declares You are Aria and points at @{you.name}: also clean, and that one is the interesting result.Three of the four are what you would guess. The fourth is not, and the only place it is visible is the artifact.
$ npx typeglish build you-name.tg ✓ built .typeglish/dist/you-name.txt ← you-name.tg (d006f518b309, full) # Role You are a customer support agent for Northbeam Mobile. Your name is Dana. # Constraints - You MUST greet a caller as Dana in your first reply. $ npx typeglish build copula-name.tg ✓ built .typeglish/dist/copula-name.txt ← copula-name.tg (da16c0a7eac4, full) # Role You are a customer support agent for Northbeam Mobile. You are Aria. # Constraints - You MUST greet a caller as {you.name} in your first reply.
{you.name} in it, from a file that is 0 error, 0 warning, 0 info under check and unchanged under check --strict. You are Aria never wrote the slot, so the pointer has no compile-time target and lowers to a runtime hole for a host to fill. Nothing filled it.That is worth separating from the failure it resembles. The bare @{name} errors because no such symbol exists anywhere. @{you.name} does not error because you exists and .name is a property a host could reasonably supply at request time, which is the same benefit of the doubt every unbound value on the compile-time plane gets. The consequence is specific and worth writing down: a property pointer that resolves is proof the slot was written, and a property pointer that ships is proof it was not.
§4The other owner in the file
Halfway down the same prompt there is a second set of possessives, and they are the ones that decide what a customer is offered.
cust/balance.tg:5:1 error logic/contradiction Conflicts with line 5. Contradiction — single-valued "customer.account.balance" is assigned 2 different values ("40 pounds", "90 pounds"). Keep one, or hold several with a list ("… is 40 pounds and 90 pounds"). cust/balance.tg:5:1 error logic/measure Money conflict on customer — "exactly GBP 40" and "exactly GBP 90" cannot both hold. (with line 5) cust/balance.tg:5:1 error logic/measure Money conflict on customer.account.balance — "exactly GBP 40" and "exactly GBP 90" cannot both hold. (with line 5) cust/balance.tg:6:1 error logic/contradiction Conflicts with line 4. Contradiction — single-valued "customer.account.balance" is assigned 2 different values ("40 pounds", "90 pounds"). Keep one, or hold several with a list ("… is 40 pounds and 90 pounds"). cust/balance.tg:6:1 error logic/measure Money conflict on customer — "exactly GBP 40" and "exactly GBP 90" cannot both hold. (with line 4) cust/balance.tg:6:1 error logic/measure Money conflict on customer.account.balance — "exactly GBP 40" and "exactly GBP 90" cannot both hold. (with line 4) cust/tier.tg:5:1 error logic/contradiction Conflicts with line 5. Contradiction — "customer.tier" is assigned "gold" and "silver" — both name a metal, and a single-valued slot holds one. cust/tier.tg:6:1 error logic/contradiction Conflicts with line 4. Contradiction — "customer.tier" is assigned "gold" and "silver" — both name a metal, and a single-valued slot holds one. ✗ 3 files — 8 error, 0 warning, 0 info program: 3 independent files — no $IMPORT compositions
tier.tg is The customer's tier is gold beside The customer's tier is silver. balance.tg is the chained possessive, reported twice over at two depths. caller.tg is The customer's tier is gold beside The caller's tier is silver and does not appear here at all.Three things fall out of that report.
- The proof reaches the values, not just the slot. gold and silver are not two spellings of a symbol the checker is tracking; the message says both name a metal, and a single-valued slot holds one. It knows they are two members of one coordinate set and refuses to let a slot hold both.
- A chain is a slot at every depth. The customer's account's balance reports as
customer.account.balance, and the money conflict lands twice, once on the full path and once oncustomer. That is the possessive chain shipped in 0.2.3 doing exactly what it says: each link is addressable. - Renaming the owner switches the checker off. The customer's tier and The caller's tier are the same human being on the same contact, and they are two owners, so
caller.tgis 0 error, 0 warning, 0 info. If your prompt calls them the customer in one section and the caller in another, which most contact-centre prompts do, half your facts about that person are unreachable from the other half.
There is a bigger problem with that whole section, though, and it is not the vocabulary. The customer's tier is gold is a declaration: it asserts, for every conversation this prompt is ever used in, that the customer is gold. It is not wrong because it conflicts with the line under it. It is wrong because it is a per-conversation fact written as a permanent one, which is a premise the model reasons from rather than something it can check.
§5Bind it once, and let the host say the rest
So the rewrite splits the file's nouns by who owns them and how long the value lasts. The agent's name is fixed for every conversation, so it is a declaration, bound once, referenced by pointer. The customer's tier changes on every contact, so it is an input the host supplies and the compiler resolves.
<$CONFIG> $REQUIRE variable customer_tier: one of gold, silver </$CONFIG> # Role @@ identity: one owner, one property, so the next rebrand is a one-line diff Your name is Dana. @@ role: chat support for a mobile network You are a customer support agent for Northbeam Mobile. # Constraints @@ greeting: the pointer proves the name was bound, so it cannot reach the model as a hole - You MUST greet a caller as @{you.name} in your first reply. @@ brevity: four sentences keeps a chat reply scannable - You MUST keep every reply to at most 4 sentences. @@ tier_callback: entitlement is a fact about this caller, so the host supplies it and the compiler keeps one arm $IF @{customer_tier} is equal to gold: @@ gold: the callback is the paid-for part of the tier You MUST offer a callback within 15 minutes. $ELSE: @@ other: no tier below gold has a callback to promise You MUST NOT offer a callback. $TEST first_reply - input:: Hi, my broadband is down again. - expect:: - contains "Dana"
enforceability 93 named as the remaining lever. The rebrand that started this post is now one line, and the pointer means the greeting rule follows it.Then the builds are the part you can hand to somebody. Three of them, because the tier has two members and no binding at all is also a state your pipeline can be in.
$ npx typeglish build named.tg --vars '{"customer_tier":"gold"}' ✓ built .typeglish/dist/named.txt ← named.tg (2f21be9f0006, full) # Role Your name is Dana. You are a customer support agent for Northbeam Mobile. # Constraints - You MUST greet a caller as Dana in your first reply. - You MUST keep every reply to at most 4 sentences. You MUST offer a callback within 15 minutes. $ npx typeglish build named.tg --vars '{"customer_tier":"silver"}' ✓ built .typeglish/dist/named.txt ← named.tg (9f5e8afc6ee2, full) # Role Your name is Dana. You are a customer support agent for Northbeam Mobile. # Constraints - You MUST greet a caller as Dana in your first reply. - You MUST keep every reply to at most 4 sentences. You MUST NOT offer a callback. $ npx typeglish build named.tg ✓ built .typeglish/dist/named.txt ← named.tg (7b0fc3d95eb4, full) # Role Your name is Dana. You are a customer support agent for Northbeam Mobile. # Constraints - You MUST greet a caller as Dana in your first reply. - You MUST keep every reply to at most 4 sentences. If customer_tier is gold: You MUST offer a callback within 15 minutes. Otherwise: You MUST NOT offer a callback.
$ npx typeglish test named.tg --dry ✓ named.tg coverage: 0/4 rules exercised · first_reply — "Hi, my broadband is down again." (not run) ✓ contains "Dana" ✓ 1 prompt — 0 failed
coverage: 0/4 is honest and not a mistake, because a case is attributed to the rules it exercises and a greeting case exercises none of the four as written. It is still the one test worth having, because it is the only thing in the toolchain that reads the name the way a customer does.§6Common questions
- Why does my AI agent introduce itself with the wrong name?
- Because more than one line in the prompt claims the name and nothing compared them. A possessive binds a property of its owner, so Your name is Dana writes to
you.name, while You are Aria, Name IS Aria and The assistant's name is Aria each land somewhere else. Six files identical except for that second line separate cleanly: only a second Your name is collides, at 2 blockinglogic/contradictionerrors readingsingle-valued "you.name" is assigned 2 different values. The other five check at 0 error, 0 warning, 0 info, and the build ships every name in the file to the model in one document. - Does TypeGlish catch a system prompt that gives the agent two names?
- Only when both lines are possessives on the same owner. Your name is Dana beside Your name is Aria is 2 blocking
logic/contradictionerrors and the file is capped at F (50/100). Your name is Dana beside You are Aria, beside Name IS Aria, or beside The assistant's name is Aria is 0 error, 0 warning, 0 info at B (84/100) withconsistency 100, because a different owner is a different address and two claims at two addresses are not a conflict. The same rule runs on the customer: The customer's tier is gold beside The customer's tier is silver is caught, and beside The caller's tier is silver it is silent. - Should the agent's name be a declaration or a runtime variable?
- A declaration, because the name is the same in every conversation. Bind it once with Your name is Dana and reference it with
@{you.name}, and the pointer compiles the value into the rule so the model reads a name rather than a placeholder. Facts that change per conversation go the other way: a tier, a plan or a balance written as The customer's tier is gold is a claim about every caller, so declare it$REQUIRE variable customer_tier: one of gold, silverand let the host supply it. The compiler then keeps one arm and the two builds carry one rule each. - Why is @{you.name} showing up in my deployed prompt?
- Because nothing bound it at compile time. After Your name is Dana the pointer inlines the value and the artifact reads greet a caller as Dana. After You are Aria the same pointer checks at 0 error, 0 warning, 0 info under both
checkandcheck --strict, and the artifact carries the literal text{you.name}for a host to fill, because a property pointer with no compile-time target lowers to a runtime hole. The bare@{name}does error, at a blockingstructure/undefined-ref, which is why the pointer is the fastest way to find out what a line actually bound.
The habit this leaves you with is small and it costs nothing. When you write a fact about somebody into a prompt, write the owner down the same way every time, and then point at it once from a rule. The pointer is the whole trick: it is a one-character question that the compiler has to answer, and the answer tells you whether the rest of the file agrees with you about who owns what. Prompts drift on nouns long before they drift on rules, because a rule gets reviewed and a noun gets copied. Two names is the version of this you can laugh about in a standup. The customer's tier and the caller's tier, four sections apart, deciding different things about the same person, is the version that reaches a bill.