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.
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.
<$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
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
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)
§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.
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
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.
airline.tg:3:3 warn structure/unused-import Imported tool "lookup_booking" is never used. ✓ 1 file - 0 error, 1 warning, 0 info
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.
<$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].
Delete the anchored privacy rule now and the file is no longer indistinguishable from the original.
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
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
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.
✓ 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)
--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.
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, andtypeglish check --strictturned 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
$REQUIREvariable a$SWITCHreads, an$IMPORTtool 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
--strictin CI. A plaincheckkeeps correctness findings such asstructure/unused-importat warning severity, which means a deletion that orphans a declared tool or variable still exits 0 and merges. With--strictthe 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.