> Markdown rendition of https://typeglish.dev/blog/upsell-line-rewrite ("Rewrite: mention Premium before you go - Tidelines"). Canonical page: https://typeglish.dev/blog/upsell-line-rewrite · All pages: https://typeglish.dev/llms.txt

[← Tidelines](https://typeglish.dev/blog)/ Best practices Oct 1, 2026

# Rewrite: mention Premium before you go

Somewhere below the support rules, every subscription prompt grows an Offers section: mention the upgrade, but not to upset people, and only to the right plan. It checks clean, and it pitches Premium to customers who already pay for it.

by **TypeGlish team** 7 min read #best-practices

Who gets the pitch.

**TL;DR** An upsell line in a support prompt needs a moment and an audience the agent does not have to guess: pass the customer’s plan in as a typed `$REQUIRE` input so the offer compiles out for everyone it does not suit, tie it to an observable event like a confirmed fix, and pin the complaint case with a `$TEST`.

Nobody sets out to build a support agent that sells. The Offers section arrives one line at a time, usually from the growth team, usually with a perfectly reasonable request attached: we have a better plan, the agent talks to thousands of customers a day, could it mention it?

## §1 The before

Quillon TV is a streaming service with three plans: Basic, Standard and Premium. Its chat agent handles playback problems, billing questions and cancellations. Here is the Offers section as it ships, six lines written by four people over two quarters.

upsell.tg - the Offers section as it ships ✗ 0 error, 1 warning, 6 info

```typeglish
# Role
You are the chat support agent for Quillon TV, a streaming subscription service.

# Offers
- ALWAYS mention Quillon Premium before ending the chat.
- IF the customer is upset THEN NEVER mention Quillon Premium.
- Try to upsell where appropriate.
- Only offer Premium to customers on the Basic plan.
- MUST tell the customer that Premium has no ads and 4K.
- NEVER be pushy.
```

Mention it every time, except to upset people. Upsell where appropriate. Only to Basic. Sell the features. Do not be pushy. Each line is a reasonable sentence. Together they describe a salesperson who has to read the customer’s mood and their account, neither of which it can see.

tg check, score and build - upsell.tg ✗ C (68/100), builds

```
$ typeglish check upsell.tg
upsell.tg:7:3  info   prompt/hedging  Hedging - "Try to" turns this instruction into a suggestion the model may skip. Delete the hedge, or commit to a modal (MUST / NEVER / SHOULD).
upsell.tg:7:17  info   prompt/vague  Vague - "where appropriate" names a judgment call without the criterion to judge by, so nothing can check it. Replace it with a measurable form ("at most 3 sentences", "within 24 hours", "cite the source").
upsell.tg:7:1  info   prompt/unparsed-rule  Reads like a rule, but the rule reader cannot parse it as one, so it earns no Enforceability credit and no measurability check: no modal (MUST / NEVER / SHOULD) and "Try" is not a verb the checker knows, so it reads as prose. Fix: lead with a modal, or start with a verb from the catalog (Escalate …, Ask …).
upsell.tg:7:1  info   prompt/unregistered-doer  A bare generic doer never enters the world model - instruction to the agent, or background about users? Use the imperative if the agent acts, or a definite party ("The user should ...") to register the doer.
upsell.tg:8:1  info   prompt/unparsed-rule  Reads like a rule, but the rule reader cannot parse it as one, so it earns no Enforceability credit and no measurability check: no modal (MUST / NEVER / SHOULD) and "Only" leads the action and the checker reads the verb at the head, so it reads as prose. Fix: lead with a modal, or start with a verb from the catalog (Escalate …, Ask …).
upsell.tg:8:1  info   prompt/unintroduced-definite  "the Basic plan" retrieves something this document never introduces - a model must guess which plan is meant. Mention a plan on an earlier line, or anchor it here ("their plan", "the plan you ...").
upsell.tg:10:1  warn   prompt/unmeasurable  Unmeasurable rule - no observable action or bound, so nothing can check compliance. Name a concrete action ("cite the source", "at most 3 sentences") or move it to a prose block.

✓ 1 file - 0 error, 1 warning, 6 info

$ typeglish score upsell.tg   # the facet lines and the findings ledger, elided
upsell.tg - C (68/100)  proven errors: none  tiers: base+z3
  rules   4 of 6 rule-shaped lines read · 3 not provable · 1 unmeasurable
    L5   ~ unprovable    ALWAYS mention Quillon Premium before ending the chat.
                         the checker knows "mention" but cannot prove this shape: it stops reading at "before" - the clause after it is not part of the provable action → if the clause is a condition, make it one: WHEN <clause> THEN MUST <verb> …; otherwise give it its own rule
    L7   ✗ invisible     Try to upsell where appropriate.
                         no modal (MUST / NEVER / SHOULD) and "Try" is not a verb the checker knows, so it reads as prose → lead with a modal, or start with a verb from the catalog (Escalate …, Ask …)
    L8   ✗ invisible     Only offer Premium to customers on the Basic plan.
                         no modal (MUST / NEVER / SHOULD) and "Only" leads the action and the checker reads the verb at the head, so it reads as prose → lead with a modal, or start with a verb from the catalog (Escalate …, Ask …)
    L9   ~ unprovable    MUST tell the customer that Premium has no ads and 4K.
                         the checker knows "tell" but cannot prove this shape: it stops reading at "that" - the clause after it is not part of the provable action → if the clause is a condition, make it one: WHEN <clause> THEN MUST <verb> …; otherwise give it its own rule
    L10  ~ unmeasurable  NEVER be pushy.
                         no observable action or bound, so nothing can check compliance → name a concrete action or a bound ("at most 3 sentences")

$ typeglish build upsell.tg
✓ built .typeglish/dist/upsell.txt ← upsell.tg (41ffb981518d, full)
```

A C, nothing blocking, and it builds. The rules table is the useful part: two of six lines are invisible to the rule reader, and the audience line, *Only offer Premium to customers on the Basic plan*, is one of them.

## §2 The checker stops at *before*

Lines 5 and 6 disagree about the upset customer. *ALWAYS mention Quillon Premium* covers every chat; line 6 forbids it in some of them; nothing says which wins. The check passed them because line 5 ends in a trailing *before* clause, and the rules table says exactly what that does: the checker stops reading at *before*. Take the clause off and nothing else changes.

upsell-nobefore.tg - line 5 without *before ending the chat* ✗ logic/contradiction

```typeglish
# Role
You are the chat support agent for Quillon TV, a streaming subscription service.

# Offers
- ALWAYS mention Quillon Premium.
- IF the customer is upset THEN NEVER mention Quillon Premium.
- Try to upsell where appropriate.
- Only offer Premium to customers on the Basic plan.
- MUST tell the customer that Premium has no ads and 4K.
- NEVER be pushy.
```

tg check, score and build - upsell-nobefore.tg ✗ F (53/100), build refused

```
$ typeglish check upsell-nobefore.tg   # the six info and one warning from upsell.tg, elided
upsell-nobefore.tg:5:1  error  logic/contradiction  Conflicts with line 6. Logical conflict - "mention quillon premium" is both required and forbidden. Keep one, or scope the two rules so they cannot both apply (IF <condition> THEN ...).
                    ↳ line 6: - IF the customer is upset THEN NEVER mention Quillon Premium.
upsell-nobefore.tg:6:1  error  logic/contradiction  Conflicts with line 5. Logical conflict - "mention quillon premium" is both required and forbidden. Keep one, or scope the two rules so they cannot both apply (IF <condition> THEN ...).
                    ↳ line 5: - ALWAYS mention Quillon Premium.
✗ 1 file - 2 error, 1 warning, 6 info

$ typeglish score upsell-nobefore.tg
upsell-nobefore.tg - F (53/100)  proven errors - grade capped at F  tiers: base+z3

$ typeglish build upsell-nobefore.tg
typeglish build: upsell-nobefore.tg refused - nothing written
```

Two blocking errors and a refused build. The conflict was there all along; the trailing clause kept it away from the prover. Yesterday’s post, [We put the condition last. The checker stopped defending the rule.](https://typeglish.dev/blog/condition-last-ablation), measures that effect across seven rules. This is what it looks like in a section nobody thought of as risky.

The obvious repair is to turn the trailing clause into a condition, `WHEN the chat is ending THEN MUST mention Quillon Premium`, and give line 6 precedence. Do not do it yet. A provable version of a rule aimed at the wrong person at the wrong moment is still aimed at the wrong person at the wrong moment.

## §3 Who the line is talking to

Read the section as the agent does, one chat at a time, and three questions have no answer in it.

**Which plan is this customer on?** Line 8 assumes the agent knows. It does not, unless the platform tells it, and nothing here says the platform does. A model asked to *only offer Premium to Basic customers* without the plan in front of it will ask the customer, infer it from the conversation, or quietly drop the condition. The third is the most common and the most expensive: the customer already paying for Premium hears that Premium has no ads.

**Is the customer upset?** Line 6 hands that call to the model’s reading of tone. As [Every WHEN is a classifier](https://typeglish.dev/blog/every-when-is-a-classifier) argues, *is upset* is a classifier nobody tested, and here its misses become a sales pitch to someone who has just waited forty minutes for a refund.

**When is the end of the chat?** A customer who says *thanks* and then asks one more question gets two endings. A customer who asks to cancel gets an ending too, and line 5 says to pitch them.

> An upsell needs an audience and a moment. The before file guesses both.

The rewrite answers each question with something the agent does not have to guess. The audience is data: the plan arrives as a typed input, `$REQUIRE variable plan: one of basic, standard, premium`, and the offer lives inside a `$IF` arm, so the build for anyone not on Basic does not contain the offer at all. The moment is an event: *the customer confirms a playback fix*, which is the one point in a support chat where the customer’s experience just got better. Cancellation goes to the retention desk by tool call, and a billing complaint gets its own prohibition.

## §4 What ships

upsell-fixed.tg - the plan is an input, the moment is an event ✓ A (99/100)

```typeglish
<$CONFIG>
  $REQUIRE variable plan: one of basic, standard, premium
</$CONFIG>

# Role
@@ role: the desk this file speaks for, and the owner of every rule below
You are the chat support agent for Quillon TV, a streaming subscription service.
@@ offer_object: the plan the offer names, declared before any rule uses it
The %Premium plan% IS the Quillon TV plan with no ads and 4K streaming.

# Offers
@@ plan_arm: the host passes the plan in, so a customer who already pays for Premium never gets the pitch
$IF @{plan} is equal to basic:
  - WHEN the customer confirms a playback fix THEN MUST offer the %Premium plan% once.
  - WHEN the customer disputes a charge THEN NEVER offer the %Premium plan%.
$ELSE:
  - NEVER offer the %Premium plan%.
@@ cancel_route: a customer who asks to cancel goes to the retention desk, not to a pitch
- WHEN the customer asks to cancel THEN MUST call @[transfer_to_retention].

$TOOL transfer_to_retention
  - description:: Hand the chat to the retention desk.
  - input::
    - reason:: the customer's own words.

$TEST playback_fixed
  - input:: That worked, the film plays now. Thanks!
  - expect::
    - offers the Premium plan once
    - at most 3 sentences

$TEST refund_landed
  - input:: The double charge is back on my card, thanks for sorting it.
  - expect::
    - matches /^(?!.*Premium)/

$TEST cancel_request
  - input:: I want to cancel my subscription.
  - expect::
    - calls transfer_to_retention
    - matches /^(?!.*Premium)/
```

Four rules where there were six. *Do not be pushy* became *once*, which is a bound the model can follow and a test can check. The feature list moved into a declaration of what the %Premium plan% is, which the model reads as context rather than as a script to recite.

tg check, score, test --dry and two builds - upsell-fixed.tg ✓ 4/4 covered by $TEST

```
$ typeglish check upsell-fixed.tg
✓ 1 file - 0 error, 0 warning, 0 info

$ typeglish score upsell-fixed.tg
upsell-fixed.tg - A (99/100)  proven errors: none  tiers: base+z3
  planes  runtime 99 (what the model reads) · hygiene 100 (source only)
  facets  enforceability 100 x.21 · hardness 100 x.12 · directness 92 x.08 · consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 100 x.12 (hygiene) · style 100 x.08 · security 100 x.08
  rules   4 of 4 rule-shaped lines read · 4/4 covered by $TEST

$ typeglish test upsell-fixed.tg --dry
✓ upsell-fixed.tg  coverage: 4/4 rules exercised
  · playback_fixed - "That worked, the film plays now. Thanks!" (not run)
      ✓ at most 3 sentences
      · rubric - 1 prose expectation, judged on a live run (needs ANTHROPIC_API_KEY)
  · refund_landed - "The double charge is back on my card, thanks for sorting it." (not run)
      ✓ matches /^(?!.*Premium)/
  · cancel_request - "I want to cancel my subscription." (not run)
      ✓ matches /^(?!.*Premium)/
      · rubric - 1 prose expectation, judged on a live run (needs ANTHROPIC_API_KEY)
✓ 1 prompt - 0 failed

$ typeglish build upsell-fixed.tg --vars '{"plan":"basic"}'
✓ built .typeglish/dist/upsell-fixed.txt ← upsell-fixed.tg (6f7d74a8cfd3, full)

$ typeglish build upsell-fixed.tg --vars '{"plan":"standard"}'
✓ built .typeglish/dist/upsell-fixed.txt ← upsell-fixed.tg (ce2e32ed3865, full)
```

Two builds, two different hashes, because they are two different prompts. Here is what each customer’s agent actually reads.

.typeglish/dist/upsell-fixed.txt - built with plan basic ✓ the offer, once

```
# Role
You are the chat support agent for Quillon TV, a streaming subscription service. The Premium plan IS the Quillon TV plan with no ads and 4K streaming.

# Offers
- WHEN the customer confirms a playback fix THEN MUST offer the Premium plan once.
- WHEN the customer disputes a charge THEN NEVER offer the Premium plan.
- WHEN the customer asks to cancel THEN MUST call transfer_to_retention.
```

.typeglish/dist/upsell-fixed.txt - built with plan standard ✓ no offer to make

```
# Role
You are the chat support agent for Quillon TV, a streaming subscription service. The Premium plan IS the Quillon TV plan with no ads and 4K streaming.

# Offers
- NEVER offer the Premium plan.
- WHEN the customer asks to cancel THEN MUST call transfer_to_retention.
```

The Standard customer’s agent has no offer rule to misapply and no plan to guess. The losing arm never reached the model. That is the difference between telling the agent who to sell to and deciding it before the chat starts.

## §5 What this does not fix

The first draft of the rewrite said *WHEN the customer confirms a fix*, not *a playback fix*, and it also checked clean at A (99/100). It should not have shipped. A customer whose double charge has just been refunded confirms a fix, and they also disputed a charge, so both guarded rules match one chat: one says offer, one says never. The checker has no way to know whether two guards can be true at once; you do. This is the rule collision that [Bake-off: one test per rule vs. one test per contact](https://typeglish.dev/blog/tests-per-rule-vs-per-contact) finds with tests built from real contacts, and it is why `refund_landed` is in the file. Narrowing the offer to playback fixes resolved the overlap. The test is what keeps it resolved when someone widens the guard again.

The `$IF` arm is only as good as the value the host passes. If the platform sends *basic* for a customer mid-upgrade, the agent will pitch an upgrade they have just bought. That is a data question for whoever owns the integration, and the right place to settle it is before the build, not in the prompt.

And none of this decides whether a support agent should sell at all. Some teams will reasonably decide it should not, and the rewrite makes that a one-line change: the `$ELSE` arm already says what the prompt looks like with no offer in it.

## §6 Common questions

**Should my AI support agent upsell at all?**

That is a business decision, not a prompt decision, but if it does, give the offer a moment and an audience the agent does not have to guess. A good moment is an observable event, such as the customer confirming the problem they came with is fixed. A good audience is data the platform already has, such as the plan the customer pays for, passed in as a typed input rather than inferred from the chat.

**Why does my support agent pitch an upgrade to customers who already have it?**

Because a rule like *Only offer Premium to customers on the Basic plan* asks the model to know a fact it cannot see. It will ask, guess from the conversation, or skip the condition. Declare the plan as a `$REQUIRE` variable and put the offer inside a `$IF` arm, and the build for a Premium or Standard customer does not contain the offer at all.

**Why does typeglish check pass ALWAYS mention X beside IF upset THEN NEVER mention X?**

Because the first line ended with *before ending the chat*, and the checker stops reading a rule at a trailing *before* clause, so the two lines no longer name the same action. Delete the trailing clause and the same pair is two blocking `logic/contradiction` errors. The rules table in `typeglish score` flags the line as unprovable, which is the hint to look.

**How do I stop my agent pitching an upgrade during a complaint?**

Write the complaint case as its own guarded prohibition, such as `WHEN the customer disputes a charge THEN NEVER offer the Premium plan`, narrow the offer to the contacts it suits, and pin the collision with a `$TEST`: a message where the complaint has just been resolved, with a `matches` assert that the reply does not name the plan.

Field note

The line that did the most damage in the before file was the one that sounded most careful: *Only offer Premium to customers on the Basic plan*. It reads like targeting. In a prompt it is a request for the model to know something about the customer that only the billing system knows, and the checker said as much in two ways: the rule reader could not read the line, and *the Basic plan* was never introduced. Whenever a rule’s condition is a fact your platform already holds, the plan, the tier, the region, the contract end date, it belongs in a typed input and a compile-time arm, not in a sentence the model has to act on without evidence. `typeglish build --vars` will show you each customer’s prompt before any of them see it.

∿ washed up Oct 1, 2026 ∿
