← Tidelines/Best practices

Nobody who wrote this prompt still works here

The handover was a Slack thread and a link to a file. Everything the file was for lives in somebody else's head, and the bot is answering customers this morning either way.

by TypeGlish team8 min read#best-practices
Somebody else wrote this.

TL;DR Nine checks for the first day with an agent prompt you did not write, each with a command behind it. Do not start by reading it: build it, because the artifact is the text the model gets and an unbound conditional ships both branches as one line of prose. Then read the advisories as the previous author's unfinished decisions, the unused declarations as features that were started and abandoned, and the coverage number as how much of it was ever pinned. The file here exits 0 and scores D (53/100).

It is a normal Tuesday. Somebody moved teams, and the agent that handles delivery and billing questions is now yours. You have the repo, you have a file, and you have a stakeholder who wants a new rule in it by Thursday. Nobody is available to explain what any of it was for.

The instinct is to read the prompt end to end, and that is the one thing that will not help yet, because reading tells you what somebody meant and the problem is everything they did not finish. This list is the order to run the commands in, and what each one tells you that the prose cannot. It is not the launch checklist, which assumed you wrote the thing, and it is not the incident list, which assumed something had already gone wrong. Here nothing is wrong. You just do not know anything.

support.tg - what you inherited✗ D (53/100)
<$CONFIG>
  $CONFIG model claude-opus-4-8
    - via:: anthropic
  $REQUIRE variable tier: one of standard, premium
  $REQUIRE variable locale: string
  $IMPORT tool get_order
</$CONFIG>

$SERVICE anthropic
  - base:: https://api.anthropic.com
  - headers::
    - x-api-key:: @{env.ANTHROPIC_API_KEY}

$DEFINE vip AS @{tier} is equal to premium

$TOOL send_survey
  - description:: Sends the post-contact survey.
  - input::
    - ticket_id:: string
  - request:: POST anthropic /survey

# Role
You are a support agent for Cobalt Mobile.

# Constraints
- You SHOULD try to confirm the account before you discuss a bill.
- You MUST call @[get_order] before you state a delivery date.
- You MUST answer a delivery question in at most 3 sentences.

$IF @{tier} is equal to premium: You MUST promise a reply within 4 hours.
$ELSE: You MUST promise a reply within 2 working days.
A plausible file. Somebody typed a locale variable, a vip condition and a survey tool, and none of the three is referenced anywhere. Three rules, a tier conditional, no tests, no notes. It exits 0, which is why it has been like this for a year.

§1Find out what actually ships

1. Prove the file is the prompt. Before any of it means anything, establish that this document is what the runtime serves. build writes the artifact and records both hashes in the manifest, so you have something to compare against whatever your deploy path is holding.

tg build support.tg - and the manifest row
$ npx typeglish build support.tg
 built .typeglish/dist/support.txt ← support.tg (a5efde574729, full)

$ jq '.builds[0]' .typeglish/build-manifest.json
{
  "source": "support.tg",
  "sourceSha256": "ac0f06cbe544553385c3b8e42ff8fbeb7c97d79cb5e8065996b91f6e7ce95554",
  "artifact": ".typeglish/dist/support.txt",
  "artifactSha256": "a5efde57472909b5894308e85174d52ac8c623e73c73ac51115004a5e8523079",
  "vars": null,
  "typeglish": "0.7.2",
  "checkMode": "full",
  "report": {
    "ok": true,
    "counts": { "error": 0, "warn": 5, "info": 3 },
    "strict": false
  },
  "builtAt": "2026-08-10T10:25:15.069Z"
}
Two hashes, because they answer different questions. sourceSha256 pins the file in git; artifactSha256 pins the text the model receives. If the deployed prompt does not match the second one, somebody hand-edited production and this whole exercise is about a document nobody is reading.

2. Read the artifact before you read the file. This is the step that changes what you think you inherited, and it takes one command. The artifact is not a tidied copy of the source: scaffolding is gone, pointers are flattened, and anything the compiler could not resolve has been turned into prose.

the built artifact - what the model receives
# Role
You are a support agent for Cobalt Mobile.

# Constraints
- You SHOULD try to confirm the account before you discuss a bill.
- You MUST call get_order before you state a delivery date.
- You MUST answer a delivery question in at most 3 sentences.

