← Tidelines/Deep dives

Six rows, five prompts, zero new bytes

Every ablation on this blog so far has deleted something from a prompt. This one deletes from the file above the prompts, one row at a time, and watches five agents at once. The blast radius runs from nothing to every build in the repository, and the artifact never changes.

by TypeGlish team10 min read#deep-dives
One row up. Five prompts down.

TL;DR A repo-level glish.tgc holds the rows that teach the compiler your house vocabulary, and deleting them one at a time splits cleanly into three severities: the custom $CONFIG kind is 5 blocking errors and 0/5 builds, the tone word is 3 warnings that check --strict refuses to escalate, and two house nouns are advisory info. Every surviving build returns the same artifact hash it returned before, so the file that decides whether your prompts compile is invisible to an artifact-hash gate and absent from the build manifest.

Most teams meet this file the same way. You start with one agent prompt, it needs the word excess to mean the thing on a policy schedule rather than a general noun, so you write a line in the prompt that says so. A year later there are five prompts, one per channel, and that line has been copied five times, in four slightly different spellings, by three people. TypeGlish has a place to put it: a glish.tgc at the project root, discovered by walking up from whatever .tg file is being checked. It is a small file and it is genuinely load-bearing, which makes it a good ablation target, because the interesting question about any config file is which of its rows would be missed.

So: a claims desk with five prompts and one project file. Delete one row, check all five prompts, put the row back, repeat.

glish.tgc - the house vocabulary, six rows
# R1 $CONFIG desk one of chat, voice, email, whatsapp, assist
# R2 $CONFIG lexicon
       - undefined_adjectives:: warn
# R3 $CONFIG adjective unfussy
       - axis:: fuss
       - opposite:: fussy
# R4 $DEFINE word excess
       - class:: noun
       - unique:: true
# R5 $DEFINE word wallboard
       - class:: noun
       - unique:: true
# R6 $CONFIG language en
The # R1 labels are this post's, not the file's. R1 declares a $CONFIG kind the team invented so each prompt can record which desk it serves. R3 puts the brand's tone word on a named axis. R4 and R5 add two house nouns to the vocabulary so the excess and the wallboard need no introduction. R2 is a strictness dial and R6 is a language code.
prompts/chat.tg - one of the five✓ A (92/100)
<$CONFIG>
  $CONFIG desk chat
</$CONFIG>

# Role
You are a claims agent for Tarnbrook Insurance, a home insurer.

# Constraints
@@ tone: the house voice, defined once in glish.tgc
Your tone IS unfussy.
@@ excess: the figure on a policy schedule, never an estimate
- You MUST read the excess from a policy schedule before you quote it.
@@ brevity: three sentences keeps a chat reply scannable
- You MUST keep every reply to at most 3 sentences.
Fourteen lines, three of which lean on the project file, and none of which mention it. This blog machine-verifies its samples by running the real checker on each one in isolation, and this block is tagged as emitting config/unknown-kind rather than as clean for exactly the reason the post is about: pulled out of its repository, away from its glish.tgc, the file does not compile.

§1The baseline

Five files, checked as one program, with the project file intact.

tg check prompts - the control group✓ 0 error
$ npx typeglish check prompts
 5 files — 0 error, 0 warning, 0 info
program: 5 independent files — no $IMPORT compositions

$ npx typeglish check prompts --strict
 5 files — 0 error, 0 warning, 0 info

$ npx typeglish build prompts
 built .typeglish/dist/prompts/assist.txt ← prompts/assist.tg (7aa8c5f68cc6, full)
 built .typeglish/dist/prompts/chat.txt ← prompts/chat.tg (fbf3cb949378, full)
 built .typeglish/dist/prompts/email.txt ← prompts/email.tg (9cbc3bb03490, full)
 built .typeglish/dist/prompts/voice.txt ← prompts/voice.tg (6ada9794601a, full)
 built .typeglish/dist/prompts/whatsapp.txt ← prompts/whatsapp.tg (56c8de1efa98, full)
 built 5/5
Clean, clean under --strict, five artifacts. The scores are A (93/100) for assist, A (92/100) for chat, voice and whatsapp, and A (91/100) for email. Those five hashes are the numbers to keep an eye on, because they are about to stop moving in a way that is more interesting than if they moved.

