← Tidelines/Deep dives

Delete a line. See who notices.

We took an airline support prompt that compiles clean and deleted eleven lines from it, one at a time. Four deletions refused to compile. One warned. Six went through in total silence, including the line that says what the agent is. The line between loud and silent is not importance, and it is not risk.

by TypeGlish team7 min read#deep-dives
Eleven deletions. Five noticed.

TL;DR Your tooling notices a deleted line only when something else in the file pointed at it, so a $REQUIRE, an $IMPORT, or a $SWITCH arm fails the build on removal while every plain-prose rule, up to and including the agent's identity, deletes in silence. Anchor the rules you cannot afford to lose to a declared symbol and run check --strict, and their deletion becomes a red build instead of a quiet one.

Nobody deletes a load-bearing rule on purpose. It happens the way it happens in code: a prompt gets long, someone trims it for token budget or readability, three lines go, two of them were decoration and one of them was the guardrail. The interesting question is not whether that happens to you. It is which of the deletions your pipeline would have stopped. We measured it the boring way, by doing all of them.

§1The prompt on the bench

The subject is a support agent for a regional airline. It uses both halves of a TypeGlish file: the machine plane at the top (a typed input, an imported tool, a switch over the channel) and the prose plane in the middle (five rules the model reads). Every rule carries an @@ note. It compiles with nothing to report.

airline.tg✓ B (89/100)
<$CONFIG>
  $REQUIRE variable channel: one of voice, chat
  $IMPORT tool lookup_booking
</$CONFIG>

# Role
@@ role: name the domain so "helpful" has a scope
You are a support agent for a regional airline.

# Constraints
@@ identify: the record locator is the only key the booking system takes
- You MUST ask for the record locator before discussing a booking.
@@ lookup: never answer a booking question from memory
- You MUST call @[lookup_booking] before stating a departure time.
@@ no_fares: quoted fares go stale within the hour
- You NEVER quote a fare.
@@ privacy: another passenger's itinerary is not this caller's to hear
- You NEVER reveal another passenger's itinerary.
@@ weather: a waiver day has its own rules and a human owns them
- WHEN a flight is cancelled for weather THEN you MUST escalate to a human agent.

$SWITCH ON @{channel}
  - voice:: You MUST reply in at most 2 sentences.
  - chat:: You MUST reply in at most 4 sentences.

$TEST fare_question
  - input:: How much would it cost to move my flight to Friday?
  - expect::
    - does not quote a fare
    - offers to check the booking
The B rather than an A is the judgment rule pulling enforceability down to 68, which is the same effect we took apart in the last ablation. It is not the subject of this one. What matters here is that the baseline is green: 0 error, 0 warning, 0 info.

Then we made eleven copies, each missing exactly one line, and ran typeglish check and typeglish score on every one. Deleting a rule takes its @@ note with it, the way a real edit would.

§2The whole table, honestly

leave-one-out - one row per deleted line
line deleted                                   result       score
(nothing - baseline)                           clean        B (89)
$REQUIRE variable channel                      2 errors     F (74)
    structure/undefined-ref + structure/undeclared-input
$IMPORT tool lookup_booking                    1 error      F (79)
    structure/undefined-tool-inline
- voice:: arm of the $SWITCH                   1 error      F (79)
    structure/non-exhaustive-switch
- chat:: arm of the $SWITCH                    1 error      F (79)
    structure/non-exhaustive-switch
- MUST call @[lookup_booking] ...              1 warning    B (83)
    structure/unused-import
- MUST ask for the record locator ...          clean        B (89)
- NEVER quote a fare.                          clean        B (87)
- NEVER reveal another passenger's itinerary.  clean        B (89)
- WHEN cancelled for weather THEN escalate     clean        B (87)
the # Role body (what the agent is)            clean        B (89)
$TEST fare_question (the whole block)          clean        B (89)
Four deletions block the build. One drops a warning. Six are indistinguishable from no edit at all. Sort that column by business consequence and it is close to backwards: the two lines whose loss would actually reach a customer, the fare rule and the privacy rule, are both in the silent group.

§3The four that refused to compile

Every blocking deletion has the same shape. The removed line was the definition of something another line uses, so taking it out left a reference pointing at nothing, and a pointer with no target is a compile error rather than a warning. Delete the typed input and the switch below it is reading a name the file no longer declares.

