← Tidelines/Best practices

Rewrite: the paragraph that lives in three prompts

Billing, faults and retention all open with the same three house rules, because somebody pasted them. Every file checks clean, and the last line of the output explains why that is worth nothing.

by TypeGlish team8 min read#best-practices
Three copies. One file.

TL;DR Three agent prompts that share a house-rules paragraph by copy-paste check at 0 error, 0 warning, 0 info, and the checker tells you what that means: program: 3 independent files, no $IMPORT compositions, nothing co-checked. Move the paragraph into one house.tg that each root transcludes and the artifacts come back at the same three hashes with the same B (84/100) scores, so the refactor costs nothing the model can see, while a policy edit that used to be a copy-paste chore becomes 2 blocking logic/contradiction errors naming the other file and line.

The second bot is where this starts. The first agent gets built carefully, and then somebody needs one for faults, and the fastest correct thing to do is open the billing prompt, copy the top of it, and change the role line. Nobody is being lazy. Those three rules genuinely apply to every agent you run, they were signed off once, and there was no other place to put them. A quarter later there are three prompts, or six, and the paragraph is in all of them.

§1Before: three files, three green ticks

Northwind Broadband runs three agents. Each one is short, each one is well written, and each one opens with the same # Constraints block.

prompts/billing.tg - one of three✓ B (84/100)
# Role
You are the billing agent for Northwind Broadband.

# Constraints
- You MUST ask for an account number before you discuss an account.
- You MUST NOT waive a cancellation fee.
- You MUST keep every response to at most 4 sentences.

# Billing
- You MUST NOT state a specific price.
- You MUST read the last three charges back before you dispute one.
Lines 5 to 7 are the house rules. They are byte-identical in faults.tg and retention.tg, which each add two rules of their own below.
tg check prompts - output
 3 files — 0 error, 0 warning, 0 info
program: 3 independent files — no $IMPORT compositions, nothing co-checked

$ typeglish score prompts/billing.tg
prompts/billing.tg — B (84/100)  proven errors: none  tiers: base+z3
prompts/faults.tg — B (84/100)  proven errors: none  tiers: base+z3
prompts/retention.tg — B (84/100)  proven errors: none  tiers: base+z3
Three clean files at the same grade. The second line is the one to read twice.

That second line is not decoration and it is not a warning. It is the scope of the tick above it, written out: three independent files, no $IMPORT compositions, nothing co-checked. Each prompt was proven consistent with itself. Nothing whatsoever was proven about the three of them together, because as far as the compiler can tell they have nothing to do with each other. They are three documents that happen to share a directory and, invisibly, a paragraph.

§2The drift the tick cannot see

Copies do not stay copies. Two edits, three months apart, each one reasonable on the day it landed. Somebody loosened the length rule for billing, because a charge breakdown does not fit in four sentences. Somebody else added an exception to the fee rule for retention, because the save desk needed one.