§2Six deletions, three severities

One row out, check prompts, row back in. Six runs.

the leave-one-out table
row  what it declares            check prompts                files  build
---  --------------------------  ---------------------------  -----  -----
R1   $CONFIG desk (custom kind)  5 error  config/unknown-kind   5/5   0/5
R2   $CONFIG lexicon dial        0 error, 0 warning, 0 info     0/5   5/5
R3   $CONFIG adjective unfussy   3 warn   undefined-adjective   3/5   5/5
R4   $DEFINE word excess         4 info   unintroduced-definite 4/5   5/5
R5   $DEFINE word wallboard      1 info   unintroduced-definite 1/5   5/5
R6   $CONFIG language en         0 error, 0 warning, 0 info     0/5   5/5
---  --------------------------  ---------------------------  -----  -----
R2+R3  both rows out             3 error  undefined-adjective   3/5   2/5
One row is a full stop, one is a warning, two are advisory, two are silent, and the last line is the one this post is really about. The severity ordering is not the ordering you would guess from reading the file, and it is not the ordering of how much each row matters to the model either, because none of them reach the model at all.

R1, the custom kind, is the only blocking row. Each of the five prompts opens with $CONFIG desk <name>, a kind the team invented, and the single row in glish.tgc is what makes that kind exist. Take it out and every prompt in the repository stops compiling, at line 2, with a diagnostic that names the missing file.

tg check prompts - R1 removed✗ 5 error
$ npx typeglish check prompts
prompts/assist.tg:2:3    error  config/unknown-kind  $CONFIG desk isn't a known kind - the line was stripped and configured nothing. Known kinds come from the built-ins + your glish.tgc.
prompts/chat.tg:2:3      error  config/unknown-kind  $CONFIG desk isn't a known kind - the line was stripped and configured nothing. Known kinds come from the built-ins + your glish.tgc.
prompts/email.tg:2:3     error  config/unknown-kind  $CONFIG desk isn't a known kind - the line was stripped and configured nothing. Known kinds come from the built-ins + your glish.tgc.
prompts/voice.tg:2:3     error  config/unknown-kind  $CONFIG desk isn't a known kind - the line was stripped and configured nothing. Known kinds come from the built-ins + your glish.tgc.
prompts/whatsapp.tg:2:3  error  config/unknown-kind  $CONFIG desk isn't a known kind - the line was stripped and configured nothing. Known kinds come from the built-ins + your glish.tgc.

 5 files — 5 error, 0 warning, 0 info

$ npx typeglish build prompts
 built 0/5 — 5 refused (each named above, nothing written for them)
The line was stripped and configured nothing is the contract worth internalising: a $-command that silently does nothing is an error rather than a shrug. Note what did not move. tg score on the same five files still reports A (92/100) and A (93/100), unchanged, with proven errors: none, because this is a structural defect rather than a proved one and the grade cap does not apply to it. Five refused builds and an A is a combination worth knowing about before you gate a deploy on the grade.

There is no way to move this row into the prompts. Five of the six rows here have an in-file equivalent: write them inside a prompt's own <$CONFIG> block and that prompt is self-contained, at the cost of twelve lines of preamble in every file. The custom kind is different, because the kind has to exist before the line that sets it is read.

tg check - a prompt trying to declare its own kind✗ 2 error
$ npx typeglish check self.tg   # no glish.tgc anywhere above it
self.tg:2:3  error  config/unknown-kind  $CONFIG desk isn't a known kind - the line was stripped and configured nothing. Known kinds come from the built-ins + your glish.tgc.
self.tg:3:3  error  config/unknown-kind  $CONFIG desk isn't a known kind - the line was stripped and configured nothing. Known kinds come from the built-ins + your glish.tgc.

 1 file — 2 error, 0 warning, 0 info
The declaration and the use, on consecutive lines, both refused. One honest limit while we are here: R1 legalises the kind and not the value. Setting $CONFIG desk retention in a prompt, against a declared domain of chat, voice, email, whatsapp and assist, is 0 error, 0 warning, 0 info. It is the same boundary the input bag has, where the keys are checked and the values are not.

