← Tidelines/Deep dives

Your headings decide which rules get checked

We took a support prompt that compiles clean and deleted its section headings, one at a time, changing nothing else. One deletion added three warnings. One deletion removed a warning, raised the grade eight points, and quietly dropped two facets off the scorecard. That second one is the dangerous one.

by TypeGlish team7 min read#deep-dives
Same rules. Different room.

TL;DR A section heading is not a label, it is a declaration of what kind of text follows: prose under # Role is exempt from rule analysis and rules under # Constraints are not, so deleting one heading can silence a real finding and raise your score while the model reads almost exactly the same prompt.

Headings are the part of a prompt everyone treats as furniture. They get added when a file gets long, removed when someone trims it, renamed when a new person inherits it, and reviewed by nobody, because they are not instructions. Except that in a checked prompt they are the closest thing the file has to a type annotation, and moving one moves the analysis. We ran the whole leave-one-out to find out how much.

§1The prompt on the bench

An internet-provider support agent, using every structural marker a real file uses: a folded <$CONFIG>, a # Role block with soft persona prose, a # Constraints block with three annotated rules, a # Examples block with sample copy, and a switch on the channel. It checks with nothing to report.

ivy.tg✓ B (88/100)
<$CONFIG>
  $REQUIRE variable channel: one of voice, chat
  $IMPORT tool lookup_account
</$CONFIG>

# Role
You are Ivy, the support agent for a regional internet provider.
Be warm, be brief, and sound like a person rather than a form letter.

# Constraints
@@ identify: an account number is the only key the billing system takes
- You MUST call @[lookup_account] before discussing a bill.
@@ ack: a dispute clock starts when we say we heard it
- You MUST acknowledge a billing dispute within 2 hours.
@@ no_credit: a credit is a finance decision, not a support one
- You NEVER promise a credit.

# Examples
Sorry about that, I can see an outage in your area and it should clear by 6pm.
Want a text when it does?

$SWITCH ON @{channel}
  - voice:: You MUST reply in at most 2 sentences.
  - chat:: You MUST reply in at most 4 sentences.
Baseline: 0 error, 0 warning, 0 info at B (88/100), with runtime 94 and hygiene 69. The B rather than an A is annotation 38, because only three of the statements carry a @@ note. Not the subject here, and it holds still across every run below.

Then five copies, each missing exactly one structural marker and nothing else. No rule text was edited in any of them.

§2The whole table

leave-one-out - one row per deleted marker
marker deleted                       result                       score
(nothing - baseline)                 clean                        B (88)
# Role                               3 warnings                   C (75)
    prompt/unmeasurable + structure/loose-content x2
# Constraints                        clean                        B (85)
# Examples                           clean                        B (83)
<$CONFIG> (both tags)              2 warnings                   B (81)
    structure/bad-indent x2
all three # headings              10 warnings                  C (66)
Every row is the same prompt. The rules are byte-identical in all six files. The only thing that moved is which region of text each rule was sitting in.

Two of those rows are worth a section each, and they point in opposite directions. Deleting # Role makes the checker stricter. Deleting # Constraints makes it looser. The two remaining rows are quick: pulling the <$CONFIG> tags leaves the two commands stranded at an indent that no longer matches any nesting, which is structure/bad-indent twice, though notably the $REQUIRE and $IMPORT still bind and the switch below still resolves. Deleting # Examples drops the sample copy into the constraints region, where two lines of friendly customer service get read as rules and drag enforceability from 80 to 60.

§3Delete the Role heading, and the persona becomes a rule

The line Be warm, be brief, and sound like a person rather than a form letter. is soft on purpose. It names no observable action, and it should not: it is persona, not policy. Under # Role the checker knows that and leaves it alone. Take the heading away and the same sentence is just a statement floating in the file.

tg check - after deleting the # Role line
ivy.tg:6:1  warn   structure/loose-content  This content is outside any section - put it
  inside a <tag> (a heading ends where a tag or the next heading begins).
ivy.tg:7:1  warn   prompt/unmeasurable  Unmeasurable rule - "be <quality>" names no
  observable action, so nothing can check compliance. Name a concrete action, or move it
  to a prose block (persona prose is allowed to be soft).
ivy.tg:7:1  warn   structure/loose-content  This content is outside any section - put it
  inside a <tag> (a heading ends where a tag or the next heading begins).

 1 file - 0 error, 3 warning, 0 info
C (75/100), from enforceability 63, structure 40, style 70. Read the fix text in the middle finding: move it to a prose block (persona prose is allowed to be soft). The checker is telling you the heading is the mechanism.

This direction is the benign one. It is noisy, it is obvious, and it fails loudly in front of you. Nobody ships it by accident, because three new warnings on a file you did not change the rules of is the kind of thing that stops a pull request.

§4Delete the Constraints heading, and the rules become persona

Now the same mechanism running the other way, which is the reason this post exists. Here is a two-rule file. One rule is soft and gets caught; the other is a proper measurable one.

ivy.tg - with the heading✕ B (83/100)
# Role
You are Ivy, the support agent for a regional internet provider.

# Constraints
@@ tone: a frustrated customer hears posture before content
- You MUST be empathetic.
@@ ack: a dispute clock starts when we say we heard it
- You MUST acknowledge a billing dispute within 2 hours.
0 error, 1 warning, 0 info. The warning is prompt/unmeasurable on line 6, and it is correct: be empathetic is not something you can check, test, or hold anyone to.
ivy.tg - one line deleted, the # Constraints heading✓ A (91/100)
# Role
You are Ivy, the support agent for a regional internet provider.