If tier is premium: You MUST promise a reply within 4 hours. Otherwise: You MUST promise a reply within 2 working days.
The last line is the find. With no tier bound, the deterministic chain compiles to a single line of English carrying both SLAs, so the live agent is being told about the 4-hour promise and the 2-day promise in one breath and left to work out which applies. The survey tool is absent entirely. So is the vip condition.

3. Run fmt --check before check. It is the cheap one and it belongs first, because formatting defects hide correctness defects: a hard-wrapped rule is two statements to the compiler, and a bound that never assembled cannot be contradicted. A clean answer here means the findings you are about to read are about content.

tg fmt support.tg --check - output
$ npx typeglish fmt support.tg --check
 1 file already formatted
$ echo $?
0
Nothing to join and nothing to split. Worth knowing on day one rather than assuming: an inherited file that has been through a wiki, a flow builder or somebody's editor usually has at least one wrap, and fmt would report the joins as a count rather than a diff you have to eyeball.

§2Read the report as an inheritance

4. Run check and treat every advisory as an unfinished decision. On a prompt you wrote, advisories are a to-do list you have chosen to defer. On a prompt you inherited they are archaeology: each one is a place the previous author started something and stopped.

tg check support.tg - output
support.tg:5:3   warn   structure/unused-import  Required variable "locale" is never used.
support.tg:5:21  warn   clarity/unused-variable  $REQUIRE variable "locale" is never used
  — no @{locale} reference fills it. Remove it, or reference it in the prompt.
support.tg:14:1  warn   structure/unused-define  $DEFINE vip is never used.
support.tg:14:9  warn   clarity/unused-definition  $DEFINE "vip" is never used — no $IF,
  $SWITCH, or guard references it. Remove it, or wire the condition in.
support.tg:16:1  warn   structure/unused-tool  Defined tool "send_survey" is never used in the
  prompt — the model cannot discover a tool no rule mentions. Reference it in a rule
  (e.g. "call send_survey when ...") or delete the definition.
support.tg:26:14 info   prompt/hedging  Hedging — "try to" turns this instruction into a
  suggestion the model may skip. Delete the hedge, or commit to a modal (MUST / NEVER /
  SHOULD).
support.tg:26:1  info   prompt/unintroduced-definite  "the account" retrieves something this
  document never introduces - a model must guess which account is meant. Introduce it on
  another line ("You manage an account.") or name it outright.
support.tg:30:1  info   structure/switch-shaped-chain  Every arm tests @{tier} for one member
  — this chain is a $SWITCH in disguise. The block form proves coverage by construction
  (a member added to the domain later fails loudly here instead of falling through).
  Replace the chain with:
$SWITCH ON @{tier}
  - premium:: You MUST promise a reply within 4 hours.
  - otherwise:: You MUST promise a reply within 2 working days.

 1 file — 0 error, 5 warning, 3 info
Eight findings and a tick. Five of the warnings are the same story told three times: something was declared and never wired in. The locale variable was somebody starting a multilingual build. vip was somebody starting a tier rule. send_survey was somebody starting post-contact surveying. None of the three shipped, and none of the three is visible in the artifact.

5. Sort the dead surface by what it implies, not by severity. The three unused declarations are all warnings and all cost the same on the ledger, but they mean different things to you on Tuesday. An unused variable is an interface your host may or may not still be filling. An unused $DEFINE is a condition somebody worked out and did not apply. An unused $TOOL is the loudest of the three, and the message says exactly why.

The model cannot discover a tool no rule mentions. It is in your file, in your review, and in nobody's context window.

Resist deleting them on day one. Each one is a question for whoever asked for it, and the deletion is a one-line commit whenever the answer comes back. What you should not do is leave them and assume they work, which is the state you found them in.

6. Read the arms against the domain. A typed input plus a chain or a switch is the one place in an inherited file where a stale edit becomes a compile error rather than a silent gap, so it is worth running deliberately. Here is the same prompt as somebody left it after the pricing team renamed a tier.

tiers.tg - an arm for a tier that no longer exists✗ 1 error
<$CONFIG>
  $REQUIRE variable tier: one of standard, premium
</$CONFIG>

# Role
You are a support agent for Cobalt Mobile.

# Constraints
- You MUST answer a delivery question in at most 3 sentences.

$SWITCH ON @{tier}
  - standard:: You MUST promise a reply within 2 working days.
  - premium:: You MUST promise a reply within 4 hours.
  - gold:: You MUST promise a reply within 1 hour.
