Your system prompt has a compile time
You write one file, but the model reads a different one. Part of every prompt is instructions it consumes at runtime; part is scaffolding that exists only for you and the compiler. The bugs live on the border, where a line you wrote for yourself gets shipped to the model by accident.
TL;DR A system prompt has two planes: the runtime prose the model reads, and the compile-time scaffolding (comments, @@ notes, <$CONFIG>, and every $-command) that it never sees. Keeping them straight is what stops a compiler-only symbol from leaking into the shipped prompt as a dangling {placeholder}. Build the file and read the artifact to see exactly what the model receives.
Source code has a compile step, and everyone who writes it carries a quiet mental model of what survives it: comments do not run, #ifdef branches get chosen, macros expand. Prompts have the same split, but almost no one writes them with it in mind, so the file you edit and the text the model reads get treated as one thing. They are not. A TypeGlish .tg file is source; what a prompt-taking system receives is a build artifact. Most of the frustrating "the model is ignoring my config" bugs are really border bugs: something you meant for compile time ended up at runtime, or the reverse.
§1Two planes in one file
Every line you write lands on one of two planes. The runtime plane is what the model actually reads: the role prose, the MUST/NEVER rules, the declarations that render. The compile-time plane is everything that exists to help you author and the compiler check, and is gone before the model sees a token. That second plane is larger than most builders realize:
- Line comments (
//) and block comments (/* … */) - documentation, never sent. - Annotations (
@@ why this rule exists) - authoring metadata for the line below, never compiled. - The
<$CONFIG>section - foldable scaffolding whose whole extent never reaches the model. - Every
$-command -$DEFINE,$IMPORT,$REQUIRE,$CONFIG. They are compiler directives, not content. - The losing arms of a
$IF/$SWITCH- the compiler evaluates the selection and only the winning branch's prose ships.
This is why a heavily commented, heavily configured TypeGlish prompt can still be lean for the model: you pay for annotation and structure on the compile-time plane, and the runtime plane stays exactly as long as the instructions require. The trouble starts only when a symbol from one plane shows up on the other.
§2The line that leaks
Here is the border bug in its natural habitat. A $DEFINE names a compile-time condition, vip, meaning "the tier input equals gold." That belongs on the compile-time plane. Then a normal instruction line points at it, dragging a compiler-only symbol into runtime prose.
# Role You are a support agent for an online electronics retailer. <$CONFIG> $REQUIRE variable tier: one of gold, standard $DEFINE vip AS @{tier} is equal to gold </$CONFIG> # Constraints - MUST reply in at most 3 sentences. - MUST offer priority handling to @{vip} customers.
@{vip} is a compile-time condition, so the compiler cannot render it into prose. Ship this and the model would read a dangling {vip}.support.tg:11:35 error structure/define-in-prose Compile-time condition in prose - "vip" is a $DEFINE, resolved by the compiler, so the model would read a dangling {vip} placeholder here. Use it in a $IF / $SWITCH ON condition or a when= guard; for a value the host fills at runtime, declare "$REQUIRE variable vip" instead. ✗ 1 file - 1 error, 0 warning, 0 info
Notice what the diagnostic understands that a spellchecker never could: it knows vip lives on the compile-time plane, it knows the instruction line lives on the runtime plane, and it knows the crossing is illegal. The fix is not to reword the sentence. It is to keep vip where it belongs, as a condition the compiler resolves.
§3What the model actually reads
Move the decision into a $IF and the symbol stays on the compile-time plane. The compiler evaluates the condition and emits only the branch that wins, so the runtime plane never contains vip at all, just the sentence the customer's tier earned.
# Role @@ role: scope the agent so "helpful" has a domain You are a support agent for an online electronics retailer. <$CONFIG> $REQUIRE variable tier: one of gold, standard $DEFINE vip AS @{tier} is equal to gold </$CONFIG> # Constraints @@ brevity: replies over three sentences get skimmed - MUST reply in at most 3 sentences. @@ vip_path: gold-tier customers pay for priority, so honor it $IF @{vip}: Offer priority handling and a direct callback number. $ELSE: Offer standard handling.
@@ notes, the four-line <$CONFIG> block, and the $IF/$ELSE control words. The model reads none of them.Build it, and the artifact is the proof. This is the runtime plane, verbatim, for a gold-tier customer: the config is gone, the notes are gone, the conditional has collapsed to its winning line.
✓ built .typeglish/dist/support.txt ← support.tg (d5e02163b91e, full) # --- support.txt (what the model receives) --- # Role You are a support agent for an online electronics retailer. # Constraints - MUST reply in at most 3 sentences. Offer priority handling and a direct callback number.
.tg, so the text you tested is the text that runs.The score puts a number on the same split. It reports two planes: a runtime score for what the model reads, and a hygiene score for the source-only material. A prompt can be immaculate on one plane and a mess on the other, and reading them separately tells you which problem you have.
support.tg - A (100/100) proven errors: none tiers: base+z3
planes runtime 100 (what the model reads) · hygiene 100 (source only)
facets enforceability 100 x.26 · consistency 100 x.21 · structure 100 x.16 (hygiene) · annotation 100 x.16 (hygiene) · style 100 x.11 · security 100 x.11
The file you edit is not the prompt the model reads. Confusing the two is where the leaks come from.
§4Writing to the boundary on purpose
Once you can see the two planes, a few habits keep symbols on the right one and keep surprises out of production.
- Comment and annotate freely. Compile-time notes cost the model nothing, so the reason a rule exists belongs in an
@@note, not in the rule text where it becomes runtime tokens the model has to weigh. - Keep decisions in conditions, not prose. A
$DEFINEor a switch predicate is compile-time; the moment you want to reference it from an instruction, reach for a$IFor awhen=guard, never a bare@{}in a sentence. - Read the artifact before you ship.
buildwrites the exact runtime plane to a file. If a config line, a comment, or a stray placeholder shows up there, you found a leak the cheap way. - Deploy the artifact, not the source. The
.tgis for humans and the compiler. Sending it to the model ships the whole compile-time plane as noise, which is the worst of both worlds.
The build command, $DEFINE, and the two-plane score all shipped in TypeGlish 0.1.0. If §2 felt like a type error, that is the right instinct: it is the same idea as few-shot examples are a type system you haven't written down, applied to the boundary between what the compiler resolves and what the model reads. And the reason to keep scaffolding off the runtime plane is the same reason to keep the runtime plane lean in the first place, which is the whole argument in the rewrite of a bloated support prompt.
FAQCommon questions
- What does the model actually see in my system prompt?
- Only the runtime plane: the prose, rules, and declarations that render. Everything else is compile-time scaffolding that gets stripped before the model reads a word: line comments (//),
@@annotations,<$CONFIG>blocks, and every$-command like$DEFINEand$IMPORT. The reliable way to know what ships is to build the prompt and read the artifact, which is the exact text the model receives. - Do comments and $CONFIG blocks in a prompt get sent to the model?
- No. In TypeGlish, // comments,
@@notes, and the whole<$CONFIG>section never reach the model, and neither do the$-commands ($DEFINE,$IMPORT,$REQUIRE). They exist for you and the compiler. That is the point of the compile-time plane: you can annotate and configure a prompt heavily without adding a single token to what the model actually reads. - Why is my agent reading a literal placeholder like {vip} in its instructions?
- Because a compile-time symbol leaked into runtime prose. A
$DEFINEis resolved by the compiler, not the model, so pointing at it from a normal instruction line leaves a dangling{vip}placeholder in the shipped prompt. TypeGlish blocks this at compile time withstructure/define-in-prose. Use the define inside a$IF/$SWITCHcondition or awhen=guard instead, or, if the host fills the value at runtime, declare it with$REQUIRE variable. - What is the difference between prompt source and the compiled prompt?
- The source is the
.tgfile you keep in git: it carries comments, config, conditionals, and everything you need to reason about the prompt. The compiled prompt is the build artifact, the plain text the model receives after the compiler strips the scaffolding and resolves the conditionals. Deploy the artifact, never the.tg, so that what you tested is exactly what runs.