← Tidelines/Best practices

Your coding agent writes the prompt now

The system prompt is a file in a repo, so the thing that edits files edits it. Nine checks, each with a command behind it, and only one of them can tell a fix from a deletion.

by TypeGlish team9 min read#best-practices
It made the check pass. That was the problem.

TL;DR Give the agent the compiler rather than your notes on it (typeglish mcp serves check, build, score, explain and reference over stdio, and nothing else), then gate on the artifact, because the patch that keeps your refund cap and the patch that deletes it are both 0 error, 0 warning, 0 info at B (87/100) with byte-identical facets and the same coverage: 1/2 rules exercised, and the only thing in the toolchain that tells them apart is the built prompt.

The prompt stopped being special some time in the last year. It sits in prompts/ next to the code, it has a review process, and the same agent that renames your variables will happily reword a refund rule if a ticket asks it to. That is fine. It is also the first time your agent prompt has had an author who does not know what happened in March.

These nine are for the steady state of that: not the migration, not the incident, just the ordinary week where a share of the diffs on the file were not typed by a person. Every one has a command behind it, and they are ordered by when in the loop they apply.

§1Before it writes a line

Check 1: hand it the language, not your memory of the language. A model writing TypeGlish out of its priors writes a plausible average of every prompt format it has seen, and the average is not this one. typeglish reference prints the reference generated from the compiler's own source of truth, and it is the single highest-value thing you can put in the agent's context before it opens the file.

Here is what turns up when it does not get one. This is a first draft of a returns prompt from a model working off familiar shapes.

priors.tg - written without the reference✗ 1 error, 5 warning
# Role
You are a returns agent for Harlow Home.

# Constraints
- Always be helpful and professional.
- Keep every reply to ≤ 3 sentences.
- Refund up to {refund_cap} pounds without approval.
- IMPORTANT: never promise a delivery date — always check first.
- Use “clear, friendly” language at all times.

EXPECT: the agent refunds under the cap.
Nothing here is stupid. Every line is a shape that works in some other prompt format: the bold IMPORTANT, the typographic dash, the smart quotes around a phrase, an EXPECT line from a test harness that was retired.
tg check priors.tg - output
priors.tg:5:1  warn   prompt/unmeasurable  Unmeasurable rule — no observable action or bound, so nothing can check compliance. Name a concrete action ("cite the source", "at most 3 sentences") or move it to a prose block.
priors.tg:6:23  warn   structure/special-character  The numeric tier parses bounds written in English: '≤ 3' lowers with no bound while 'at most 3' is Z3-provable. Write it out.
priors.tg:8:43  warn   structure/em-dash  A typographic dash hides its break from the ASCII parsers, and models mirror the tic (the canonical AI-tell). Its plain form is the spaced hyphen: " - ".
priors.tg:9:7  warn   structure/curly-quote  A curly quote is not a TG string literal: only straight " binds an utterance, so this text is invisible to the quote doctrine. Straighten it.
priors.tg:9:23  warn   structure/curly-quote  A curly quote is not a TG string literal: only straight " binds an utterance, so this text is invisible to the quote doctrine. Straighten it.
priors.tg:11:1  error  structure/bad-test  EXPECT is retired — write each expectation as a "- " sub-bullet under "- expect::" inside a $TEST block.

 1 file — 1 error, 5 warning, 0 info
Four of the six findings are about characters rather than policy, and one of them is structure/em-dash, whose message says models mirror the tic (the canonical AI-tell). The checker is not being fussy: '≤ 3' lowers with no bound while 'at most 3' is Z3-provable, so the length rule the model wrote is a rule nothing can prove anything about.

None of that survives one read of the reference. All of it survives any number of reads of your team's wiki page about the prompt.

§2Give it the compiler, not a transcript of the compiler

Check 2: run the compiler as a tool the agent can call. typeglish mcp serves check, build, score, explain and reference to any MCP client over stdio, which means the agent stops guessing whether an edit compiles and starts knowing. The handshake is the whole setup.

tg mcp - the handshake
$ printf '%s\n' "$INIT" "$READY" "$LIST" | npx typeglish mcp
{"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{"listChanged":true}},
 "serverInfo":{"name":"typeglish","version":"0.9.0"}},"jsonrpc":"2.0","id":1}