tg check - after deleting the $REQUIRE line
airline.tg:21:12  error  structure/undefined-ref  Dangling pointer - @{channel} names
  nothing. Declare it with "$REQUIRE variable channel" (a runtime variable), or
  "channel IS …" (a fixed value).
airline.tg:21:1   error  structure/undeclared-input  This $SWITCH reads @{channel},
  which isn't a declared input or $DEFINE.

 1 file - 2 error, 0 warning, 0 info
Note where the error is reported: line 21, the switch, not line 2, the deletion. The checker cannot know which end of a broken reference you meant to keep. It knows only that the two ends no longer meet.

The two $SWITCH arms fail for a stricter reason: exhaustiveness. The domain one of voice, chat is declared, so a switch over it must cover both members, and deleting either arm leaves a member with nowhere to go. This is the mechanism the multichannel bake-off was really about, running in reverse. There it caught a channel you added. Here it catches an arm you removed. Same proof, either direction.

What is worth sitting with is that none of these four lines is a rule. Not one of them says anything to the model. They are scaffolding, and the scaffolding is the only part of the file that the compiler will physically not let you break.

§4The six that went through in silence

Now the other end of the table. NEVER reveal another passenger's itinerary is a data-protection rule with a regulator behind it. Delete the line and its note, run the checker, and you get the same eight words you got before the edit: 0 error, 0 warning, 0 info. The grade does not move either. Same for the fare rule, the record-locator rule, and the escalation rule.

The one that stops people is the role line. Delete You are a support agent for a regional airline., which is the entire body of the # Role section and the only sentence in the file that says what this thing is, and the check comes back clean at the same B (89). There is no rule anywhere in TypeGlish that a prompt must have a persona. There is no rule that it must have any content at all. A file with a heading and nothing under it is a valid file, and a valid file compiles.

A checker validates the file in front of it. It has no memory of the file you had yesterday.

That is the whole explanation, and it is not a gap anyone should want closed. A checker that complained about absent rules would need an opinion about which rules your business requires, and no compiler has one. So the silence is correct, and it is also the exact reason the silence is dangerous: it reads, to a reviewer skimming a green CI run, like approval.

One row sits between the two groups and it is the most instructive line in the table. Delete You MUST call @[lookup_booking] before stating a departure time, a perfectly ordinary prose rule, and the checker does speak up.

tg check - after deleting the tool-call rule
airline.tg:3:3  warn   structure/unused-import  Imported tool "lookup_booking" is never used.

 1 file - 0 error, 1 warning, 0 info
The rule itself is not missed. The import is: with the rule gone, nothing in the file points at the declared tool, so the declaration is orphaned and the checker says so. The rule became visible to the tooling by association.

That is the boundary, stated properly. It is not importance, and it is not severity. A deletion is visible exactly when the deleted line participated in a reference: when it defined a symbol something else uses, or when it was the last thing using a symbol something else defined. A rule composed only of English words participates in nothing, so its removal leaves no hole to detect.

§5Making a deletion loud

Which turns the finding into a technique. If a rule matters enough that you would want a red build when it disappears, give it something to hold onto. Two of the silent rules can be anchored without changing what they ask the model to do, and both rewrites make the rule better prose as well as louder scaffolding.

airline.tg - the same rules, anchored✓ compiles
<$CONFIG>
  $REQUIRE variable passenger_id: string
  $IMPORT tool escalate_to_human
</$CONFIG>

# Role
@@ role: name the domain so "helpful" has a scope
You are a support agent for a regional airline.

# Constraints
@@ privacy: an itinerary belongs to the passenger on the record
- You NEVER reveal an itinerary for a passenger other than @{passenger_id}.
@@ weather: a waiver day has its own rules and a human owns them
- WHEN a flight is cancelled for weather THEN you MUST call @[escalate_to_human].
The privacy rule now names the passenger it is protecting, which is what it always meant. The escalation rule names the tool that performs the handoff instead of describing it, which is what escalation rules should do anyway. Neither edit was made for the checker's benefit, and both of them are what makes the deletion detectable.

Delete the anchored privacy rule now and the file is no longer indistinguishable from the original.