R4 and R5, the house nouns, are advisory. Both rows do the same job for different words. $DEFINE word excess with - unique:: true declares that the lemma names exactly one thing, so the excess in a rule is a definite reference the document does not have to introduce. Remove the row and four of the five prompts pick up an info.

tg check prompts - R4 removed✓ 4 info
$ npx typeglish check prompts
prompts/chat.tg:12:1      info   prompt/unintroduced-definite  "the excess" retrieves something this document never introduces - a model must guess which excess is meant. Introduce it on another line ("You manage an excess.") or name it outright.
prompts/email.tg:10:1     info   prompt/unintroduced-definite  "the excess" retrieves something this document never introduces - a model must guess which excess is meant. Introduce it on another line ("You manage an excess.") or name it outright.
prompts/voice.tg:12:1     info   prompt/unintroduced-definite  "the excess" retrieves something this document never introduces - a model must guess which excess is meant. Introduce it on another line ("You manage an excess.") or name it outright.
prompts/whatsapp.tg:12:1  info   prompt/unintroduced-definite  "the excess" retrieves something this document never introduces - a model must guess which excess is meant. Introduce it on another line ("You manage an excess.") or name it outright.

 5 files — 0 error, 0 warning, 4 info
R5 is the same finding with a blast radius of one: only the agent-assist prompt reads the wallboard, so removing it is a single info on prompts/assist.tg:10:1. Four files against one, from two rows written identically, which is the argument for the project file in one number: the cost of a row is paid once and the benefit scales with however many prompts happen to use the word. What the info is telling the model is covered in your agent has never heard of a wallboard.

§3The row that sets the volume

R2 and R6 are silent. Delete the $CONFIG lexicon dial and the five prompts check at 0 error, 0 warning, 0 info. Delete the language code and the same. In an ablation that is a result, and the correct reading of it is narrow: nothing in these five prompts reads those rows. A row you cannot make fail is a row nothing is currently checking, and the useful follow-up question is whether you expected it to be load-bearing.

For R6, no. For R2, very much yes, and finding out why takes a second deletion.

R3, the tone word, is where the file earns its place. The brand voice is one adjective. Three of the five prompts open their Constraints with Your tone IS unfussy., and unfussy is not a word the compiler's own lexicon knows, so R3 is what puts it on a named axis with a named opposite. Take R3 out on its own and the result is three warnings.

tg check prompts - R3 removed3 warning
$ npx typeglish check prompts
prompts/chat.tg:10:1  warn   prompt/undefined-adjective  "unfussy" is not in the vocabulary — the claim is INVISIBLE to the checker. Define it:
$CONFIG adjective unfussy
  - axis:: <name>
  - opposite:: <pole>
…or relax the dial ($CONFIG lexicon → undefined_adjectives).
# the same finding on prompts/voice.tg:10:1 and prompts/whatsapp.tg:10:1

 5 files — 0 error, 3 warning, 0 info

$ npx typeglish check prompts --strict
 5 files — 0 error, 3 warning, 0 info
# exit 0

$ npx typeglish build prompts
 built 5/5
Read the second command twice. --strict escalates every IMPORTANT finding to a blocking error, and it does not escalate this one: same counts, exit 0, five artifacts written. Three prompts now carry a brand-voice claim the checker has told you it cannot see, and there is no exit code anywhere in the toolchain that stops it. If you want a gate here it has to be the warning count, or the dial.

Which is what R2 was doing all along. The dial is silent on its own because it changes nothing while every adjective in the repo is defined. What it changes is how loud the absence of R3 is. Delete both rows and the same three lines are blocking errors.

tg check prompts - R2 and R3 both removed✗ 3 error
$ npx typeglish check prompts
prompts/whatsapp.tg:10:1  error  prompt/undefined-adjective  "unfussy" is not in the vocabulary — the claim is INVISIBLE to the checker. Define it: …

 5 files — 3 error, 0 warning, 0 info
prompt/undefined-adjective is an error by default. Somebody wrote - undefined_adjectives:: warn in the project file at some point, almost certainly to unblock a build on a Friday, and the row has been quietly converting a class of blocking error into a class of warning for every prompt in the repository ever since. That is the most consequential row in the file and the only one whose deletion, on its own, does nothing at all.