# the tools/list reply, reduced to name + readOnlyHint + title:
typeglish_check       readOnly  Check TypeGlish source
typeglish_build       writes    Build a deployable prompt artifact
typeglish_score       readOnly  Score a prompt (the TG score)
typeglish_explain     readOnly  Explain a diagnostic code
typeglish_reference   readOnly  The TypeGlish language reference
Five tools. Four of them cannot change a file. typeglish_reference is check 1 again, this time as something the agent fetches for itself, and its description ends with the instruction Read this before writing TypeGlish.

Check 3: let it compile a draft it has not written yet. Both typeglish_check and typeglish_build take an inline source string as an alternative to a path. That sounds like a convenience for hosts without a shell and it is actually the difference between an agent that proposes an edit and an agent that tests one, because it can compile the candidate rule before it touches your working tree.

tools/call typeglish_check with inline source - the reply
{
  "checkMode": "full",
  "report": {
    "ok": false,
    "consistent": true,
    "wellFormed": true,
    "counts": { "error": 2, "warn": 0, "info": 0 },
    "diagnostics": [
      {
        "code": "logic/numeric",
        "category": "logic",
        "name": "numeric",
        "importance": "critical",
        "severity": "error",
        "effectiveSeverity": "error",
        "blocking": true,
        "range": { "line": 5, "column": 1, "endColumn": 60 },
        "message": "Conflicts with line 5. Numeric conflict — \"at most 50 pounds\" and \"at least 100 pounds\" can't both hold."
      },
Nothing was written to disk to produce this. The candidate prompt went in as a string and came back proven inconsistent.

Check 4: make it read the JSON, not the pretty output. The text renderer drops fields the agent should be branching on. importance: "critical" and blocking: true are in the JSON and not in the terminal, and they are exactly the two an agent needs to decide whether a finding is a merge blocker or a note for later. typeglish check --json is the same payload on the CLI side, so a hook and an agent see the same thing.

Check 5: know what it cannot reach. Five tools is the whole surface. There is no test tool, no fmt tool and no resolve tool, so typeglish test --dry and typeglish fmt --check live in CI, outside anything the agent drives. Treat that as the design rather than a gap: the agent gets the fast loop, and the thing that decides whether an edit ships runs where it has no hands.

§3The one failure mode that is actually new

Everything above is hygiene. This is the check that earns the post, and it is worth being blunt about the shape of it: an agent asked to make the check pass will make the check pass, and deleting a rule is a completely reasonable way to do that.

Here is a returns prompt where somebody has already left a conflict behind. The desk limit is 50 pounds; a rule added later for damaged deliveries starts at 100.

refunds.tg - the file the ticket points at✗ 2 error
# Role
You are a returns agent for Harlow Home.

# Constraints
- You MUST refund at most 50 pounds without a manager code.
- You MUST refund at least 100 pounds for a damaged delivery.
- You MUST keep every reply to at most 4 sentences.

$TEST damaged_rug
  - input:: The rug arrived damaged. Can you refund the 80 pounds I paid for it?
  - expect::
    - contains "manager"
    - at most 4 sentences
Two blocking logic/numeric errors, one per participating line, and typeglish build refunds.tg answers refunds.tg refused - nothing written at exit 1. The compiler has done its job. Now somebody has to decide which number is the policy.

There are two one-line patches. refunds-a.tg deletes the 50 pound cap. refunds-b.tg deletes the damaged-delivery rule and leaves the cap standing. One of those is a refund policy change that finance has not heard about. Here is every automatic signal you have, on both.

tg check / score / test --dry - the two patches, side by side
$ npx typeglish check refunds-a.tg refunds-b.tg
 2 files — 0 error, 0 warning, 0 info

$ npx typeglish score refunds-a.tg
refunds-a.tg — B (87/100)  proven errors: none  tiers: base+z3
  planes  runtime 100 (what the model reads) · hygiene 50 (source only)
  facets  enforceability 100 x.21 · hardness 100 x.12 · directness 100 x.08 · consistency 100 x.17
          structure 100 x.12 (hygiene) · annotation 0 x.12 (hygiene) · style 100 x.08 · security 100 x.08

$ npx typeglish score refunds-b.tg
refunds-b.tg — B (87/100)  proven errors: none  tiers: base+z3
  planes  runtime 100 (what the model reads) · hygiene 50 (source only)
  facets  enforceability 100 x.21 · hardness 100 x.12 · directness 100 x.08 · consistency 100 x.17
          structure 100 x.12 (hygiene) · annotation 0 x.12 (hygiene) · style 100 x.08 · security 100 x.08

$ npx typeglish test refunds-a.tg refunds-b.tg --dry
 refunds-a.tg  coverage: 1/2 rules exercised
 refunds-b.tg  coverage: 1/2 rules exercised
 2 prompts — 0 failed
Identical. Not similar: the grade, the plane split and all eight facet numbers are the same to the digit, and the offline suite reports the same coverage against both, because the case exercises the brevity rule either way. There is no threshold you could have set that would have separated these two commits.

Check 6: never let "the check passes" stand in for the diff review. A green report proves no defect was proven. It says nothing at all about whether the file still contains your policy, which is the same false green that a hand-written rule can walk through, arriving faster now that something can write forty of them a day.

Check 7: gate on the artifact. One thing in the toolchain does tell these two apart, and it is the only thing that reads what the model will actually receive.

tg build - the two patches
$ npx typeglish build refunds-a.tg refunds-b.tg
 built .typeglish/dist/refunds-a.txt ← refunds-a.tg (54d4310aa849, full)
 built .typeglish/dist/refunds-b.txt ← refunds-b.tg (dc908bb971e8, full)
 built 2/2

# refunds-a.txt
# Constraints
- You MUST refund at least 100 pounds for a damaged delivery.
- You MUST keep every reply to at most 4 sentences.

# refunds-b.txt
# Constraints
- You MUST refund at most 50 pounds without a manager code.
- You MUST keep every reply to at most 4 sentences.
Two hashes, and the deployed text under each. Put typeglish build in the pipeline and require the artifact diff in the pull request body, and the reviewer stops reading a source patch and starts reading the prompt. It is four extra lines in a review and it is the only automatic difference that exists.
A gate an agent can satisfy by removing the rule is not a gate. It is a scoreboard.

§4The score is a target now, so aim it

Check 8: keep the score floor, and know which twelve points are free. score --min B in CI is still worth having, but the moment something optimises against it you should know how it is composed. annotation carries weight x.12 on the hygiene plane and is measured by whether a @@ note sits above each statement. Notes never reach the model.

tg score / tg build - annotations only, nothing else changed
$ npx typeglish score plain.tg
plain.tg — B (87/100)  proven errors: none  tiers: base+z3

$ npx typeglish score noted.tg
noted.tg — A (100/100)  proven errors: none  tiers: base+z3

$ npx typeglish build plain.tg noted.tg
 built .typeglish/dist/plain.txt ← plain.tg (dc908bb971e8, full)
 built .typeglish/dist/noted.txt ← noted.tg (dc908bb971e8, full)

# .typeglish/build-manifest.json, reduced to the hashes:
noted.tg   source 0fe205cce441  artifact dc908bb971e8
plain.tg   source 3d13754939c7  artifact dc908bb971e8
Thirteen points and a grade boundary, bought with three comment lines. The two source hashes differ and the two artifact hashes are the same, which is the manifest telling you plainly that nothing the model reads has changed.

Read that twice, because it cuts both ways and the second reading is the useful one. Check 9: ask the agent for the notes. An annotation is the one edit that provably cannot change behaviour, and it is the one thing an agent is genuinely better placed to write than the next person, because it has the ticket, the conversation and the diff in front of it right now and nobody will in eight months. @@ refund_cap: 50 pounds is the desk limit finance signed off in March costs zero bytes at runtime and is the whole reason the next reader does not delete the line.

§5Keep the review about the policy

The last one is small and it pays for itself in the first week. An agent that hard-wraps a long rule produces a diff whose findings are all about whitespace, and a human reviewer spends the attention there instead of on the number.

tg fmt --check then tg check - the same file
$ npx typeglish check wrapped.tg
wrapped.tg:5:1  warn   structure/missing-period  Unterminated statement — end it with a period (or ! ?; a lead-in may end with ":"). Statement boundaries are a compile contract.
wrapped.tg:6:1  warn   structure/bad-indent  Indentation mirrors section nesting: expected column 0 (0 levels deep), found 2. The indent unit is 2 spaces.
wrapped.tg:6:3  warn   structure/wrapped-fragment  Reads as a hard-wrapped continuation of line 4 — it starts lowercase, classifies as plain prose, and line 4 has no terminator. If they are ONE statement, join them (typeglish fmt); if this line stands alone, capitalize its first word (and terminate line 4).

 1 file — 0 error, 3 warning, 0 info

$ npx typeglish fmt wrapped.tg --check
wrapped.tg: 1 change needed
 1/1 file need formatting — run typeglish fmt

$ npx typeglish fmt wrapped.tg
 formatted wrapped.tg (1 change)

$ npx typeglish check wrapped.tg
 1 file — 0 error, 0 warning, 0 info
Three warnings before, none after, and not one of them was about the rule. fmt --check exits 1 and writes nothing, so it belongs in front of check in the pipeline. Two of the three findings are the mechanical consequence of one wrapped line, which is a review nobody needs to have.

The nine, in the order they run:

  • 1. Put typeglish reference in the agent's context before it opens the file.
  • 2. Serve the compiler with typeglish mcp so it can call check, build, score and explain.
  • 3. Let it compile candidates as inline source, before the working tree changes.
  • 4. Branch on --json: blocking and importance are not in the text output.
  • 5. Keep test --dry and fmt --check in CI, where the five tools do not reach.
  • 6. Never accept a green check as the diff review.
  • 7. Require the built artifact and its hash in the pull request.
  • 8. Hold the score --min floor, and discount the twelve annotation points when you read a jump.
  • 9. Ask for a @@ name: why on every line it touches, because that is the part only it still knows.

§6Common questions

Can I let an AI coding agent edit my system prompt?
Yes, on the same terms as any other code it edits: it needs the language reference, a compiler it can call, and a gate it cannot satisfy by deleting things. The gate is the part people skip. A returns prompt with one proven numeric conflict has two one-line fixes, and the fix that keeps the 50 pound refund cap and the fix that deletes it are indistinguishable to every automatic check: both are 0 error, 0 warning, 0 info, both score B (87/100) with byte-identical facets, and both report coverage 1/2 rules exercised from the same test suite. Only the built artifact separates them, at 54d4310aa849 against dc908bb971e8.
How do I connect TypeGlish to an AI coding agent?
Run typeglish mcp, which serves the compiler to any MCP client over stdio. The handshake reports serverInfo typeglish 0.9.0 and the server exposes five tools: typeglish_check, typeglish_build, typeglish_score, typeglish_explain and typeglish_reference. Four are marked readOnlyHint, and only typeglish_build writes. Both check and build take an inline source string as well as a path, so the agent can compile a draft it has not written to disk yet, which is the difference between an agent that guesses and an agent that iterates.
How do I stop an AI agent from deleting a rule to make the check pass?
Review the artifact, not the verdict. Every automatic signal you have is blind to the difference: check, score and test --dry all return identical output for the patch that kept the refund cap and the patch that deleted it. The artifact hash is not, so put typeglish build in the pipeline and require the built prompt diff in the pull request. Ask for the fix as an added guard rather than a removal, and keep one opposing rule per bound in the repo so that a widening edit fails loudly instead of quietly.
Can an AI agent run my prompt tests?
Not through the MCP server. The five tools are check, build, score, explain and reference; there is no test tool, no fmt tool and no resolve tool, so typeglish test --dry and typeglish fmt --check stay in CI where the agent cannot reach them. That is a useful division rather than a gap: the agent gets the fast feedback loop, and the gate that decides whether an edit ships runs somewhere it does not control.
Field note

The thing that surprised us assembling this was how ordinary the dangerous case is. Nobody has to be careless. The ticket says the file does not compile, the fix is one line, the check goes green, the score does not move, the suite still passes, and a refund cap that finance signed off in March is gone from the deployed prompt. Every individual step was correct. The gap is that a compiler proves the absence of defects and has no opinion at all about the absence of rules, and until now the person writing the patch was also the person who knew that. Bounds are the sharp edge here, and a compile-fail fixture per bound is the version of check 7 that runs without a reviewer. If you only take one thing: put the artifact in the pull request. It is the diff of the document your customers actually meet.

∿ washed up Aug 21, 2026 ∿