tg check - the anchored privacy rule, deleted
airline.tg:2:3   warn   structure/unused-import  Required variable "passenger_id" is never used.
airline.tg:2:21  warn   clarity/unused-variable  $REQUIRE variable "passenger_id" is
  never used - no @{passenger_id} reference fills it. Remove it, or reference it in
  the prompt.

 1 file - 0 error, 2 warning, 0 info
Two warnings where there were none. Better, but a warning still exits 0, and a pipeline that goes green on warnings is a pipeline nobody reads.
tg check --strict - the same file✕ 2 errors
airline.tg:2:3   error  structure/unused-import  Required variable "passenger_id" is never used.
airline.tg:2:21  error  clarity/unused-variable  $REQUIRE variable "passenger_id" is
  never used - no @{passenger_id} reference fills it. Remove it, or reference it in
  the prompt.

 1 file - 2 error, 0 warning, 0 info
This is the pair that matters: the same deletion, the same file, exit 0 on check and exit 1 on check --strict. Strict is what turns an orphaned declaration from a note into a merge blocker.

Not every rule can be anchored, and pretending otherwise would be the wrong lesson. You NEVER quote a fare refers to no input and calls no tool; there is no honest symbol to hang it on, and inventing one would be writing scaffolding to satisfy a linter. For rules in that class the tripwire is a case, not a reference. The prompt already carries $TEST fare_question, and deleting the fare rule moves the one number that reports on it.

tg test --dry - before and after deleting the fare rule
 airline.tg  coverage: 2/6 rules exercised
  · fare_question - "How much would it cost to move my flight to Friday?" (not run)

# with the fare rule deleted
 airline.tg  coverage: 1/5 rules exercised
  · fare_question - "How much would it cost to move my flight to Friday?" (not run)
Offline, --dry reports coverage and does not fail on this, so treat the number as a signal rather than a gate. The case is still the right home for the rule: it is the artifact that outlives the line, and it is what a live run grades against.

So the practical version of the whole exercise is short. Run your own delete-one-line pass before you need it, on a branch you throw away: for each line in the prompt, remove it, run check --strict, write down whether anything happened. Every silent row is a line your pipeline will let someone delete on a Friday afternoon. Anchor the ones that can be anchored, put a case under the ones that cannot, and accept the rest as genuinely optional, which is a useful thing to have learned about a prompt you inherited.

Field note

The reference family (@{variable}, @[tool], @<section>) is the thing doing the work here, and it is the same declared surface we argued for in your system prompt is an API contract: a prompt that names its inputs and tools gets a dependency graph for free, and a dependency graph is what makes a deletion detectable. --strict is the flag that makes the graph a gate, and it is step two of the triage list for exactly this reason.

FAQCommon questions

How do I stop someone from deleting an important rule in my system prompt?
Give the rule a reference. A prose rule that mentions only English can be deleted with zero diagnostics, but a rule that points at a declared symbol leaves that symbol orphaned when it goes, and the checker reports it. In the worked example, deleting a privacy rule written as plain prose produced 0 errors and 0 warnings; rewritten to reference a declared @{passenger_id}, the same deletion produced two warnings, and typeglish check --strict turned both into blocking errors. That is a red build in CI, which is the only kind of tripwire a busy team actually notices.
Why does my prompt still pass its checks after I deleted a rule?
Because a checker validates the file in front of it, not the file you had yesterday. Nothing in a well-formed prompt requires any particular rule to exist, so removing one leaves a smaller, still-valid prompt. Only lines that other lines depend on (a $REQUIRE variable a $SWITCH reads, an $IMPORT tool a rule points at, an arm of a switch over a typed domain) break something when they go. Everything else deletes clean.
What is leave-one-out ablation for a prompt?
You remove one line at a time from a working prompt, re-run your tooling on each variant, and write down what changed. Ablating against the score ranks rules by how checkable they are. Ablating against the checker, which is what this post does, ranks lines by how connected they are: it tells you exactly which deletions your build would catch and which ones would ship.
Should I run typeglish check or typeglish check --strict in CI?
Run --strict in CI. A plain check keeps correctness findings such as structure/unused-import at warning severity, which means a deletion that orphans a declared tool or variable still exits 0 and merges. With --strict the same finding is a blocking error. In the worked example, deleting an anchored rule gave 0 errors and 2 warnings on a plain check, and 2 errors under --strict.
∿ washed up Jul 27, 2026 ∿