← Tidelines/Deep dives

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.

by TypeGlish team8 min read#deep-dives
One bot. Three names. One tick.

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.

names.tg - as inherited✗ B (84/100)
# 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.
Line 2 is the launch name. Line 5 is what the persona block still says, because the persona block was copied from the pilot bot and never revisited. Line 6 is the formal one legal asked for. The rule underneath tells the model to use one of them.
tg check names.tg - output
 1 file — 0 error, 0 warning, 0 info
Nothing. Not a warning, not an info, and 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.

tg score names.tg - output
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.

tg build names.tg - the artifact
$ 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.
Two of the three names end up on the same physical line of the artifact, which is the clearest possible picture of the problem and still produced no diagnostic. Introduce yourself by name now has three candidates and no tiebreak.

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

tg check owner/ - six ways to name a bot
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
Every file carries Your name is Dana. and one second line. One of the six is reported. 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 is lines are 2 blocking logic/contradiction errors, 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.

tg check ptr/ - four files, one pointer each
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
All four files carry the rule You MUST greet a caller as <pointer> in your first reply. 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.

tg build - the same pointer, two declarations
$ 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.
The second artifact reaches the model with {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.

tg check cust/ - three files about the person on the other end
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 on customer. 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.tg is 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.

named.tg - one owner per property✓ A (99/100)
<$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"
0 error, 0 warning, 0 info at A (99/100), runtime 98 and hygiene 100, with 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.

tg build named.tg --vars - three bindings
$ 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.
One name in all three artifacts, spelled out, with no pointer left in the text. Two hashes for the two bindings, and a third for the unbound template, which ships both callback rules as prose and is the state to fail your deploy on rather than the state to ship.
tg test named.tg --dry - output
$ 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
One assert, settled offline: the first reply has to contain the name. 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 blocking logic/contradiction errors reading single-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/contradiction errors 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) with consistency 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, silver and 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 check and check --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 blocking structure/undefined-ref, which is why the pointer is the fastest way to find out what a line actually bound.
Field note

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.

∿ washed up Aug 21, 2026 ∿