← Tidelines/Deep dives

We moved eleven lines. Six shipped a different prompt.

Every ablation on this blog so far has taken something out. This one takes nothing out. Eleven elements of a clean support prompt get moved somewhere else legal, one at a time, and the question is which of those moves the compiler is entitled to have an opinion about.

by TypeGlish team11 min read#deep-dives
Same rules. New running order.

TL;DR Position is an edit, and a checker can barely see it. Moving eleven elements of a clean A (97/100) support prompt one at a time, three moves refuse to build, two leave the deployed prompt byte-identical, and six ship different bytes to the model at the identical grade with nothing reported. The worst of the six swaps two arms of a $IF chain and turns a free courier collection into a drop-off label on a 600-pound order. Gate position-only edits on the built artifact, because that diff is the only place they exist.

Somebody reorganises the prompt. It happens for good reasons: the persona reads better at the bottom, the constraints block has grown and wants splitting, a note got orphaned when a rule landed between it and its line, ops asked for the survey step earlier. None of it is a policy change. Nobody writes a ticket for it. In review it looks like the diff equivalent of tidying a drawer, and it goes in under a green tick because the green tick is honest: nothing was added and nothing was removed.

So this run does the thing the file cannot tell you about. Eleven variants, each one base.tg with exactly one element moved to a different place in the file, the words untouched. Then check, score and build on all eleven, and a diff of every artifact against the baseline.

base.tg - the baseline✓ A (97/100)
$REQUIRE variable order_total: number
$TOOL check_stock
  - description:: Check whether an item is in stock at a depot.
  - input::
    - sku:: string

# Role
@@ role: one sentence names the speaker, so every later "you" is the agent
You are Wren, the order support agent for Ellesmere Tools.

# Constraints
@@ brevity: three sentences keeps a chat reply scannable
- You MUST keep every reply to at most 3 sentences.
@@ stock_needs_tool: never guess whether a depot holds an item
- WHEN a customer asks about availability THEN you MUST call @[check_stock].
@@ credit_default: goodwill is a supervisor decision
- UNLESS an order is more than 5 days late THEN you MUST NOT offer a goodwill credit.
@@ credit_exception: a late order is the one case the desk settles itself
- WHEN an order is more than 5 days late THEN you MUST offer a goodwill credit.
@@ claims: a damaged delivery is a claims decision
- WHEN a customer reports a damaged delivery THEN you MUST transfer them to a claims handler.
@@ courier: the higher band pays for a courier, the lower band does not
$IF @{order_total} is at least 500:
  - You MUST offer a free courier collection.
$ELSE IF @{order_total} is at least 100:
  - You MUST offer a free drop-off label.
$ELSE:
  - You MUST quote a standard return postage.

# Instructions
@@ step_greet: a name in the first line tests better than a greeting alone
1. Greet a customer by name.
@@ step_order: the order number unlocks every later lookup
2. Confirm an order number.
@@ step_answer: answer before offering anything
3. Answer a question.
@@ step_survey: the survey goes last or it reads as a brush-off
4. Offer a survey.

$TEST damaged
  - input:: My drill arrived with a cracked case.
  - expect::
    - transfers to a claims handler
Forty-three lines, thirteen rule-shaped statements, one declaration, one tool, one chain, one procedure, one test. A reviewed prompt, not a broken one.
tg check + tg score + tg build base.tg - output
$ npx typeglish check base.tg
 1 file — 0 error, 0 warning, 0 info

$ npx typeglish score base.tg
base.tg — A (97/100)  proven errors: none  tiers: base+z3
  planes  runtime 99 (what the model reads) · hygiene 90 (source only)
  facets  enforceability 100 x.21 · hardness 100 x.12 · directness 95 x.08 · consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 79 x.12 (hygiene) · style 100 x.08 · security 100 x.08
  lever   annotation 79/100 (up to +3 overall) — Put a "@@ why" note directly above each statement ("@@ name: why" also names it).

$ npx typeglish build base.tg
 built .typeglish/dist/base.txt ← base.tg (e56aa317e9de, full)
e56aa317e9de is the number the rest of this post is about. It is the hash of the bytes the model reads, and it is the only thing in the pipeline that has an opinion about where a line sits.

§1Eleven moves