Worth being precise about what R3 buys, because it is less than you might hope and more than nothing. It is not the only thing standing between you and a contradiction. Add Your tone IS fussy. to the chat prompt six lines below the first claim and both versions refuse to compile, but for different reasons.

tg check - one tone slot, two values, with and without the axis
# with R3: the axis reading
prompts/chat.tg:10:1  error  logic/contradiction  Conflicts with line 10. Contradiction — "you.tone" is assigned "unfussy" and "fussy", opposite sides of fuss (one truth per dimension).
 1 file — 2 error, 0 warning, 0 info

# without R3: the slot reading
prompts/chat.tg:16:1  error  logic/contradiction  Conflicts with line 7. Contradiction — single-valued "you.tone" is assigned 2 different values ("unfussy", "fussy"). Keep one, or hold several with a list ("… is unfussy and fussy").
 1 file — 2 error, 1 warning, 0 info
Same count, different proof. The slot mechanism catches two different values on one attribute whatever the words are, so the flat contradiction survives R3's removal. What the axis adds is the reason: it knows the two words are opposite poles of one dimension rather than merely unequal strings, which is the machinery a brand voice that cancels itself out runs on. The honest summary is that R3 upgrades a string comparison into a claim about a dimension, and that in this repo the cheaper proof happened to cover the same pair.

§4Nothing in the artifact moved

Here is the finding that makes this file worth a post rather than a paragraph. Take the four ablations that still build, and compare the artifacts.

tg build prompts - four ablations, one set of hashes
                 assist        chat          email         voice         whatsapp
baseline         7aa8c5f68cc6  fbf3cb949378  9cbc3bb03490  6ada9794601a  56c8de1efa98
minus R3         7aa8c5f68cc6  fbf3cb949378  9cbc3bb03490  6ada9794601a  56c8de1efa98
minus R4         7aa8c5f68cc6  fbf3cb949378  9cbc3bb03490  6ada9794601a  56c8de1efa98
minus R5         7aa8c5f68cc6  fbf3cb949378  9cbc3bb03490  6ada9794601a  56c8de1efa98
minus R6         7aa8c5f68cc6  fbf3cb949378  9cbc3bb03490  6ada9794601a  56c8de1efa98

score, chat.tg   A (92/100)    A (92/100) baseline · B (85/100) minus R3 · A (90/100) minus R4
Twenty builds, five distinct hashes, built 5/5 every time. Every row in glish.tgc is control-plane: it configures the compiler and is stripped before a byte is emitted, so the prompt the model receives is identical whether the house vocabulary is defined or not. The grade is not. chat.tg goes from A (92/100) to B (85/100) under minus R3, with style collapsing from 100 to 25 and the ledger row reading prompt/undefined-adjective −1, and to A (90/100) under minus R4 with style at 81. A letter grade moved and not one byte of the deployed prompt did.

Which lands on the gate. If your CI freezes prompts by artifact hash, and a lot of them sensibly do, this file is outside it in both directions. The manifest is explicit about what it records.

.typeglish/build-manifest.json - what a build remembers
{
  "source": "prompts/chat.tg",
  "sourceSha256": "fbf3cb949378…",
  "artifact": ".typeglish/dist/prompts/chat.txt",
  "artifactSha256": "fbf3cb949378…",
  "vars": null,
  "typeglish": "0.9.0",
  "checkMode": "full",
  "report": { "ok": true, "counts": { "error": 0, "warn": 0, "info": 0 } }
}
Two hashes for the prompt, the compiler version, the check tier, the counts, and no mention of glish.tgc at all. So a hash-based freeze cannot tell you that somebody changed the file deciding what compiles, and a hash-based diff review will show nothing when they do. The thing that does move is report.counts: the same prompt goes from 0 info to 1 info at an unchanged artifactSha256, which is the one signal available. That is the same source-versus-artifact boundary the prompt is frozen, the artifact is not is about, with the project file sitting one level further out than either.

