← Tidelines/Deep dives

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.

by TypeGlish team6 min read#deep-dives
Some lines never ship.

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.

support.tg✕ won't compile
# 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.
The last line reads fine in English, which is exactly the problem. @{vip} is a compile-time condition, so the compiler cannot render it into prose. Ship this and the model would read a dangling {vip}.
tg check - output
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
The checker does not warn and hope. It refuses to build, because a leaked compile-time symbol is not a style preference, it is a token the model was never supposed to see.

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.

support.tg✓ A (100/100)
# 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.
Nine lines of this file are compile-time only: two @@ 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.

tg build - the artifact the model reads
 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.
Half the source file did not make the crossing, and that is by design. Deploy this artifact, never the .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.

tg score - output
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
Annotation and structure are hygiene facets: they grade the compile-time plane, the part the model never sees. Enforceability and consistency grade the runtime plane. One score, two audiences.
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 $DEFINE or a switch predicate is compile-time; the moment you want to reference it from an instruction, reach for a $IF or a when= guard, never a bare @{} in a sentence.
  • Read the artifact before you ship. build writes 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 .tg is 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.
Field note

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 $DEFINE and $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 $DEFINE is 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 with structure/define-in-prose. Use the define inside a $IF / $SWITCH condition or a when= 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 .tg file 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.
∿ washed up Jul 19, 2026 ∿