the same three lines, three files, after two quarters✗ drifted
$ for f in prompts/*.tg; do echo "--- $f"; sed -n '5,7p' $f; done
--- prompts/billing.tg
- You MUST ask for an account number before you discuss an account.
- You MUST NOT waive a cancellation fee.
- You MUST keep every response to at most 6 sentences.
--- prompts/faults.tg
- You MUST ask for an account number before you discuss an account.
- You MUST NOT waive a cancellation fee.
- You MUST keep every response to at most 4 sentences.
--- prompts/retention.tg
- You MUST ask for an account number before you discuss an account.
- You MUST NOT waive a cancellation fee without a manager code.
- You MUST keep every response to at most 4 sentences.

$ typeglish check prompts
 3 files — 0 error, 0 warning, 0 info
program: 3 independent files — no $IMPORT compositions, nothing co-checked
There is now no such thing as "the house rules". There are three house rules, two of which nobody decided. Same tick, same three zeros.

Nothing here is a defect the checker is missing. Each file is internally consistent, and a length cap of six sentences is a perfectly legitimate rule to write. The defect is at the level above the file, and the file is the only unit anything in your pipeline looks at. No diagnostic compares two documents that never reference each other, no reviewer diffs a paragraph across a repo on a Tuesday, and the artifacts all build.

A rule in three files is three rules. You just have not been told which ones yet.

§3After: one file, three roots

The rewrite is small. Lift the paragraph into a file of its own, and have each agent point at it.

prompts/house.tg - the shared section✓ 0 findings
# Constraints
- You MUST ask for an account number before you discuss an account.
- You MUST NOT waive a cancellation fee.
- You MUST keep every response to at most 4 sentences.
A section and nothing else. It checks alone at 0 error, 0 warning, 0 info and scores B (83/100), which is a fine score for a file with no role line in it.
prompts/retention.tg - rewritten
<$CONFIG>
  $IMPORT file "house.tg" as house
</$CONFIG>

# Role
You are the retention agent for Northwind Broadband.

# Constraints
@<house.Constraints>

# Retention
- You MUST offer a loyalty discount.
- You MUST NOT accept a cancellation on a first contact.
Two new lines and one replaced paragraph, in each of the three agents. @<name> is the section pointer: angle brackets are the section type, the way @{name} is a variable and @[name] is a tool.

Before anything else, settle the question the person reviewing this will ask, which is whether the agents now behave differently. Build all three, before and after.

tg build, copy-paste vs. imported - output✓ same hashes
copy-paste                                     imported
 prompts/billing.txt   (025d3cd2ab03, full)    prompts/billing.txt   (025d3cd2ab03, full)
 prompts/faults.txt    (f0af4de34a40, full)    prompts/faults.txt    (f0af4de34a40, full)
 prompts/retention.txt (353add72e3ef, full)    prompts/retention.txt (353add72e3ef, full)

prompts/billing.tg   B (84/100)                    prompts/billing.tg   B (84/100)
prompts/faults.tg    B (84/100)                    prompts/faults.tg    B (84/100)
prompts/retention.tg B (84/100)                    prompts/retention.tg B (84/100)
Three identical short hashes and three identical grades. The transclusion inlines the section, so what the model receives is byte for byte what copy-paste produced. Nothing to deploy, nothing to re-test, nothing to explain to whoever owns the change window.

The score not moving is the honest part of this rewrite and worth saying plainly. The TG score is a property of one file, and the file the model reads did not change, so there is no grade to point at. The same thing happened when a whole grade of hygiene work compiled to identical artifacts, in the other direction. What did change is the shape of the program.

tg check prompts - after
 4 files — 0 error, 0 warning, 0 info
program: 3 roots — prompts/billing.tg (2 files), prompts/faults.tg (2 files),
                   prompts/retention.tg (2 files)
Four files, three roots, and each root is now a composition of two. Nothing co-checked is gone.

§4What the composition buys

The drift is dead by construction: there is one paragraph, so there is nothing to disagree with. The interesting part is the edit that comes next. Legal decides discounts have to go through the save desk, so a fourth rule goes into house.tg. That is one line, in one place, which was the whole point of the refactor.

tg check prompts, after one line added to house.tg - output✗ 2 errors
$ tail -1 prompts/house.tg
- You MUST NOT offer a loyalty discount.

prompts/house.tg:5:1      error  logic/contradiction  Logical conflict — "offer a loyalty
  discount" is both required and forbidden. Keep one, or scope the two rules so they cannot both
  apply (IF <condition> THEN ...). (conflicts with prompts/retention.tg line 9)
prompts/retention.tg:12:1 error  logic/contradiction  Logical conflict — "offer a loyalty
  discount" is both required and forbidden. (conflicts with prompts/house.tg line 5)

 4 files — 2 error, 0 warning, 0 info
Reported once per participating rule, each naming the other file and its line. Billing and faults stay clean, so the output is also a blast radius: of the three agents, exactly one has a rule that argues with the new policy.

One wrinkle in the attribution, since it will cost you a minute otherwise. The report on house.tg says conflicts with prompts/retention.tg line 9, and line 9 of retention.tg is the @<house.Constraints> pointer, not the discount rule. That is the position the house rule occupies inside the composed root. The report on the other side, at retention.tg:12, sits on the real rule and names house.tg line 5. Read the pair, not either one alone.

The obvious objection is that copy-paste would have caught this too, and on this exact edit it would have. Paste the new rule into all three files and retention.tg is 2 blocking errors on its own, no composition required:

prompts/retention.tg - the same edit, pasted in✗ 2 errors
# Role
You are the retention agent for Northwind Broadband.

# Constraints
- You MUST ask for an account number before you discuss an account.
- You MUST NOT waive a cancellation fee.
- You MUST keep every response to at most 4 sentences.
- You MUST NOT offer a loyalty discount.

# Retention
- You MUST offer a loyalty discount.
- You MUST NOT accept a cancellation on a first contact.
2 error, 0 warning, 0 info, on lines 8 and 11. Copy-paste catches it, on the condition that you pasted into all three.

That condition is the entire argument. Paste into two of the three and forget retention, which is the specific failure copy-paste actually produces, and the directory is back to 0 error, 0 warning, 0 info: the policy is simply not in the file that needed it, and there is nothing to detect. With one shared file there is no version of the edit that reaches two agents out of three. The proof is not stronger under composition, it is unavoidable, which is a different and better property.

§5Two limits worth knowing before you commit

First, and this is the one that will bite a CI pipeline: the cross-file pass runs on directory targets only. On the tree that just reported two blocking errors, naming a single file reports nothing at all.

tg check, directory vs. file, same tree - output
$ typeglish check prompts
 4 files — 2 error, 0 warning, 0 info          # exit 1

$ typeglish check prompts/retention.tg
 1 file — 0 error, 0 warning, 0 info           # exit 0

$ typeglish check prompts --no-program
 4 files — 0 error, 0 warning, 0 info          # exit 0
A CI step that loops over files and checks each one is a gate with the cross-file proofs switched off, and it will be green on the day the policy breaks. Point it at the directory. --no-program is the deliberate version of the same thing, for when you want the pre-composition behaviour.

Second, not every proof composes. The deontic contradiction above crosses the file boundary; a numeric bound, today, does not. Here is the controlled pair. Add a length rule to retention.tg that flatly contradicts the house cap, and the program pass is quiet:

tg check prompts, with a conflicting bound across files - output
$ tail -1 prompts/retention.tg
- You MUST write at least 6 sentences in every response.

 4 files — 0 error, 0 warning, 0 info
program: 3 roots — prompts/billing.tg (2 files), prompts/faults.tg (2 files),
                   prompts/retention.tg (2 files)
The house cap is at most 4 sentences. The composed root asks for at least six. Clean.
the same two rules, in one file✗ 2 errors
# Role
You are the retention agent for Northwind Broadband.

# Constraints
- You MUST keep every response to at most 4 sentences.
- You MUST write at least 6 sentences in every response.
logic/numeric twice, blocking, with Z3 naming both bounds: "at most 4 sentences" and "at least 6 sentences" can't both hold. Same two rules, one file boundary apart, two different verdicts.

So the rule of thumb for what to put in the shared file: the prohibitions and obligations that define the house travel well; the numbers are still a per-file property. If a bound genuinely has to hold across every agent, it needs a test or a review habit behind it, not a transclusion. That is the same discipline as pinning any resolved conflict with a $TEST, and it is worth writing down next to the import rather than assuming the composition covers it.

§6What the rewrite actually changed

  • Three copies to one. The house paragraph exists in exactly one place, so the two silent drifts from §2 are not edits anybody can make any more.
  • Same artifacts. 025d3cd2ab03, f0af4de34a40, 353add72e3ef, before and after. No behaviour change, no re-test, no deployment story.
  • Same grades. B (84/100) across all three, because the score reads the file the model reads and that file is identical. Nothing to claim here, so do not claim it.
  • A policy edit is now proven. One line into house.tg is either clean across every consumer or 2 blocking logic/contradiction errors naming the file and the line that objects.
  • The CI target changed. typeglish check prompts, not a loop over files. This is the step that makes the rest of it real.

§7Common questions

How do I share rules between several agent prompts?
Put the shared rules in their own .tg file and transclude them. The consumer declares $IMPORT file "house.tg" as house inside its <$CONFIG> block, then writes @<house.Constraints> where the paragraph used to sit. The build inlines the section, so the artifact is exactly what copy-paste produced: in a three-bot rewrite all three artifacts came back at the same short hashes as before, 025d3cd2ab03, f0af4de34a40 and 353add72e3ef, and every score stayed at B (84/100). The refactor is free at runtime. What you buy is that the copies can no longer disagree, because there is one copy.
Why does typeglish check say nothing co-checked?
Because the files you pointed it at are not joined by an $IMPORT, so there is no composition to prove anything about. Running typeglish check on a directory of three unrelated prompts prints program: 3 independent files, no $IMPORT compositions, nothing co-checked, and that line is the honest footnote on the green tick above it. Each file was proven consistent with itself and nothing was proven about the three together. Once the files import a shared one, the same command reports program: 3 roots and each root is proven against what it composes.
Will duplicated rules in different agent prompts get caught?
No. Duplication across files is invisible, and so is drift. Three prompts carrying the same house paragraph, where one copy has quietly become at most 6 sentences and another has grown a without a manager code clause, check at 0 error, 0 warning, 0 info as a directory. No diagnostic compares two files that never reference each other, and nothing in a normal CI run diffs a paragraph across a repo. The only structural fix is to stop having three copies.
Should my CI point typeglish check at a file or a directory?
At the directory, always. The cross-file program pass runs only on directory targets. On a repo with a genuine cross-file contradiction, typeglish check prompts is 2 blocking logic/contradiction errors and exit 1, while typeglish check prompts/retention.tg on the same tree is 0 error, 0 warning, 0 info and exit 0, because naming a single file checks that file alone. A per-file loop in CI is a gate with the cross-file proofs switched off. The flag --no-program does the same thing deliberately, which is what it is for.
Field note

The line to steal from this one is nothing co-checked. It is a compiler saying out loud what its green tick does and does not cover, and almost nothing else in a prompt toolchain does that. Most false greens are the same shape: a check that ran honestly over a smaller object than the one you had in your head, whether that is a file with five values missing from it or a directory the checker had no reason to treat as one program. The habit is to read the scope line before the verdict line. A tick is only as wide as the thing it was pointed at.

∿ washed up Aug 9, 2026 ∿