Three things follow, and they are cheap. Check the whole prompts directory in CI rather than the files a pull request touched, because a one-line diff to glish.tgc is a change to five prompts and touches none of them. Gate on the warning count as well as the error count, because the dial can turn a blocking class into a warning class for the entire repository and --strict will not turn it back. And review the project file as a spec: six rows, one of which is load-bearing for every build, one of which is the reason your brand voice is enforced, and one of which is a strictness decision somebody made once and nobody has read since.

§5Common questions

What is glish.tgc and where does it go?
It is the project file the compiler discovers by walking up from the .tg file it is checking, so one glish.tgc at the repository root serves every prompt underneath it. It holds the rows that extend the language rather than the prompt: $CONFIG adjective to put a tone word on a named axis, $DEFINE word to add a noun to the vocabulary, $CONFIG lexicon to set the strictness dials, and a declaration of any custom $CONFIG kind your team invents. Five of those six kinds of row can also be written inside a prompt's own <$CONFIG> block, at the cost of repeating them in every file. The custom kind cannot: declaring $CONFIG desk one of chat, voice inside a prompt is 2 blocking config/unknown-kind errors, because the kind has to exist before the line that sets it is read. That one row has no in-file equivalent at all.
How do I define my company's tone words so a prompt checker understands them?
With $CONFIG adjective, giving the word an axis and an opposite pole, and put it in glish.tgc so every prompt in the repo reads the same definition. An adjective in a claim seat that sits on no named axis is prompt/undefined-adjective, an error by default, whose message says the claim is INVISIBLE to the checker. With the row present, Your tone IS unfussy is a claim the compiler holds, and asserting the opposite pole later is a blocking logic/contradiction reading opposite sides of fuss (one truth per dimension). Do not reach for the $CONFIG lexicon dial to make the finding go away. Setting undefined_adjectives to warn leaves the word undefined, drops the same file from A (92/100) to B (85/100) with style at 25, and check --strict will not escalate it back.
Why does my prompt compile on my machine but not in CI?
Because the thing that decides whether it compiles may not be in the prompt. The compiler walks up from the .tg file looking for glish.tgc, so a prompt that is clean in a checkout with that file is a different verdict in a checkout without it. Deleting one row, a custom $CONFIG kind five prompts set, takes a repository from 5 files, 0 error, 0 warning, 0 info to 5 blocking config/unknown-kind errors at exit 1 and 0/5 builds, and the diagnostic says so outright: known kinds come from the built-ins plus your glish.tgc. Nothing in any prompt file changed. Commit the project file, review it like a spec, and check the whole prompts directory in CI rather than the file somebody edited.
Does changing the project config change the prompt the model reads?
No, and that is the part to build a gate around. Every row in glish.tgc is control-plane: it configures the compiler and is stripped before anything is emitted. Across four ablations that still build, all five artifact hashes come back byte-identical, 7aa8c5f68cc6, fbf3cb949378, 9cbc3bb03490, 6ada9794601a and 56c8de1efa98, while one prompt moves from A (92/100) to B (85/100) and another from A (92/100) to A (90/100). The build manifest records sourceSha256 and artifactSha256 for the prompt and says nothing at all about the project file, so an artifact-hash freeze gate cannot see a change to the file that decides what compiles. Gate on the check of the whole directory, and on the warning count as well as the error count.
Field note

The reason to run an ablation over a config file rather than a prompt is that config files acquire rows and never lose them. Every row in this one was added by somebody solving a real problem on a real afternoon, and five of the six are still doing exactly what that person intended. The sixth is the dial, and it is a fair bet that whoever wrote - undefined_adjectives:: warn meant it to be temporary. Nothing in the repository has reminded anybody since: it produces no findings, it moves no score, it changes no artifact, and its entire effect is to make one class of error quieter across five prompts at once. That is the general shape of the risk in a file above your prompts. Its rows are invisible in the diffs people actually read, absent from the hash people actually gate on, and scoped to everything. The counterweight is that it is six lines long, which makes it one of the few specs in a CX stack you can genuinely read in full at review time, and worth doing once a quarter with the question this ablation asks: if this row vanished, what would tell me? If you are setting one up, your agent has never heard of a wallboard is the piece on which glossary goes where, and gold and silver conflict, priority and standard do not is the closed-set half of the same vocabulary problem, including why the set is better written in the prompt than declared as a domain.

∿ washed up Sep 11, 2026 ∿