Each variant lives in move/ and differs from the baseline by one relocation. No word changes. Four of the eleven move a control-plane declaration, four move a rule or a section, two move a piece of structure, and one reorders a chain.

move/ - one relocation each
# declarations and control plane
require.tg      the $REQUIRE line drops below the Role sentence
tool.tg         the $TOOL block moves to the end of the file
test_top.tg     the $TEST block moves above # Role
note.tg         one @@ note moves up one line, off its statement

# rules and sections
brevity.tg      the brevity rule moves to the bottom of # Constraints
credit_order.tg the exception moves above the default it excepts
credit_far.tg   the exception moves out of # Constraints into # Instructions
role_last.tg    the whole # Role section moves to the end of the file

# structure and chains
steps.tg        the numbered procedure is reordered, renumbered 1 to 4
else_first.tg   the $ELSE arm moves above the $IF
arms.tg         the two bands of the chain swap places
Eleven edits a reviewer would describe as housekeeping. Three of them are not.
tg check move/ - all eleven at once✗ 4 error
$ npx typeglish check move
move/else_first.tg:23:1  error  structure/bad-switch  $ELSE without a deterministic $IF above it at the same column — a chain reads $IF @{x} …: / $ELSE IF …: / $ELSE:.
move/else_first.tg:24:1  warn   structure/bad-indent  Indentation mirrors section nesting: expected column 0 (0 levels deep), found 2. The indent unit is 2 spaces.
move/else_first.tg:25:1  error  structure/non-exhaustive-switch  This chain isn't exhaustive — no arm handles @{order_total}=0.0. Add an arm or an ELSE.
move/note.tg:33:1  error  structure/stacked-annotation  Stacked annotations: a `@@` note documents the ONE statement directly below it, and the line below is another `@@`. Merge the notes into one line, or give each note its own statement.
move/require.tg:9:1  error  structure/misplaced-command  A $-command must sit at the top of the file, outside any content section (it currently follows content on line 6).
move/role_last.tg:9:3  info   prompt/declared-later  `you` is declared on line 28, after its first use here: declare the identity before the rules that use it (the model reads top-down).

 11 files — 4 error, 1 warning, 1 info
program: 11 independent files — no $IMPORT compositions
Three of the eleven files appear with an error. One appears with a non-blocking info. Seven do not appear at all. That ratio is the post.

§2The three the language refuses

Take the good news first, because it is real. Three positions are not legal positions, and the compiler says so before anything is written.

require.tg moves the $REQUIRE line from the top of the file to just under the Role sentence. That is structure/misplaced-command, and the message names the content line it now follows. The manifest commands are the file's header, and a header below the body is not a header. This one matters more than it looks: a $REQUIRE that quietly stopped being read would leave @{order_total} dangling in a chain, which is the difference between a typed input and a hole.

note.tg moves one @@ note up a single line, which is the shape a file lands in whenever a note and its statement get separated by an edit. Now two notes sit stacked and the statement below them has no note at all. structure/stacked-annotation is blocking rather than advisory, which is the right call: an annotation is a claim about the line beneath it, and a claim pointing at the wrong line is worse than no claim.

else_first.tg lifts the $ELSE arm above its $IF. Three findings, and the third is the interesting one: structure/non-exhaustive-switch, with a counterexample, @{order_total}=0.0. Moving the default off the bottom of the chain did not just break the syntax, it left a real input with no arm, and the checker went and found that input.