The gold arm is a policy somebody signed off, sitting in the file, unreachable.
tg check tiers.tg - output
tiers.tg:14:3  error  structure/impossible-case  "gold" is not a member of @{tier}'s domain
  — one of standard, premium.

 1 file — 1 error, 0 warning, 3 info
$ echo $?
1
A blocking error and exit 1, which is the behaviour you want from an inherited file: the arm cannot quietly never fire. typeglish --explain structure/impossible-case gives you the one-liner, a CASE pattern outside the switch domain, the arm can never fire, which is also the answer to give the person who asks what happened to gold.

§3Ask what was ever measured

7. Score it, and read the lever rather than the grade. The grade is a number to compare against itself later. The lever is the actionable part, and on an inherited file it tells you which of the eight facets is carrying the loss.

tg score support.tg - output
support.tg — D (53/100)  proven errors: none  tiers: base+z3
  planes  runtime 71 (what the model reads) · hygiene 0 (source only)
  facets  enforceability 85 x.21 · hardness 85 x.12 · directness 88 x.08 · consistency 14 x.17
          structure 0 x.12 (hygiene) · annotation 0 x.12 (hygiene) · style 79 x.08 · security 100 x.08
  lever   consistency 14/100 (up to +14 overall) — Fix the ledger rows with logic/ or
          clarity/ codes; state each fact in one place.
The split is the diagnosis. runtime 71 against hygiene 0: what the model reads is mediocre, and what the next engineer reads is nothing at all. enforceability 85 and hardness 85 say the three surviving rules were written by somebody competent. annotation 0 and structure 0 say nobody ever wrote down why. Eight ledger rows follow the lever, and the three -1 rows are the three orphans, which is the same report from step 4 priced.

8. Run test --dry and read the coverage number, including when there is not one. This is the question that separates a prompt somebody maintained from a prompt somebody edited, and the answer here is the worst possible one, delivered with a tick.

tg test support.tg --dry - output
$ npx typeglish test support.tg --dry
 support.tg
  no $TEST cases
 1 prompt — 0 failed
$ echo $?
0
Zero cases passes, because zero cases fail. There is no coverage line at all, which is a different output shape from coverage: 0/3 and the reason a CI gate on this command has to assert the key exists. Nothing in the file has ever been pinned by anything. Every rule in it is a rule you are free to break by accident.

9. Use resolve to see the variants somebody was serving. The artifact from step 2 was the template build. Bind the input and you get the document one real customer segment sees, which is the closest thing to a specification of the behaviour you have inherited.

tg resolve support.tg --vars - the premium variant
$ npx typeglish resolve support.tg --vars '{"tier":"premium"}'
  ...
# Role
You are a support agent for Cobalt Mobile.

# Constraints
- You SHOULD try to confirm the account before you discuss a bill.
- You MUST call @[get_order] before you state a delivery date.
- You MUST answer a delivery question in at most 3 sentences.

You MUST promise a reply within 4 hours.
One arm, no condition, no mention of the standard tier. resolve prints the resolved source rather than the deployable text, which makes it the right instrument for reading: it keeps the pointers visible so you can see which references the compiler is still holding. Run it once per member of every typed domain and you have the set of prompts your agent has actually been.

§4Leave it better than you found it

Now the edit. The temptation on Thursday is to add the stakeholder's rule and move on, and the argument against is that you are the second person in a row to do that. Two changes cost nothing in behaviour and everything in the next handover.

The first is annotations, which are the only edit in a prompt that provably cannot change what the model does: an @@ note is compile-time and never reaches the artifact, so two files differing only in their notes build to the same hash. Write down what you inferred while running the commands above, including the parts you are unsure of. The second is deciding the three orphans, the hedge, and the chain. Here is the file after both.

support-handover.tg - the same agent, with its reasons written down✓ B (86/100)
<$CONFIG>
  $CONFIG model claude-opus-4-8
    - via:: anthropic
  $REQUIRE variable tier: one of standard, premium
  $IMPORT tool get_order
</$CONFIG>

$SERVICE anthropic
  - base:: https://api.anthropic.com
  - headers::
    - x-api-key:: @{env.ANTHROPIC_API_KEY}

# Role
@@ role: delivery and billing for a mobile network
You are a support agent for Cobalt Mobile.

# Constraints
@@ identity: inherited as "should try to confirm", and a bill is account data
- You MUST confirm a date of birth before you discuss a bill.
@@ lookup_first: a delivery date lives in one system, and it is not this file
- You MUST call @[get_order] before you state a delivery date.
@@ brevity: a delivery answer is one fact, and three sentences is generous
- You MUST answer a delivery question in at most 3 sentences.