@@ tone: a frustrated customer hears posture before content
- You MUST be empathetic.
@@ ack: a dispute clock starts when we say we heard it
- You MUST acknowledge a billing dispute within 2 hours.
0 error, 0 warning, 0 info. The rules did not change. The rule that could not be checked is now sitting in the # Role prose block, where soft language is allowed, so there is nothing to report.

B (83) to A (91), by deleting a line of documentation. That alone would be a bad enough incentive, but the scorecards say something worse than the grades do. Put them side by side.

tg score - the two files, facet by facet
# with # Constraints
B (83/100)  planes  runtime 83 · hygiene 84
  facets  enforceability 78 x.21 · hardness 100 x.12 · directness 100 x.08
          consistency 100 x.17 · structure 100 x.12 · annotation 67 x.12
          style 0 x.08 · security 100 x.08

# heading deleted
A (91/100)  planes  runtime 94 · hygiene 84
  facets  enforceability 85 x.26 · consistency 100 x.21 · structure 100 x.16
          annotation 67 x.16 · style 100 x.11 · security 100 x.11
Count them. Eight facets become six. hardness and directness are not scored lower, they are gone, and the remaining weights re-normalize upward to fill the gap: enforceability from x.21 to x.26, consistency from x.17 to x.21. There are no rules left to measure hardness or directness on, so those measurements no longer exist.
The file did not get better. Two of the eight ways of being wrong stopped applying.

And the model barely notices. Build both files and the artifacts differ by exactly one line, the heading itself; the two rules are byte-identical and in the same order. Whatever changed here changed for your tooling and your reviewers, not for the thing reading the prompt. That is the same source-versus-artifact split as your prompt has a second reader, except pointed the wrong way: normally the hygiene plane costs you points for sloppiness, and here sloppiness bought points.

§5What the exemption does not cover

Before this turns into a scare story, the boundary is narrower than it looks, and the narrowness is the useful part. The prose-block exemption covers rule-quality advisories. It does not cover the prover.

two rules, no # Constraints heading anywhere✕ 2 errors
# Role
You are Ivy, the support agent for a regional internet provider.
@@ no_credit: a credit is a finance decision, not a support one
- You NEVER promise a credit.
@@ goodwill: nobody hangs up empty-handed
- You ALWAYS promise a credit.
Both rules live inside the persona block. The checker still returns 2 error, 0 warning, 0 info, two logic/contradiction findings, and exit code 1. You cannot hide a proven conflict by moving it into prose.

Nor does the exemption cover discourse. A definite article that points at a noun the file never introduced is still prompt/unintroduced-definite inside a # Examples block, which is the same result the pasted-policy teardown got when it tried moving a whole policy document into # Context and received an identical seven findings. So the rule is specific: headings move rule analysis, and nothing else. Contradictions, dangling pointers, unintroduced nouns, and leaked secrets all travel with the text.

Which leaves a short, practical position, and it is not "add more headings."

  • Put soft language under a prose heading deliberately. # Role and # Persona exist for the sentences you genuinely cannot test. Moving be warm, be brief there is the right fix, and the checker's own message recommends it. Hardening it into a fake measurable rule is the wrong fix.
  • Keep # Constraints for statements you would be willing to write a $TEST for. That is a better admission test than "is this important," and it is the same discipline as where the rules go.
  • Treat a heading edit as a semantic edit. Renaming, deleting, or moving a heading changes which analyses run on everything under it. Re-run check and diff the score, and be suspicious of a heading change that made the number go up.

The last one is the whole post, honestly. A score that improves because you deleted documentation is a score telling you something, and it is not "well done."

Field note

The prose blocks are a fixed set (Role, Persona, Context, Background, Examples, Notes), which is worth knowing before you invent # Tone and wonder why your voice guidelines started failing. And a file that mixes one XML section with # headings gets structure/heading-outside-section on every heading, because one <tag> anywhere makes XML the file's structure model. Pick one convention per file. This is the second ablation on this bench, after deleting the rules one at a time, and the two results rhyme: your tooling reacts to structure, and a rule made only of English words is invisible to it either way.

FAQCommon questions

Do section headings actually matter in a system prompt?
They do more than organize it. A heading like # Role or # Examples marks the text beneath it as prose, which exempts it from rule analysis, while # Constraints marks the text beneath it as rules, which subjects it to rule analysis. In the worked example, deleting a single # Role line turned one persona sentence into a prompt/unmeasurable warning and moved the file from B (88/100) to C (75/100), with no other byte changed.
Why did my prompt score go up after I deleted a line?
Because the line you deleted may have been the thing telling the checker to look. Deleting a # Constraints heading drops the rules beneath it into the preceding prose block, where soft rules are allowed, so the finding against them disappears. In the worked pair, one file scored B (83/100) with a prompt/unmeasurable warning and the same file with the heading removed scored A (91/100) with zero findings. The grade rose because two facets, hardness and directness, stopped being measured at all.
Where should soft persona instructions go in a system prompt?
Under a prose heading, on purpose. Lines like "be warm, be brief" name no observable action, so they will be flagged as unmeasurable anywhere a checker expects rules. That is the correct verdict, and the correct response is not to harden the sentence into a fake rule but to put it under # Role or # Persona, where soft language is legitimate. Keep # Constraints for statements you would be willing to test.
Does putting rules under # Role stop the checker from finding contradictions?
No. The prose-block exemption covers rule-quality advisories such as prompt/unmeasurable, not the logic prover. Two rules placed directly under a # Role heading, one saying you NEVER promise a credit and one saying you ALWAYS promise a credit, still produce 2 blocking logic/contradiction errors and exit 1. The proofs are heading-independent; the style advice is not.
∿ washed up Aug 1, 2026 ∿