tg score move/*.tg - the split, one line each
move/arms.tg — A (97/100)  proven errors: none  tiers: base+z3
move/brevity.tg — A (97/100)  proven errors: none  tiers: base+z3
move/credit_far.tg — A (97/100)  proven errors: none  tiers: base+z3
move/credit_order.tg — A (97/100)  proven errors: none  tiers: base+z3
move/else_first.tg — F (89/100)  proven errors — grade capped at F  tiers: base+z3
move/note.tg — F (91/100)  proven errors — grade capped at F  tiers: base+z3
move/require.tg — F (92/100)  proven errors — grade capped at F  tiers: base+z3
move/role_last.tg — A (97/100)  proven errors: none  tiers: base+z3
move/steps.tg — A (97/100)  proven errors: none  tiers: base+z3
move/test_top.tg — A (97/100)  proven errors: none  tiers: base+z3
move/tool.tg — A (97/100)  proven errors: none  tiers: base+z3
No middle. Three files cap at F on a file whose words did not change, and the other eight land on A (97/100), which is the baseline grade to the point. The grade is a property of how the rules are phrased, and none of these edits rephrased anything.

§3The two that really changed nothing

Two of the eight legal moves are worth separating out, because they are the only ones in the set where the silence is complete and correct. Build them and the artifact is not merely similar to the baseline, it is the baseline.

tg build move/ - eight artifacts, two of them old news✗ 3 refused
$ npx typeglish build move
 built .typeglish/dist/move/arms.txt ← move/arms.tg (d918a14e1d72, full)
 built .typeglish/dist/move/brevity.txt ← move/brevity.tg (3b3698dcd92f, full)
 built .typeglish/dist/move/credit_far.txt ← move/credit_far.tg (25de28446895, full)
 built .typeglish/dist/move/credit_order.txt ← move/credit_order.tg (ec54bfd78c8a, full)
typeglish build: move/else_first.tg refused — nothing written
typeglish build: move/note.tg refused — nothing written
typeglish build: move/require.tg refused — nothing written
 built .typeglish/dist/move/role_last.txt ← move/role_last.tg (a40889ecec2a, full)
 built .typeglish/dist/move/steps.txt ← move/steps.tg (ae7633f3a649, full)
 built .typeglish/dist/move/test_top.txt ← move/test_top.tg (e56aa317e9de, full)
 built .typeglish/dist/move/tool.txt ← move/tool.tg (e56aa317e9de, full)
 built 8/11 — 3 refused (each named above, nothing written for them)
The diagnostic text for the three refusals is reprinted under each name in the real output; it is cut here for length. Read the last two hashes against the baseline's.

test_top.tg and tool.tg both come out at e56aa317e9de, which is the baseline's artifact hash, not a near miss. Nothing about the deployed prompt changed, because a $TOOL declaration and a $TEST block are control plane: the tool's schema ships in the build --bundle sidecar, the test suite is graded out of band, and neither contributes a byte to the prompt the model reads. Move them wherever the file is easiest to read. That is genuinely a free edit, and it is the only free edit in this list.

Two moves out of eleven change nothing. Ten reviewers out of ten would have guessed eight.

§4The six that shipped a different prompt

Which leaves six files that check with nothing reported, hold A (97/100), and hand the model different bytes. Here is the whole of what changed, in the only place it exists.

diff dist/base.txt dist/move/*.txt - the four quiet ones
=== brevity ===
5d4
< - You MUST keep every reply to at most 3 sentences.
15a15
> - You MUST keep every reply to at most 3 sentences.

=== credit_order ===
7d6
< - UNLESS an order is more than 5 days late THEN you MUST NOT offer a goodwill credit.
8a8
> - UNLESS an order is more than 5 days late THEN you MUST NOT offer a goodwill credit.

=== credit_far ===
8d7
< - WHEN an order is more than 5 days late THEN you MUST offer a goodwill credit.
21a21
> - WHEN an order is more than 5 days late THEN you MUST offer a goodwill credit.

=== steps ===
18,21c18,21
< 1. Greet a customer by name.
< 2. Confirm an order number.
< 3. Answer a question.
< 4. Offer a survey.
---
> 1. Offer a survey.
> 2. Greet a customer by name.
> 3. Confirm an order number.
> 4. Answer a question.
Read credit_far as a support agent rather than as an engineer. The prohibition on goodwill credit now sits in the constraints and the one exception to it sits thirteen lines away, at the bottom of a numbered procedure, next to the survey step. Both rules still ship. The prover is still satisfied, because a guard holds them apart no matter how far apart they are on the page. The model reading it top-down has no such guarantee.

steps is the one an ops lead would ask for by name, and it is the clearest case of the compiler having nothing to say because the thing it would need to know is not in the language. A numbered list is prose to this checker. It reads 1. Greet a customer by name. as a bare imperative heading a line, proves what it can about it, and never asks whether 1 comes before 4 for a reason. Offering the survey first is a procedure that will annoy every customer who uses it, and it is 0 error, 0 warning, 0 info at A (97/100). If the order of a procedure is load-bearing, the order has to be stated as a rule the checker can read, not implied by a number. That is the same lesson as how to write a procedure your agent follows in order, arriving from the other direction.

role_last.tg is the only one of the six that gets any signal at all, and it is worth looking at because the signal is exactly right and it is not blocking. Moving the whole # Role section to the end of the file is a real practice, argued for on the grounds that the persona sits closest to the response. The compiler notices something narrower and more checkable.

tg check move/role_last.tg - one info, no blocki 1 info
$ npx typeglish check move/role_last.tg
move/role_last.tg:9:3  info   prompt/declared-later  `you` is declared on line 28, after its first use here: declare the identity before the rules that use it (the model reads top-down).

 1 file — 0 error, 0 warning, 1 info

$ diff .typeglish/dist/base.txt .typeglish/dist/move/role_last.txt
1,3d0
< # Role
< You are Wren, the order support agent for Ellesmere Tools.
<
21a19,21
>
> # Role
> You are Wren, the order support agent for Ellesmere Tools.
Thirteen rules address you before the file says who you is. The finding is an info, not an error, and that is the correct severity: the identity does bind, the artifact is valid, and the objection is about reading order rather than logic. It is also the only member of the six with any trace in the report, which makes it the exception that proves the rule.

§5The move that changes the answer

And then arms.tg, which is not like the other five. The other five move text the model weighs. This one moves text the compiler evaluates, and a $IF chain is first-true-wins.

move/arms.tg - the two bands, swapped
@@ courier: the higher band pays for a courier, the lower band does not
$IF @{order_total} is at least 100:
  - You MUST offer a free drop-off label.
$ELSE IF @{order_total} is at least 500:
  - You MUST offer a free courier collection.
$ELSE:
  - You MUST quote a standard return postage.
Same three arms, same three bodies, same annotation. The 500 arm is now unreachable: every order that satisfies it satisfies the 100 arm first, so the courier collection the higher band is supposed to get can never be reached.
tg build --vars - bind the input and read one line✗ 10c10
$ npx typeglish check move/arms.tg
 1 file — 0 error, 0 warning, 0 info

$ npx typeglish build base.tg --vars '{"order_total": 600}' --out-dir out/base
 built out/base/base.txt ← base.tg (aab9c453f4fc, full)

$ npx typeglish build move/arms.tg --vars '{"order_total": 600}' --out-dir out/arms
 built out/arms/move/arms.txt ← move/arms.tg (ac538ff92c3b, full)

$ diff out/base/base.txt out/arms/move/arms.txt
10c10
< - You MUST offer a free courier collection.
---
> - You MUST offer a free drop-off label.
One line, and it is a line with a cost attached. A 600-pound return gets a free courier on Tuesday's prompt and a drop-off label on the tidied one, and the tidied one reports nothing and scores identically. The --vars build is where a chain stops being prose and starts being a decision, which is also the whole argument of your system prompt has a compile time.

The natural next question is whether some flag catches this. It does not, and it is worth being explicit about that rather than leaving a reader to hope.

tg check --strict - the six quiet files, strictly
$ npx typeglish check move/arms.tg move/brevity.tg move/credit_far.tg \
    move/credit_order.tg move/steps.tg move/role_last.tg --strict
move/role_last.tg:9:3  info   prompt/declared-later  `you` is declared on line 28, after its first use here: declare the identity before the rules that use it (the model reads top-down).

 6 files — 0 error, 0 warning, 1 info
Strict mode raises the exit code on advisories; it does not invent findings. There is nothing for it to raise. The compiler cannot know which of two legal orders you meant, and a checker that guessed would be worse than one that stays quiet.

What you can do instead is remove the ordering from the design, so that no future tidy-up can move it. A $IF chain takes ordered predicates that may overlap. A $SWITCH ON a typed input takes members of a closed domain, which are unordered by construction, so shuffling the arms is not a semantic edit and cannot become one.

bands.tg - the same three outcomes, with no running order✓ B (87/100)
$REQUIRE variable return_band: one of courier, label, postage

# Role
@@ role: one sentence names the speaker, so every later "you" is the agent
You are Wren, the order support agent for Ellesmere Tools.

# Constraints
@@ courier: one arm per band, so no arm can shadow the arm below it
$SWITCH ON @{return_band}
  - courier::
    - You MUST offer a free courier collection.
  - label::
    - You MUST offer a free drop-off label.
  - postage::
    - You MUST quote a standard return postage.
The trade is real and worth naming: the band decision moves out of the prompt and into whatever computes return_band, and the grade drops to B (87/100) because three conditional arms are less enforceable than three flat rules. What you buy is that the arms cannot shadow each other, and a member added to the domain later fails loudly at every switch instead of silently taking the first arm that fits.
tg build --vars - shuffle the arms, bind the input✓ identical
# bands_moved.tg is bands.tg with the postage arm moved to the top

$ npx typeglish build bands.tg --vars '{"return_band":"courier"}' --out-dir a
 built a/bands.txt ← bands.tg (bc7327cd6c1d, full)

$ npx typeglish build bands_moved.tg --vars '{"return_band":"courier"}' --out-dir b
 built b/bands_moved.txt ← bands_moved.tg (bc7327cd6c1d, full)

# and for the other two members
label     bands.tg 9082444d696c    bands_moved.tg 9082444d696c
postage   bands.tg 8a4e2f9b0d3f    bands_moved.tg 8a4e2f9b0d3f
Three members, three pairs of identical hashes. Compare with the chain, where the same shuffle produced aab9c453f4fc against ac538ff92c3b. That is what "order does not matter here" looks like when it is a property of the construct rather than a hope about the reviewer.

§6Common questions

Does the order of instructions in a system prompt matter?
To the model, yes, and to a compiler, almost never. Moving eleven elements of a clean A (97/100) support prompt one at a time, three moves refuse to build and eight pass. Of the eight, two leave the deployed prompt byte-identical because the thing moved was control plane, and six change the bytes the model reads while holding the identical grade and reporting nothing at all. So a checker is the wrong instrument for this question. The instrument is the built artifact: diff it, because that diff is the only place a position-only edit exists.
Will typeglish check catch a rule I moved to the wrong place?
Only when the position itself is illegal or breaks a structure the language owns. Three moves out of eleven do that: a $REQUIRE dropped below content is structure/misplaced-command, a @@ note pushed off its statement is structure/stacked-annotation, and an $ELSE lifted above its $IF is structure/bad-switch plus a now non-exhaustive chain. All three refuse to build and cap the grade at F. The other eight are legal positions, and legal is all a checker is asked about. Moving an exception thirteen lines away from the rule it excepts, or putting the survey step first in a numbered procedure, is a legal position and a different prompt.
Can reordering the arms of a conditional change what my agent does?
Yes, and this is the one move in the set that is genuinely dangerous. A $IF chain is first-true-wins, so overlapping predicates make the order load-bearing: swap the at least 500 arm below the at least 100 arm and the 500 arm can never be reached. Both files check with nothing reported and both score A (97/100). Bind the input and the difference is one line, a free courier collection becoming a drop-off label on a 600-pound order. The structural fix is a $SWITCH ON a typed input, whose arms are members of a closed domain rather than ordered predicates: shuffle those arms and every bound build is byte-identical.
Is there a strict flag that catches a position-only prompt edit?
No. Running check --strict over the six moves that changed the deployed prompt reports the same thing the ordinary check does, one non-blocking info on one file, because strict raises the exit code on warnings rather than inventing new findings. There is no flag for this and there should not be one: the compiler cannot know which of two legal orders you meant. Gate on the artifact instead. Build the prompt, hash it, and require a review on any change to the hash, which is the one number that moves every time a position-only edit moves a byte the model reads.
Field note

Every ablation on this blog has been looking for the same boundary from a different side, and this one lands on it from the least expected direction. Deleting a line is caught when something else points at it. Stripping a guard is caught when it was holding two rules apart. Replacing a bound with the word it stood for is caught by nothing you run today. Moving a line is caught when the position was part of the language and missed when it was part of your intent, and the ratio here, three loud to eight quiet, is about what the others found too. The useful conclusion is not that the checker is weak. It is that the checker and the artifact answer different questions, and a prompt review that reads only the source diff is reading the wrong document: a position-only edit is invisible in check, invisible in score, and completely legible in a five-line artifact diff. If you are setting that gate up, the prompt is frozen, the artifact is not is the mechanics, and your headings decide which rules get checked is the sibling result for the structure the lines sit inside.

∿ washed up Sep 16, 2026 ∿