> Markdown rendition of https://typeglish.dev/blog/prompt-section-headings ("Your headings decide which rules get checked - Tidelines"). Canonical page: https://typeglish.dev/blog/prompt-section-headings · All pages: https://typeglish.dev/llms.txt

[← Tidelines](https://typeglish.dev/blog)/ Deep dives Aug 1, 2026

# 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 team** 7 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.

## §1 The 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)

```typeglish
<$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.

## §2 The 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.

## §3 Delete 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.

## §4 Delete 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)

```typeglish
# 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)

```typeglish
# 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](https://typeglish.dev/blog/prompt-second-reader), except pointed the wrong way: normally the hygiene plane costs you points for sloppiness, and here sloppiness bought points.

## §5 What 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

```typeglish
# 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](https://typeglish.dev/blog/teardown-pasted-policy) 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](https://typeglish.dev/blog/instruction-placement).

- **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](https://typeglish.dev/blog/silent-deletions), and the two results rhyme: your tooling reacts to structure, and a rule made only of English words is invisible to it either way.

## FAQ Common 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 ∿