$SWITCH ON @{tier}
  - standard:: You MUST promise a reply within 2 working days.
  - premium:: You MUST promise a reply within 4 hours.

$TEST delivery
  - input:: When will my phone arrive?
  - expect::
    - at most 3 sentences
$TEST billing
  - input:: Why is my bill 12 pounds higher this month?
  - expect::
    - contains "date of birth"
D (53/100) to B (86/100): consistency 14 to 100, structure 0 to 100, annotation 0 to 80, hardness 85 to 100. 0 error, 0 warning, 2 info, and the two remaining info findings are prompt/unregistered-doer on the two switch arm rows, which is a note about the arm keys rather than the policy.

Four things happened, and only one of them was a policy decision. The hedge became a modal and picked a criterion, which is the policy call: should try to confirm the account named neither what confirming is nor whether it was required, and somebody had to choose. The chain became a $SWITCH ON, so a third tier added to the domain next year is a compile error instead of a customer falling through the $ELSE. The three orphans were deleted, each in its own commit with a name in it. And every rule got a note saying why it exists, including the honest one on line 18 that records what the rule used to say.

Then put the floor in. Both gates now pass on the file you are handing on, which is the only moment they are cheap to install.

the two CI gates, before and after
$ npx typeglish check support.tg --strict
 1 file — 6 error, 0 warning, 2 info
$ echo $?
1
$ npx typeglish score support.tg --min B
support.tg — D (53/100)  proven errors: none  tiers: base+z3
  ...
$ echo $?
1

$ npx typeglish check support-handover.tg --strict
 1 file — 0 error, 0 warning, 2 info
$ echo $?
0
$ npx typeglish score support-handover.tg --min B
support-handover.tg — B (86/100)  proven errors: none  tiers: base+z3
  ...
$ echo $?
0
Nothing was added to the inherited file to produce those six errors: --strict escalates the five unused declarations and the switch-shaped chain to blocking. That is the number to keep in mind when somebody says the prompt has been fine for a year. It has been fine with the gate switched off.

§5Common questions

I inherited a system prompt. Where do I start?
Not by reading it. Build it first, because the artifact is the text the model actually receives and it is not the file: a template build turns an unbound conditional into one line of prose that ships both branches at once. Then run check and read the advisories as the previous author's unfinished decisions, run score for the lever, and run test --dry to find out how much of the file was ever pinned by a case. Reading comes after that, with the findings as a map, because the commands tell you what is unfinished and the prose only tells you what somebody meant.
How do I find dead rules and unused declarations in a prompt?
typeglish check names each one. An orphan input is structure/unused-import plus clarity/unused-variable, an orphan condition is structure/unused-define plus clarity/unused-definition, and a tool nobody references is structure/unused-tool, whose message is the reason it matters: the model cannot discover a tool no rule mentions. All of them are warnings, so the file exits 0 with them in place. On an inherited prompt they are the highest-value findings in the report, because each one is a feature that was started and never wired in.
Is the prompt file in git the prompt that is running in production?
Assume not until you have compared hashes. typeglish build writes the artifact and records sourceSha256 and artifactSha256 in .typeglish/build-manifest.json, so the check is to build the file you inherited and compare its artifactSha256 against the text your runtime is serving. If they differ, somebody hand-edited the deployed prompt, and every conclusion you draw from the file is about a document nobody is reading.
What should I change first in a prompt somebody else wrote?
The annotations, because they are the only edit that cannot change behaviour. An @@ name: why line above a rule is compile-time only and never reaches the model, so writing down what you inferred about a rule costs nothing at runtime and rebuilds to the same artifact hash. Do that before you touch a word of policy, then put the floor in CI with check --strict and score --min at the number you actually shipped, so the next person inherits a file that fails loudly instead of one that scores D and exits 0.
Field note

The number that stayed with us from building this one is hygiene 0. Eight facets, and the two that measure whether the source explains itself were both at the floor while the rules themselves scored 85. That is what an inherited prompt is: competent work with the reasoning deleted. The commands can reconstruct what the file does, and no command will ever reconstruct why, which is the entire argument for the cheapest line in the language. An @@ note costs zero runtime bytes and rebuilds to the same hash, so there is no budget conversation to have about it. Write one above every rule you touch this week, and the next person to inherit this file gets a document instead of an archaeology dig.

∿ washed up Aug 10, 2026 ∿