Tidelines - the TypeGlish blogPrompt engineering, in plain English

Tidelines

Field notes for prompt engineers. Best practices that survive production, teardowns of real prompts, deep dives into why English fails and how to fix it - written by the team building TypeGlish.

◉ High tide - latestDeep divesSep 3, 202610 min read

Your policy remembers. Your agent does not.

Five rules about the past, none of them followable, none of them flagged. The honest rewrite will not compile.

by TypeGlish team
tg build care-shipped.tg --vars, two customers✓ A (94/100)
  3 contacts, 1 credit   66f9b9f3c278   no credit, escalate
  1 contact, 0 credits   7f1625527da6   credit, troubleshoot
  unbound template       54ea2a13c76e   both arms, 629 bytes
  the wish version       61fc5c194adb   C (73/100), 0 error
Tide table - all entries113 entries
Deep divesYour policy remembers. Your agent does not.Five rules lifted straight from a broadband provider policy, every one of them turning on a fact from before the conversation started, and the file reads 0 error, 0 warning, 3 info at C (73/100) and stays clean under --strict. The three info findings are all prompt/unintroduced-definite on the word the; not one of them is about the fact that no rule in the file can be evaluated, because twice counts across an unbounded past, before means before this chat, first-time is a lifetime count held in billing, already apologised refers to a message the agent did not send, and new is a date subtraction against an account record. The asymmetry is the point: write the rule as a wish and the checker is silent, write the honest version and $DEFINE repeat_contact AS @{contacts_30d} is at least 2 with nothing declared is 3 blocking errors, structure/undefined-ref and structure/undeclared-input asking who supplies the value in two registers, plus structure/non-exhaustive-switch asking what happens when the answer is no, the question a bare prose IF never had to face. Facts sort into three places and the fix differs for each: in the window is a wording problem about units, in a system is a plumbing problem, and nowhere at all is a product decision where the honest options are to start recording the fact or to delete the rule. The rewrite gives the agent an answer rather than a memory, five typed inputs read by four $DEFINE lines and five $IF chains, 0 error, 0 warning, 0 info at A (94/100) with enforceability 60 to 88, and because a chain resolves at compile time two customers compile to two documents, 66f9b9f3c278 with no permission to give a credit and 7f1625527da6 with no prohibition on one, against 54ea2a13c76e at 629 bytes for the unbound template that ships both arms. Then the honest limit, six one-rule files where the highest score in the set, B (87/100) with runtime 100 and enforceability 100, belongs to ALWAYS remember what a customer told you earlier, the only rule that is pure wish, while the typed conditional that actually works is C (76/100) with enforceability 50, because a rule that only sometimes applies is by construction less enforceable than one that always does. Best practicesWhy your agent invents a case referenceA delivery-support bot hands the customer a case number with the right prefix, the right digit count and the right casing, and it belongs to nothing. The defect is not a hallucinated fact, it is a filled-in template: somewhere in the prompt is a sample identifier, and a completion engine completed it. The prompt that teaches it is a normal one, a # Output section documenting that a case reference looks like HG-48210 and a # Examples exchange showing one, and it reads 0 error, 0 warning, 2 info at B (80/100), clean under --strict, with the two docked rows a definite noun and a bare doer while HG-48210 costs zero points in every facet. The artifact is 599 bytes carrying the fake reference four times, two of them inside a worked example, which is the highest-authority text in the file and the reason $EXAMPLE holds its - bad:: line out of the compiled prompt entirely, protection a # Examples prose block does not get. Eight spellings of the same rule checked in one run produce exactly one blocking error and it lands on m3_pointer, @{case_reference} with nothing behind it, 1 error structure/undefined-ref: the constant, the brace hole, the format line, the $EXAMPLE good line, the URL and the phone number are all 0 error, 0 warning, and the declared $REQUIRE variable version is clean but a fix for the wrong problem, because a host binds a value before the model sees a token and a case reference does not exist until a tool runs, so its pointer lowers straight back to {case_reference} in the artifact. There is no diagnostic in the language that inspects a literal for looking like an identifier, since that would be a guess about naming conventions rather than a proof about rules. The fix is a reframe, an identifier is never a fact about your product and always a value returned by something: declare $TOOL open_case through a $SERVICE, point at it with @[open_case] rather than naming it in bare prose where structure/bare-tool-ref waits, write ALWAYS call @[open_case] before you give a customer a case reference beside NEVER write a case reference that @[open_case] did not return, delete the format line and the filled example, and pin the absence with matches /^(?!.*HG-)/. That is 0 error, 0 warning, 0 info at A (93/100), coverage 2/4, and an artifact of 372 bytes carrying the reference zero times. GuidesBuilding a de-escalation agent, prompt-firstA support lead's six-sentence abuse policy typed straight in reads 0 error, 1 warning, 3 info at C (65/100), and consistency is 100 while the two lines that matter, Never end a conversation with a customer and If they keep swearing, warn them and then close the chat, sit four words apart. The prover is silent because close a chat and end a conversation are two different actions to it, and changing that one noun turns the identical pair, scope and all, into 2 blocking logic/contradiction errors whose message says scope the two rules rather than one, since an unscoped NEVER covers the guarded case as well. The structural finding is the ladder itself: how the customer is behaving crossed with whether a warning already went out is two axes, a $SWITCH ON is a table over one typed input, and the nested $IF inside a rude:: arm parses at 0 error and never resolves, so builds binding warnings_sent to 0 and to 2 both return d1c049b77a20 and the artifact ships the words If already_warned to a model that was never told the value, with structure/unused-define the only signal and a warning rather than an error. Flattened into one $IF chain with a compound arm, first-true-wins carries the ladder without anyone writing the negation, and the finished conduct.tg is 0 error, 0 warning, 0 info at A (95/100) over two host inputs, conduct: one of civil, rude, abusive and warnings_sent: integer, one $DEFINE, two always-on rules and eight annotated rules across four arms. Four bindings give four artifacts, ce71f74a91f3, ba2914204184, 2c43fedab70b and 50301963e002, and the abusive one carries only end_chat, flag_conversation and MUST NOT apologise, with no rule in it permitting an answer, while always apologise for the inconvenience survives as one scoped rule about a wait. Two deterministic cases pin the compliance-approved warning sentence with contains and at most 1 sentence and the absence of an apology with a negative lookahead, coverage 2/10. Best practicesBake-off: the reason in the rule vs. the reason in a noteThree files carry the same three support rules and the same three reasons, and the only difference is where the word because lives. All three check at 0 error, 0 warning, 0 info in one invocation, so the check cannot referee it, and the score splits them by fourteen points: reasons welded to their rules is B (81/100) with runtime 91, reasons moved to @@ notes is A (95/100) with runtime 97, and reasons in a # Context prose block alongside the notes is A (91/100), held back only by annotation 43 counting three unannotated context lines, which becomes A (96/100) with annotation 86 once those lines get notes of their own. The runtime number is the surprise, because it means a because clause is not untidy bookkeeping, it is changing the document the model reads. Attributing the loss one rule at a time gives two mechanisms, both six points. Hardness scores the MIN over a statement's force words, so a quantifier inside the reason becomes the strength of the whole rule and because most customers read on a phone takes hardness from 100 to 50, as does because a customer usually reads on a phone, while because customers read on a phone is free. Enforceability falls from 100 to 70 on a prohibition instead, because the clause adds rule-shaped text with no observable action in it. Then the blind spot: You respond within 30 minutes on its own line under You MUST respond within 2 hours is 2 blocking logic/time-strength errors on the slot you respond, and the identical claim moved into a because clause on the line above is 0 error, 0 warning, 0 info in the same run. The clause is parsed by the scorer, shipped to the model and invisible to the provers, which is the worst of the three available positions. Artifacts settle it, a12e582f251f at 411 bytes against 61f33dd55152 at 244, so 167 bytes and 41 percent of the deployed document is three subordinate clauses. The rule of thumb: if the model would behave differently without the sentence it is context, and if only a person would it is an annotation. Deep divesThe typo that compiles is the one to worry aboutAn ablation over keystrokes rather than rules: sixteen copies of a clean care prompt, one character dropped, added or replaced in each, and the split is ten blocking, three advisory and three completely silent. The baseline is 0 error, 0 warning, 0 info at A (92/100) carrying a folded $CONFIG block, a typed input, a $DEFINE, a $IF chain, four annotated rules, a literal zone and a test, and the directory run reads 16 files, 15 error, 16 warning, 1 info. Ten of the keystrokes leave text with no valid reading and every diagnostic names the character: structure/needs-sigil on a dropped $, structure/retired-brace-ref on a dropped @, structure/bad-pointer-name on an underscore typed as a hyphen where the message says the model would receive the line with the compile-time @ still attached, structure/bad-input-type on one of taking a colon, structure/define-binder on AS typed as =, structure/stray-close-tag on a half-quoted literal zone that also sheds five warnings of collateral, structure/bad-switch on $IF losing its colon plus two logic/time-strength errors as the arms fall out of the chain, structure/bad-test twice, and structure/impossible-value on a domain member misspelled as glod. Blast radius is the second finding, since one dollar sign is three errors in two places: a line with no sigil is content inside a folded config block, and the tool pointer sixteen lines below loses its declaration. Three edits are lookalikes and only warn, structure/curly-quote, structure/em-dash and structure/missing-period, all three at B (88/100) and all three blocking under --strict, while typeglish fmt --check reports all sixteen already formatted, so the formatter is not the safety net for this class. Then the three that ship: at most 3 sentences retyped as at most 8, MUST retyped as Must, and the brand name misspelled in the role line are 0 error, 0 warning, 0 info with and without --strict, two of them at A (92/100) with every facet unmoved. The case edit is correct, because every operator is case-insensitive, and the other two share one property, each leaves a grammatical English sentence behind, which is why too typed for to is clean as well. The misspelling exposes an asymmetry between two commands, since check prints nothing and score carries a spelling/unknown-word row worth -0.25 at A (91/100), and the same misspelling inside an @@ annotation moves neither number. The gate for the quiet class is the build, where four files give four hashes, 82cbd42c61b8, a69be5a420d9, 46832016db8f and 114f9cb464b7, and nine lines reading artifactSha256 out of .typeglish/build-manifest.json exit 0 on the baseline and 1 on all three silent variants, with the honest caveat that it fires on every intended edit too and is a re-approval trigger rather than a defect detector. Best practicesBoth prompts were clean. The merge is not.A re-org folds the billing desk into technical support and two agent prompts that each check at 0 error, 0 warning, 0 info, B (85/100) and B (86/100), become one file. Concatenating them is 6 blocking errors and F (62/100) with the build refused: four structure/duplicate-section rows for two # Role and two # Constraints headings, and two logic/quantifier-strength rows reading one rule, two counts for the three-sentence and six-sentence brevity bounds. Three more conflicts are reported nowhere at all. Two $REQUIRE variable channel declarations of different domains draw no diagnostic and the last one wins, so the same file with the two lines swapped is 1 error and then 0 error even under --strict, and whether sms exists depends on the order you concatenated. Two personas written as You are Rowan, a billing agent and You are Casper, a technical support agent are 0 error, 0 warning, 0 info because the apposition binds no name, while Your name is Rowan beside Your name is Casper is 2 blocking logic/contradiction rows on single-valued you.name. And MUST NOT discuss a network outage beside MUST explain the cause of a network outage is clean, because discuss and explain are separate keys in the verb ontology, where aligning the verb makes the pair 2 errors. The fix is routing rather than reconciliation: one root with $IMPORT file, the union of the channel domains written out once, every $IMPORT tool hoisted because a tool declaration does not travel with a transcluded section, and each desk's section on its own arm of $SWITCH ON @{intent}. The controlled pair is the evidence, since the same three files with both sections transcluded unconditionally is 2 errors across two files at their true file and line and the switched version is 0 error with program: 1 root printed under the tick. Two bound builds are b285651b3825 and ba0c665ed71b with one desk in each artifact, the unbound template build is a096b9b929e8 and ships both desks and both sides of the outage policy as conditional prose at exit 0, and coverage is 0/3 rules exercised because a merge inherits neither suite. Deep divesGold and silver conflict. Priority and standard do not.A closed set is only closed if you declare it. A support prompt whose Returns section and Loyalty section each answer which loyalty tier gets free returns is 2 blocking logic/contradiction errors while the tiers are called gold and silver, and the slot key in the diagnostic is the whole point: free returns tier.metal, both name a metal, and a single-valued slot holds one. The word tier appears nowhere in the reasoning. So the March rebrand that renamed Gold and Silver to Priority and Standard, three words in two lines, takes the same defect from F (68/100) with consistency 0 and a refused build to C (75/100) with consistency 100, 0 error, exit 0, and an artifact at 243a626460df carrying both answers six lines apart. What survives is structure/split-declaration as a 1-point warning whose suggested fix is a list, IS priority and standard, because two words it has never met read as multiplicity rather than a conflict. One file with four facts each stated twice shows the boundary: metal, channel and plan tier are catalog domains and gold beside silver, voice beside chat and enterprise beside starter are 6 errors, while priority beside standard is 2 warnings. The fix is one statement, %Loyalty tier% ARE one of priority, standard, essential, and membership is value-based rather than owner-scoped, so it turns the same two lines into 2 blocking logic/redeclaration errors, a different code from a different mechanism and the one that survives a rename. It is a declaration, so it binds and it renders: the artifact at 71804f06ab52 states the tier vocabulary to the model in the line that gave the prover its set, and the shipped file is 0 error, 0 warning, 0 info at A (96/100) with annotation 88 and coverage 2/5 rules exercised. A tier that varies per conversation belongs on the machine plane instead, as $REQUIRE variable caller_tier: one of priority, standard, essential with $SWITCH ON, where a fourth tier added to the domain later is structure/non-exhaustive-switch at every switch that was not updated. Two honest limits ship with it: the set closes members against each other and nothing else, so two guards on two members and an assignment of the non-member trade are 0 error, 0 warning, 0 info, and in 0.9.0 a declared $CONFIG domain with its required - members:: row leaves the same two declarations at 0 error, so write the set in the prompt. TeardownsTeardown: your macro library speaks in the first personThe macro library is the best-reviewed text a contact centre owns, which is why pasting the top of it under the rules feels like the opposite of cutting a corner, and four approved replies under four working rules check at 0 error, 0 warning, 1 info at B (85/100) with enforceability 94 and consistency 100. The single finding is prompt/declared-later about a variable nobody declared, and the callback macro promising three days six lines under a rule promising two hours draws nothing at all. Rules are indexed by their subject and the diagnostic prints its own index, the looser rule on "you call a customer . back" with the party inside the slot key, so one controlled clause across four files splits cleanly: You call a customer back within 3 days is 2 blocking logic/time-strength errors and I, We and the agent's own persona name Ellis are each 0 error, 0 warning, 0 info. The persona-name case is the one that catches authors out, since the role line has just said you are Ellis. Then the finding that was there: I am sorry about the wait is a declaration, because IS, ARE and AM are one copula, so it binds the name i, and a rule reading You MUST tell a customer that you are @{i} compiles with a prompt/duplicated-declaration warning and builds to an artifact whose last line reads you are sorry about the wait at 8dc9b489048b. Discourse findings do not care about the subject, so definites and passives still fire on macro lines, which is what makes the block look read when the rule plane skipped every line of it. The build is the cost, since a prose body reflows and four macros arrive at the model as one run-on paragraph at ba34d1ddbe1a, with a three-day callback promise and an unauthorised excess refund in it and no boundary marking either as a quotation. The rewrite deletes the block and asks what each entry was asserting: the callback becomes a $TEST with contains "2 hours" and at most 3 sentences, the excess becomes MUST NOT waive an excess, and the file is 0 error, 0 warning, 0 info at A (97/100) with annotation 0 to 100, enforceability 94 down to 88 and coverage 3/5 rules exercised. The honest limit is that nothing reconciles an expectation with a rule, so contains "3 days" under a two-hour prompt still validates offline. Best practicesRewrite: a box costs 34 pounds, and nothing knows itNobody reviews the Context section, because it does not tell the agent to do anything, and the compiler is reading every line of it and sorting the sentences into two piles on a point of grammar. A meal-kit support prompt with five background lines and three rules is 0 error, 0 warning, 1 info at B (82/100) with enforceability 80, the info a prompt/unregistered-doer on the only line whose subject is the company. State each Context line twice with two different values and two of the five object: Our support hours are 8am to 8pm beside 9am to 5pm is 2 errors via logic/time, Our goodwill credit limit is 20 pounds beside 30 pounds is 6 errors via logic/contradiction plus two logic/measure rows on we and we.goodwill credit limit, while Larkspur delivers on Tuesdays, The skip window closes 5 days before a delivery and A box costs 34 pounds are 0 error, 0 warning, 0 info however often you contradict them. The divide is the verb: IS, ARE and AM are the one copula and a copula binds, so changing A box costs 34 pounds to The box price is 34 pounds is the same fact, binds box.price and turns the duplicate into 6 blocking errors. The production failure is neither duplicate, it is the single edit made in one place: raise the rule to 30 and leave the Context at 20 and the file is 0 error, 0 warning, 1 info and it builds, because a rule bound and a declared value are not two claims on one slot. So the fix is arrangement rather than a new check, sorting the block by destination: a line that constrains behaviour becomes a rule with a bound, a line that varies per conversation becomes $REQUIRE variable delivery_day: one of tuesday, friday, and only a line the agent must state correctly stays a fact and gets the copula. B (82/100) to A (98/100), enforceability 80 to 93, annotation 0 to 100, building to e07489ff4d4c, and appending a second price or a second set of hours to the rewrite is 6 errors and 2 errors where the before file absorbed the price edit in silence. Coverage is the honest comparison at 1/3 before and 1/4 after, five Context lines contributing nothing to either denominator. Two limits ship with it: a duration on a compound action does not lower into the time layer, so a second skip-window rule is still clean, and nothing forces the declared price to match the world. GuidesHow to write a severity scale your agent can applyEvery contact centre runs on a ladder and prompts inherit it from a runbook that describes the top rung and the bottom rung. A hosting-support prompt whose scale is defined at severity 1 and severity 3 is 1 prompt/scale-gap warning at C (76/100), reading defined at one and three but never two, reported at file line 9 with the cross-reference counting line 8 in the blank-stripped scheme. The reader is deliberately narrow and knowing its edges is what makes it trustworthy: seven nouns and only seven (severity, level, tier, priority, stage, phase, grade), digits and number words both, three copulas (Severity 1 is, Priority 1 means, MUST treat stage 1 as), and only the interior checked, while P1 is a full service outage and Urgency 1 draw nothing at all. Filling the middle is the cheap fix and it clears the warning at B (80/100) with style 50 to 100, and enforceability goes DOWN 70 to 65 because a definition is not a rule and the score is honest about that. Then the part the clean run does not say: severity 2 is now defined and still has no routing rule, and the file is 0 error, 0 warning, 0 info, because nothing in English obliges a level to have a rule. The routing gap only becomes catchable when the levels stop being prose and become a type, so the shipped version drops the numbers for a closed domain, $REQUIRE variable severity: one of critical, degraded, question with one $SWITCH ON arm each, at A (93/100) and two build hashes, 82ca1de09566 for critical and bbe9a07512f5 for question, one arm in each artifact. Four one-line edits then refuse to compile: deleting an arm and adding a member to the domain are both structure/non-exhaustive-switch, untyping the input is structure/opaque-switch, and an otherwise arm for the levels nobody has written yet is structure/unreachable-otherwise, answering that it would silently absorb members added to the domain later. Coverage reads 0/2 and that is the right number, since the arms are compile-time and only the rules surviving every build are counted. Best practicesWhy your agent handles a 150-dollar refund two waysAn authority table copied out of a spreadsheet loses its edges on the way into a prompt. Approve a refund up to $150 beside escalate a refund over $100 is 1 prompt/tier-overlap warning at B (81/100), reading the $100-$150 range sits in BOTH tiers, a value there has two handlers and the model must guess, and the deduction lands on style 25 while consistency stays at 100, because an overlap is not two rules that cannot both hold but two rules that both hold when only one can act. A three-row table has two seams and the checker prices them four to one: a shared boundary point is info at -0.25 (exactly $150 sits in BOTH tiers) and an overlapping range is warn at -1, taking the file to C (78/100). check --strict leaves both as advisories at exit 0, so the gate is the score, where score tiers.tg --min B exits 0 and score three.tg --min B exits 1. Then the reason almost nobody has seen this finding: it reads the handler off the front of the line, so You MUST approve … beside You MUST escalate … fires and so does the bare imperative Approve …, while MUST approve … with no subject and either pair behind a - bullet are 0 error, 0 warning, 0 info on the identical amounts, which is the shape most production prompts are written in. Four more silences are about the arithmetic: both tiers guarded with WHEN, both tiers taking the same verb, a floor written at least $100, and any amount in pounds rather than dollars or percent. The message offers state which rule wins and that is not a fix, since appending instead changes nothing; what clears it is making the bounds meet exactly once, up to $150 against over $150, which with a @@ note per rule and two $TEST cases is A (100/100) and coverage 2/3. Three tiers is where prose bounds genuinely run out, because a middle tier's floor must be the tier below's ceiling and English has no way to say just above $150 without inventing $151, so the shipped shape is an ordered $IF chain over a typed amount at A (92/100) with three hashes, b3ef6453103d, ccf288cdf099 and 3f10b604054e, one tier per artifact, and the warning is the unbound build at 553ffa910c2b, which ships all three arms as conditional prose. Best practicesBake-off: import the old prompt vs. retype itThe first decision in the job and the one nobody writes up: you already have a system prompt, so do you convert it or write it again. One eight-rule returns prompt out of a markdown file, two routes, four rounds. typeglish import reports 4 repairs (structure/missing-period, structure/special-character, structure/em-dash, structure/wrapped-fragment), lifts first_name and escalation_threshold to $REQUIRE variable rows, and prints a worklist grouped by plane, five findings it will not touch out of eleven, which is the honest split between form and judgement. Three repairs are unarguable and the fourth is the round: on a hard-wrapped paragraph, structure/wrapped-fragment returns You help customers return. followed by Items, check refund status, a complete sentence the author never wrote plus a fragment, and the checker says nothing because both halves sit in prose. Round 2 is worse and changes what you type in CI: on a variant carrying a second length rule, import printed 0 errors and builds clean and import --gate exited 0, while typeglish check on the file it had just written exited 1 with 2 blocking logic/numeric errors on at most 3 sentences against at least 6 sentences and typeglish build answered refused, nothing written. Round 3 puts the imported file cleared of its worklist at 0 error, 0 warning, 0 info and C (79/100) with annotation 0 and enforceability 61 against the from-scratch file at A (93/100) with annotation 100 and enforceability 68, six facets identical; adding seven @@ notes and changing no policy takes the imported file to A (90/100) with hygiene 50 to 94 while runtime stays at 89, so eleven of the fourteen points were never in the prompt the model reads and the last three are wording. Round 4 is the asymmetry that decides it: the from-scratch artifact is missing two rules the legacy file had, be warm and be concise and if unsure ask a clarifying question, and nothing reports a rule that is not there, while an import cannot discover your tools, so Look up the order with get_order is 0 error, 0 warning, 1 info until $IMPORT tool get_order appears above it and the identical line becomes a blocking structure/bare-tool-ref. Ships as four ordered steps: read the diff prose-first, run check and score --min as their own CI step, declare the tools and expect the error count to rise, then annotate before rewording. Deep divesYour prompt is a default and its exceptionsA mature agent prompt has almost no contradictions left in it, and the pairs that remain are coherent under a precedence nobody wrote down. Five TypeGlish codes end with a version of the same clause, coherent as one default plus one exception, and a ten-rule utility care prompt trips all five at 0 error, 7 warning, 2 info with exit 0, a written artifact at af1cdd422b8b and D (58/100) carried almost entirely by consistency 18. The family reads five different layer pairs: prompt/cap-tension on at most 3 sentences beside confirm the full account balance, prompt/authority-cap on a credit capped at $200 beside an unconditional full credit, logic/only-gate on escalate only when asked beside MUST escalate every meter-reading complaint, prompt/conjunct-cap on at most 1 question beside an ask carrying two whether clauses, and logic/axis-tension on Be formal beside Speak casually on a voice call. check --strict splits the family by namespace rather than by importance, escalating the two logic/ codes to 3 error, 4 warning, 2 info at exit 1 and leaving prompt/cap-tension, prompt/authority-cap and prompt/conjunct-cap as warnings, which are the money, the reply length and the question count, so the gate for this family is score --min B rather than a strict flag. Three fixes cover all five: a scope phrase on the general rule, a stated route for what exceeds a ceiling, and a $IF arm when the host already knows which way the exception goes, taking the same policy to 0 error, 0 warning, 0 info at A (91/100) with consistency 100 and three build hashes, a5dfb4db3b9b for voice, 75cb2e16016d for chat and 8a252b13ca4b for the unbound template that ships both tone lines. The surfaces are narrower than the concepts: 200 pounds draws nothing where $200 and 200 dollars both fire, every charge and a balance in full are silent where the full balance fires, and the question count rides whether and wh-markers rather than plain coordination. GuidesBuilding a grounded help-centre agent, prompt-firstA six-sentence brief for a retrieval-backed support bot, compiled into a spec that checks, and the interesting failure is epistemic rather than structural. Typed straight in the brief is 0 error, 0 warning, 5 info at C (70/100) with hygiene 45, and the two sentences the support lead cares most about draw nothing at all. Seven differently worded grounding rules in one file return exactly one finding, a prompt/unmeasurable on You MUST be accurate, while MUST NOT answer from your own knowledge and MUST ground every claim in a source pass clean: provenance leaves no trace in the output, so no form check can reach it. The trade is to write the observable twin, You MUST end every factual sentence with an article id, backed by a declaration that renders the format, An article id IS a string like HC-1234, which clears the prompt/unintroduced-definite on the article number and gives the test suite something to match. The retrieval miss is a compiler branch rather than a judgement call, since the host knows the count before the model sees a token: $REQUIRE variable article_count: integer with $DEFINE nothing_found AS @{article_count} is equal to 0 drives a $IF chain that builds two prompts, cc021c723665 carrying the citation rule and c0d77f54dace carrying you MUST NOT answer a question and no answering instruction at all, while the unbound template build e9301a6fc11c ships both arms as prose for the model to sort out. A @@ note above the $DEFINE is a blocking structure/unattached-annotation, so the machine plane takes a // comment and the eight rules keep their @@ notes, which is what carries annotation 82. Finished the file is 0 error, 0 warning, 0 info at A (94/100) with hygiene 91 and enforceability 83 as the remaining lever, and two deterministic cases pin it: matches /HC-\d{4}/ with at most 4 sentences on the hit, and the negative written as a lookahead, matches /^(?!.*HC-\d{4})/ with contains ticket on the miss, reporting coverage 1/8 rules exercised under test --dry. Deep divesWe took the scope off every rule. Three came off quietly.A leave-one-out that deletes nothing: every rule stays, every word of policy stays, and the only thing removed is the six or seven words at the front that say when. A broadband care prompt with thirteen statements, nine of them guarded, checks at 0 error, 0 warning, 0 info and scores A (92/100) with consistency 100, and the nine variants split hard. Six removals are blocking, two errors each, logic/contradiction on the credit, callout, restoration-time and transfer pairs and logic/numeric on the two sides of the sentence bound, every one of them capped at F (78/100) on a file that changed by six words. Three removals are completely silent: strip/line_test.tg, strip/billing.tg and strip/survey.tg do not appear in the report at all and land on A (92/100), the same grade as the file they were cut from, while the artifact turns WHEN a caller is an account holder THEN you MUST read a balance aloud into an unconditional You MUST read a balance aloud. Eight facets and only directness moves, 93 to 94, because a rule with no condition in front of it really is more direct, so the scorecard is measuring the deletion accurately and rewarding it. The asymmetry is not a gap in the tool: a guard between two rules is a claim about the file that becomes provably false when it goes, and a guard on a lone rule is a claim about the world the file has never met. Two more traps fall out of building the variants, a comma being no substitute for THEN (2 blocking logic/contradiction plus an info typeglish/if-then reading WHEN needs a THEN) and one-sided scoping being enough for the deontic prover but not the numeric one, where at most 3 sentences against a guarded at least 8 sentences is still 2 errors until UNLESS scopes the general side too. The offline suite cannot separate them either, the same two cases reporting coverage 1/13 and 0 failed on both files, so the gate has to run on the artifact: fourteen lines that list every unconditional rule reaching the model and refuse anything not on a reviewed allowlist, exit 0 on the baseline and exit 1 on all three silent variants. Deep divesYou retired the plan. The prompt still sells it.Products leave, and nothing about withdrawing one touches the document that tells an agent how to talk about it. A broadband support prompt with four rules about a retired Platinum plan checks at 0 error, 0 warning, 0 info and scores B (82/100) with runtime 93 and consistency 100, and exactly one of the four is a defect a compiler can name. Removing platinum from $REQUIRE variable plan: one of essential, complete, platinum is a one-word diff that makes the chain arm testing for it a blocking structure/impossible-case reading @{plan} is never platinum (it's one of essential, complete), with typeglish build answering retired.tg refused, nothing written at exit 1. Deleting that dead arm clears the error and there the trap closes: the file is 0 error, 0 warning, 0 info at B (82/100) again, identical to the pre-retirement reading on every facet, and the artifact at b99007c84615 still instructs an essential customer's agent to waive a Platinum callout fee, quote the 900 Mbps profile and upsell the withdrawn plan to anybody who mentions buffering. The line between findable and unfindable is not importance, it is plane: a closed domain is a promise about which values can occur, so is equal to platinum is decidable, while IF the customer is on Platinum is a noun in a sentence with no set behind it and nothing to prove. Neither the count nor the grade moves, because there is no facet for rules about things that no longer exist and there could not be one. The fix stops writing plan rules as sentences about plans and writes them as arms of the plan: every Platinum rule moved inside a $SWITCH ON @{plan} member arm is 0 error, 0 warning, 0 info at A (93/100) with hygiene 100 and enforceability 68 as the lever, no policy changed and no rule reworded. Then the retirement locks from both sides, since taking the member out while the arm stands is a blocking structure/impossible-case and deleting the arm while the domain stands is a blocking structure/non-exhaustive-switch reading @{plan} can be platinum, but no arm handles it, so a half-retirement cannot compile. The two-line retirement is provably a no-op for everybody else, the essential build landing on ad17a800993a before it and ad17a800993a after, with the word Platinum absent from the deployed prompt and coverage 1/7 rules exercised behind one deterministic contains no callout fee. Best practicesThe prompt is frozen. The artifact is not.A change freeze is enforceable on code because a commit is a discrete thing, and a prompt has a second artefact behind the file that does not move with it. Nine checks, each with a command, for the six weeks after the window shuts. Freeze the artifact hash rather than the commit: typeglish build prints it on the tick and .typeglish/build-manifest.json records it as artifactSha256 beside the vars bag, the compiler version and checkMode full, one row per source, rewritten on each build. Freeze the input bag with it, because the same file bound and unbound is two prompts, c9f0aa8bef91 against db6de4fd4888, and the unbound template build ships both arms of the chain to the model as prose. Freeze the program rather than the file, which is the check most likely to catch a team out: a returns prompt carrying $IMPORT file house.tg as house builds to 649f5f35aac8, and after somebody edits house.tg from at most 4 sentences to at most 6 the byte-identical returns.tg builds to 841aa8af3c0b with the new bound in the artifact, so read the scope the tool prints, program: 1 root, prompts/returns.tg (2 files). Two edits are free and provably so, since @@ notes, // comments, $TEST blocks and the <$CONFIG> section never reach the model: two notes, one comment and a four-line test case leave the artifact on c9f0aa8bef91 while the score goes B (87/100) to A (91/100) and hygiene 50 to 67, which is the reason a freeze is the best six weeks of the year for writing down why a rule exists. Which is also why the score can never be the gate: raising the desk refund limit from 50 pounds to 250 produces a byte-identical report, B (87/100) on both with runtime 100, hygiene 50 and all eight facets unmoved, score --min B at exit 0, and only c9f0aa8bef91 becoming d2584c67c1e4 notices. Three edits look free and are not. fmt is not cosmetic, since a hard-wrapped rule is 3 warnings that build and ship, the artifact carrying the bound on one line and its condition on the next as a separate statement, and formatting moves 59cacac0a0b8 to dc908bb971e8. Run one gate, not two, because the same bytes are 0 error, 3 warning at exit 0 under check and 3 error at exit 1 under check --strict. Record which tier proved it, checkMode full and tiers: base+z3 being the two places that is visible. Then unfreeze by coverage rather than queue order: coverage 1/4 rules exercised with 0 failed names the three rules that ran peak season with nothing pinning them. TeardownsTeardown: nobody checked the Spanish oneLocalization is the least controversial system in a CX org, and the Spanish market file it produces is cleaner than the English one it came from: 0 error, 0 warning, 0 info against 0 error, 0 warning, 1 info, and it scores nine points worse, C (76/100) at enforceability 50 against B (85/100) at enforceability 95. Six faithfully translated rules are six paragraphs, because every statement type in TypeGlish is recognized by an English keyword and a line matching none of them falls through to prose, which is exempt from rule analysis by design. The test runner states it as a whole number: coverage 1/6 rules exercised for the English file and coverage 0/0 for its translation, both ending in 0 failed, because there are no rules left to be uncovered. Exactly one word survives and it is an accident, since Spanish negation collides with the English quantifier NO, so NO DEBES prometer draws hardness 100 while NUNCA prometas and DEBES prometer have no hardness facet at all, and the two lines a Spanish speaker would call the hardest prohibitions are the two the compiler agrees are hard for a reason that has nothing to do with what they say. The role line is the one nobody thinks to leave alone: two files identical but for that line differ by exactly one finding, prompt/implied-doer reading Nothing in this document says who follows its instructions on Eres Marisol against 0 error, 0 warning, 0 info on You are Marisol, and check --strict changes neither file. The sigil planes are language-neutral, so @{agente} with nothing behind it is still a blocking structure/undefined-ref. The cost is not today's file, it is the next edit: appending a five-sentence minimum beside a three-sentence maximum is 4 blocking errors and a refused build in English, and in Spanish it is 0 error, 0 warning, 0 info, an unmoved C (76/100) and an artifact at 84328e5681b7. The fix splits the file into rules the compiler reads and copy the customer reads, with the rules and the role line in English, the reply language written as its own rule, and the canned phrases in a quoted literal zone: 0 error, 0 warning, 0 info at A (99/100) and f615fadf1745. The quote marks are load-bearing, since the same three phrases in a plain tag also check clean and build to 98da135b1d79 with all three joined into one line. Best practicesRewrite: one conversation is not 70 percentThree of the six bullets in a broadband agent's Constraints section came off the quarterly deck, and the scorer cannot tell them apart from the two that work. The before file is 0 error, 0 warning, 1 info at B (83/100) with enforceability 90, its single finding a prompt/unintroduced-definite on the average handle time, and it builds to 5a4035171d4f. Split into one rule per file and the containment target, the engineer-visit escalation and the credit cap produce byte-identical reports below the filename, B (87/100) at runtime 100 with enforceability 100, hardness 100 and directness 100, while the line the grade actually punishes is the softest and most actionable one in the set: MUST resolve most billing questions yourself is C (75/100) with hardness 50, because most is the middle of the quantifier square and the middle wavers. CSAT lands at enforceability 70 since achieve is not a verb the ontology reads, and the handle-time line loses directness to 50 and style to 63 for a definite article correctly reporting that the average is not in the document and could not be. The target is not invisible, it is in the counting layer: at least 70 percent beside at most 50 percent is 6 blocking errors across logic/contradiction, logic/action-count and logic/numeric, and beside at least 90 percent it is 4 across logic/quantifier-strength and logic/action-count, the looser bound being dead weight. Beside a rule it is nothing, including the case that should fire, since MUST NOT resolve a conversation without escalating four lines below the target is 0 error, 0 warning, 0 info and unchanged under check --strict, the count being over conversations and the prohibition over a conversation. So the dashboard is proved against the dashboard and never against the behaviour that decides it. The rewrite trades 70 percent containment for a prohibition on escalating before the account lookup plus an explicit escalation list, trades four-minute handle time for at most 3 sentences and at most 1 question in a reply, and deletes 4.5 CSAT because nobody could name a behaviour behind it: 0 error, 0 warning, 0 info at A (98/100) and 48a0dec70669. Enforceability moves one point, 90 to 91, which is the most honest number in the table, because the three deleted lines were extremely well formed. The nearest measurement is the denominator, coverage 1/6 rules exercised before and 2/7 after with both runs ending in 0 failed, so all three impossible lines sit in the before file's denominator and its real ceiling is 3/6 with nothing in the toolchain saying so. GuidesHow to structure an agent prompt with XML tagsEvery model vendor recommends XML delimiters and none of them mention that a tag you invented is a structural claim the file will hold you to. A nineteen-line support prompt tagged the way the guide says is 3 error, 5 warning, 0 info and exit 1, and not one finding is about a rule. The first rule is a whole-file decision, because one XML section anywhere makes XML the structure model: every remaining # heading is then a structure/heading-outside-section warning naming both exits, a two-heading file with one <tone> section is 0 error, 2 warning, 0 info, and <$CONFIG> is control-plane so it neither triggers this nor counts as a home. The second is the one nobody guesses, since XML sections nest and a body is scoped by a two-space indent: a flat file draws structure/bad-indent per content line reading expected column 2 (1 level deep), found 0, it still builds because these are warnings, and the artifact comes back flush left, so the indent is for the compiler and the next reader rather than for the model. The third is that a tag name is a name in a scope: two <escalation> blocks are blocking structure/duplicate-section, reported once on each participating tag with each naming the other and the message reading the structure (and any reference to "escalation") is ambiguous, which is usually two teams shipping one policy name, and it sits beside structure/unclosed-tag and structure/stray-close-tag, all three blocking because a section whose extent is undefined cannot be compiled. The fourth ships a broken prompt rather than a noisy source file. A three-line output template in a plain <wrapup_format> tag is 0 error, 6 warning, 1 info across three structure/missing-period and three structure/wrapped-fragment, and it builds to disposition: resolved summary: one sentence next_action: none with all three lines joined into one, while the identical content inside a quoted <"wrapup_format"> is 0 error, 0 warning, 0 info and keeps its three lines and their indentation, emitting the tag bare. <examples> is exempt from rule analysis, so MUST offer a callback against NEVER offer a callback inside it is entirely silent where the same pair inside a <tone> tag is 2 blocking logic/contradiction errors, but exempt is not verbatim and it will still reflow. A tag is also not a scope for a bound. The finished file applies all four moves with no rule rewritten: 0 error, 0 warning, 0 info at A (98/100) with structure 100 and annotation 100. Best practicesWhy your agent never offers the credit you allowedA meal-kit support prompt hands its agent three pieces of discretion as MAY rules and is 0 error, 0 warning, 0 info at C (78/100), where hardness 40 is arithmetic rather than opinion, two of the five rules commit to a pole and three do not. Isolated, a single MAY rule scores C (75/100) with hardness 0 against B (87/100) with hardness 100 for the identical action written MUST, so a permission is not marked down for being weak, it is priced at nothing. Ten controlled pairs over one action put the boundary exactly on the deontic sign. Every obligation beside a MAY is a blocking logic/force-subsumption error, MUST, SHOULD and ALWAYS alike, the message naming the entailment each time ("should" entails "may" for the same action, so it adds nothing but waver), while NEVER, MUST NOT, DO NOT, AVOID and SHOULD NOT beside the same MAY are all 0 error, 0 warning, 0 info, unchanged under check --strict. So the file that reads most obviously broken to a human is the one nothing reports, since MUST beside NEVER on that same action is 2 blocking logic/contradiction errors. The bound leaves with the modal too, which is the part most likely to be load-bearing: MAY apply a goodwill credit of at most 15 dollars beside MUST apply a goodwill credit of at least 50 dollars is clean, and changing that one word to MUST is 2 blocking logic/numeric errors on identical amounts, so a bounded MAY records the ceiling for a human and hands Z3 nothing. The rewrite splits every permission into the two decisions it stood in for, the case as a guarded obligation and the ceiling as an unguarded bound on an obligation: C (78/100) to A (98/100) with hardness 40 to 100 and enforceability 90 to 95, and no policy changed. The payoff is the edit that has not happened yet, one appended MUST apply a goodwill credit of at least 25 dollars, which is 2 blocking logic/numeric errors and a refused build against the rewrite and 0 error, 0 warning, 0 info against the original. Deep divesThere is only one supervisorEvery the in an agent prompt claims that exactly one of the thing exists, and the compiler takes the claim literally enough to be worth four minutes of your Constraints section. A four-rule utility support prompt with no contradictions and one measurable bound is 0 error, 0 warning, 5 info at C (70/100), all five findings prompt/unintroduced-definite on the supervisor, the balance, the account, the disposition and the call, each priced at a quarter in the ledger and landing entirely on style 25, while the customer in the same rule produces nothing because a customer is a role the language already knows. In subject position the article is a declaration rather than decoration: The supervisor IS Dana in a Context block beside Supervisor IS Alex in a Notes block is 2 structure/split-declaration warnings naming each other plus one prompt/duplicated-declaration, exit 0, and the artifact at 046d06dd7cb1 renders Dana on one line and inlines Alex into the rule three lines below. The uniqueness is provable and it is proved only where it already holds: Dana IS a supervisor beside The supervisor IS NOT Dana is a blocking logic/definite-identity error reading the only declared supervisor, line 4, and adding Alex IS a supervisor to that same file leaves the denial in place and returns 0 error, 0 warning, 0 info with prompt/unintroduced-definite going quiet too, so the file carrying the real ambiguity is the silent one. A definite is also not a pointer: $REQUIRE variable supervisor beside transfer to the supervisor in prose is 2 warnings that both say the variable is never used, structure/unused-import and clarity/unused-variable, 2 blocking errors under check --strict, and an artifact at a012e8cb93e3 that ships the English with no hole for the host to fill, where @{escalation_target} would have carried {escalation_target}. The rewrite sorts every definite into three piles, a role the language knows, a thing the file can introduce, and a thing only the runtime knows, and moves the score without changing what any rule means: C (70/100) at style 25 and directness 63, C (79/100) at style 100 and directness 95 once the pointers land, A (91/100) once the @@ notes do, and enforceability a flat 60 across all three, at coverage 1/2 rules exercised behind one deterministic assert. Best practicesBake-off: the rule in the prompt vs. the rule in the tool descriptionEvery function-calling guide says to write a good tool description, and a good tool description is where policy goes to hide. One credit cap, two homes: as a Constraint it is B (82/100) with enforceability 75, and inside the tool's - description:: field the identical sentence is C (76/100) with enforceability 50, both files 0 error, 0 warning, 0 info under check --strict, because a NEVER in a description is not a weak rule to the scorer, it is not a rule. Round two is the edit that arrives eight months later, one appended MUST apply a credit above 50 pounds in the same house style as the lines above it: the prose version is 2 blocking logic/contradiction errors at F (67/100) with the build refused and nothing written, and the description version is 0 error, 0 warning, 0 info at B (82/100) building to 17564816aca3, its grade going up because the rule that broke the policy lifted enforceability from 50 to 75. One honest edge is that A wins that round only because the appended line was unscoped, since MUST apply a credit of at least 75 pounds after a long outage is clean in both. Round three pins the boundary in a four-row matrix, prose and prose 2 error at F (67/100) refused, description and prose then prose and description both 0 error at B (82/100), description and description 0 error at C (76/100), and one description holding NEVER apply a credit above 50 pounds and ALWAYS apply a credit above 50 pounds in a single line is 0 error, 0 warning, 0 info under --strict. The description is scanned and never parsed: prompt/hedging on Try to, prompt/vague on reasonable, security/leaked-secret on an AWS key literal and prompt/declared-later on a you above the Role line all fire inside it, while deontic force, the counting layer, the contradiction prover, every point of enforceability and even structure/em-dash stay outside. Round four grants B its real argument, because build --bundle puts the description verbatim on the tools array while the prompt text at fe3eed5dbf1a carries no trace of it, so the policy does ship, attached to the tool, at the point of the call. The third plausible home fails quietly, since amount:: number between 1 and 50 checks clean and the bundle records that parameter as type number with the refinement gone. The shipped spec keeps the description for what the tool is and when to reach for it and puts the cap back in the graded plane with a destination for the overflow: A (93/100) at coverage 1/1 behind a negative regex rather than a negative contains. Deep divesYour prompt cannot tell a missing input from a real oneAn ablation run twice over the same six declared inputs, once on the file and once on the bag the host sends, and the two columns disagree completely. The baseline care prompt is 0 error, 2 warning, 0 info at B (85/100), and both warnings name the same dead input, structure/unused-import and clarity/unused-variable on tier, which check --strict turns into 2 blocking errors, so the checker has already answered which declaration is safe to delete before the ablation starts. Deleting one $REQUIRE variable line at a time is 9 errors across five files: a prose pointer is 1 blocking structure/undefined-ref, while a $SWITCH, a numeric $IF arm, a $DEFINE and a section guard each report twice, once as the dangling pointer and once as structure/undeclared-input at the construct, and the $DEFINE reports at one hop only, since the two $IF @{eu} arms downstream stay silent. Five variants cap at F, four of them at 77, and the sixth goes the other way: drop/tier.tg is 0 error, 0 warning, 0 info at A (90/100) with consistency and structure back to 100, five points above the baseline it was cut from, so deleting the orphan is the only edit in the set that pays. Then the same leave-one-out on the host side, and six builds exit 0 with no diagnostic. Two change nothing, since tier is unread and agent_name is a runtime hole that ships as {agent_name} in every build including the one that bound it to Dana. Two ship the unbound template as prose. And two answer anyway: a missing region builds the Ofcom arm for an Irish customer and a missing verified ships the escalation section with the when= attribute stripped. Nine builds over the two variables give exactly four hashes, e22f25e22479, fff9f6d7fc43, a0fa4f73f65a and f2c0b12014e4, and the five omissions land precisely on top of four of them, so a missing region is byte-identical to region uk and a missing verified is byte-identical to verified true. Nothing else reaches it either, since check and score read the file and typeglish test takes no --vars, reporting coverage 2/7 on a case whose assert names an arm no binding selected. The gate is build --bundle, whose inputs array names every declared input and its type (tier included, because the bundle reports declarations rather than uses), diffed in ten lines against the host's variable bag: bound six times at exit 0, and UNBOUND region and UNBOUND verified at exit 1 on the release that stopped sending them. Archive the build manifest rather than the hash, because its vars object is the only receipt that records the question. GuidesBuilding an agent-assist drafter, prompt-firstThe assistant that drafts the reply a human agent sends is the first prompt in a contact centre where the word agent has two referents, and the six-sentence brief typed straight in is 0 error, 0 warning, 3 info at C (73/100) with enforceability 58, where the cleanest line in the file is the one that hands the whole job to a different person. Subjects settle first: You MUST NOT send a reply to a customer beside The agent MUST send a reply to a customer is 0 error, 0 warning, 0 info, and changing the second subject to You makes the identical pair 2 blocking logic/contradiction errors, so every rule the model follows is addressed to You and the human agent lives in a prose block as a fact rather than a rule. Then the finding that costs a revision, which is that a section is not a scope for a bound. Two length rules in two XML sections, write at most 5 sentences under a draft tag and write at most 1 sentence under a note tag, are 4 blocking errors across logic/quantifier-strength and logic/action-count, while naming the object (keep a draft to at most 5 sentences beside keep a note to at most 1 sentence) and naming the unit (write at most 5 sentences in every draft beside write at most 1 sentence in every note) are both entirely silent, and mixing the two spellings in the finished file is prompt/count-alignment at warn, A (95/100) against A (97/100). The output template turns up the one mark a triple-backtick fence does not protect: an angle-bracket placeholder inside a fence is a blocking structure/unclosed-tag, and the fence's real contribution is visible by subtraction, since the unfenced twin is the same 2 errors plus 2 structure/missing-period warnings while the square-bracket version is silent. Three spellings of the send prohibition land in three places, structure/unused-import for an imported send_reply banned only in prose, nothing at all for NEVER call @[send_reply], and a blocking structure/undefined-tool-inline for a pointer nobody imported, and the pointer lowers to the bare name in the artifact so no schema ships with it. The finished nine-rule spec is 0 error, 0 warning, 0 info at A (97/100) with runtime 100 and enforceability 100, builds to b141f62d0612 at 1,045 bytes, and reports coverage 1/7 behind one deterministic case; $EXAMPLE fields are one line each, so a two-part example spanning four lines is a blocking structure/bad-example saying the good field needs its text on the same line, and the bad draft that promises Thursday and a refund is held out of the artifact entirely. Best practicesYour coding agent writes the prompt nowThe system prompt is a file in a repo now, so the thing that edits files edits it, and nine checks keep the ordinary week ordinary. The cheapest comes first: a returns prompt drafted from a model's priors is 1 error, 5 warning, and four of the six findings are about characters rather than policy, including structure/em-dash whose message says models mirror the tic (the canonical AI-tell), structure/special-character reading that a lowered symbol carries no bound while at most 3 is Z3-provable, two structure/curly-quote, and a blocking structure/bad-test on a retired EXPECT line, none of which survives one read of typeglish reference. typeglish mcp then serves the compiler to any MCP client over stdio at serverInfo typeglish 0.9.0, with exactly five tools, typeglish_check, typeglish_build, typeglish_score, typeglish_explain and typeglish_reference, four of them readOnlyHint, and both check and build take an inline source string so the agent can compile a candidate before the working tree changes. There is no test tool, no fmt tool and no resolve tool, so test --dry and fmt --check stay in CI where the agent has no hands. The check that earns the post is the one that is genuinely new, because an agent asked to make the check pass will make the check pass: a returns file with 2 blocking logic/numeric errors and a refused build has two one-line fixes, and the patch that deletes the 50 pound cap and the patch that keeps it are indistinguishable to everything automatic, 0 error 0 warning 0 info on both, B (87/100) on both with byte-identical facets, and coverage 1/2 rules exercised from the same suite. Only the artifact separates them, 54d4310aa849 against dc908bb971e8, which is why the built prompt belongs in the pull request rather than the source patch. The score is a target too, since three @@ notes and nothing else take a file from B (87/100) to A (100/100) with two different source hashes and the identical artifact hash dc908bb971e8, which is both the warning and the reason to ask an agent for the notes, an annotation being the one edit that provably cannot change behaviour. Last is fmt --check first, because a hard-wrapped rule is 3 warnings, none of them about the rule, and 0 after formatting. Deep divesYour agent has two namesA rebrand leaves a support bot with three names in one file, and the file checks at 0 error, 0 warning, 0 info and scores B (84/100) with consistency 100, because a possessive binds a property of its owner and the three lines never reach one address. Your name is Dana writes the value dana to the slot you.name, and the artifact at eb63621adb81 puts two of the three names on the same physical line of the deployed prompt without producing a diagnostic. Six controlled files separate cleanly and exactly one is reported: a second Your name is Aria is 2 blocking logic/contradiction errors reading single-valued you.name is assigned 2 different values, capped at F (50/100), while Name IS Aria, You are Aria, The assistant's name is Aria, The agent's name is Aria and The bot IS Aria are all silent beside it. The pointer is the mechanical test, because it has to resolve against something actually declared: @{you.name} beside Your name is Dana inlines Dana at d006f518b309, @{assistant.name} beside The assistant's name is Aria inlines Aria, @{name} beside Your name is Dana is a blocking structure/undefined-ref, and the surprise is @{you.name} beside You are Aria, which is 0 error, 0 warning, 0 info under both check and check --strict and ships the literal {you.name} to the model at da16c0a7eac4, because a property pointer with no compile-time target lowers to a runtime hole rather than a dangling reference. The customer half of the file runs the same way: The customer's tier is gold beside silver is 2 blocking logic/contradiction errors reading both name a metal, and a single-valued slot holds one; a chained possessive reports at two depths, as customer.account.balance and again as customer, with logic/measure alongside; and The customer's tier is gold beside The caller's tier is silver is entirely silent, which is one person under two owners. The rewrite binds the name once and hands the per-conversation facts to the host as $REQUIRE variable customer_tier: one of gold, silver, landing at 0 error, 0 warning, 0 info and A (99/100) with enforceability 93 as the lever, three builds at 2f21be9f0006, 9f5e8afc6ee2 and an unbound template at 7b0fc3d95eb4, and coverage 0/4 behind one deterministic contains Dana. TeardownsTeardown: your agent has no team lead to askThe new-hire handbook is the second document that gets pasted into an agent prompt, after the policy doc, because it is the one that says how the floor actually talks to customers. Unlike a QA rubric it arrives in the right grammar: second person, imperative, addressed to you. A seven-line Working here excerpt checks at 0 error, 0 warning, 6 info and scores C (74/100) with enforceability 85, which is higher than a pasted QA scorecard manages, because these really are well formed instructions. They are well formed instructions for somebody else. Four of the six findings are prompt/unintroduced-definite on the queue, the duty manager, the handbook and the floor, which are the four nouns in the excerpt that exist only in a building; the definite article is the one place a pasted document leaves a trace of the world it came from. The other two are prompt/unregistered-doer on you will get a feel for how we talk to customers, the future tense of onboarding, and typeglish/if-then on a line where a comma is standing in for a THEN. The two lines that produce nothing at all are the two that are purely about being a new employee: three days of shadowing, and do not worry about getting it perfect at first. The paired proof for the handoff: WHEN you are not sure about a return THEN you MUST ask your team lead is 0 error, 0 warning, 0 info, while the same rule written as you MUST call @[ask_team_lead] is a blocking structure/undefined-tool-inline, because a pointer has to resolve and English does not. The rewrite deletes the employment lines, turns get a feel for how we talk into at most 4 sentences, replaces ask your team lead with a rule about what to say when the lookup comes back empty, and declares the duty manager as an $IMPORT route: five rules, 0 error, 0 warning, 0 info, A (93/100). Enforceability goes down three points in the rewrite, from 85 to 82, which is the point: the facet measuring whether the rules were well formed was never the one that was wrong. Best practicesRewrite: the sentence that repeals the rest of your promptEvery agent prompt ends with a discretion clause, and it is the only defect in the file with a constituency: somebody put it there on purpose and can name the incident that caused it. A five-rule service prompt for a furniture retailer, closed with These rules are guidelines. Use your best judgment when a case does not fit them and If in doubt, do what is right for the customer, checks at 0 error, 0 warning, 1 info and scores B (80/100) with consistency a flat 100, because the line that overrules five rules is prose and prose is never compared with anything. Six phrasings of the same hatch, checked against one file, split exactly backwards from what you would want: try to and Where possible are prompt/hedging, when it is reasonable is prompt/vague, and Use your best judgment, These rules are guidelines rather than hard requirements and You MUST use good judgment are all 0 error, 0 warning, 0 info. The three the checker names are the weak ones that soften a single instruction; the three it cannot see are the ones that repeal the section, and they pass because they are not vague, they are precise. A cleanup pass therefore selects for the strongest escape hatch. It gets worse first: annotate the unchanged before file with @@ notes and it scores A (91/100) with enforceability still 74, which is an honest reminder that there is no facet for a clause that makes the other clauses optional. The rewrite splits the hatch into the two jobs it was doing, a bounded latitude (You MAY offer a credit of at most 50 dollars without approval) and an explicit fallback (WHEN a request does not match a rule in this section THEN you MUST escalate the request to a human agent): 0 error, 0 warning, 0 info at A (94/100) with enforceability 90. Strip the annotations back out and it is B (83/100) with enforceability still 90, so the rules bought sixteen points of enforceability and the notes bought the grade. The cleanest measurement is the test runner: seven bullets and coverage 2/5 rules exercised before, seven bullets and 2/6 after, and a controlled trio proves a MAY never moves the denominator (0/1, still 0/1 with a MAY added, 0/2 with a second MUST). Best practicesWhy your agent skips step oneA numbered procedure in a system prompt is four instructions printed one under another, and it carries no ordering anything can enforce. An account-servicing prompt whose Instructions section numbers verify, open, state the balance, offer a plan, with one appended rule saying MUST offer a payment plan before you verify the caller identity, is 0 error, 0 warning, 2 info at B (81/100) with consistency 100, both findings are about the definite article, and --strict escalates nothing. The same policy written as pairwise links is 3 blocking logic/order errors at F (65/100) with consistency 0 and the grade capped, reported once per participating rule with each report naming the other two, so no pair of the three conflicts on its own. The experiment that settles it: MUST state the outstanding balance before step 3, in a file where step 3 IS that action, is 0 error, 0 warning, 1 info. The ordinal is not ignored, though, because before step 9 in a four-step file is a blocking structure/dangling-step-ref, and two claims about one ordinal (before step 1 beside after step 1) is 2 logic/order errors. A step number is a node the timeline can link, and the sequence 1 then 2 then 3 is not a set of links. Six spellings of one reversal separate cleanly: before, after and prior to each give 2 error logic/order, while then, once you have and first ... second are all 0 error, 0 warning, 0 info, which means the three phrasings that read most like a procedure are the three that build nothing. The rewrite keeps the numbering as reading order and writes the two transitions that carry real risk (a disclosure and a priced promise) as before links: 0 error, 0 warning, 0 info at A (91/100), and the identical handle-time edit that was invisible in the first version is now 3 blocking logic/order errors at F (74/100), landing on the compliance gate as well as the new line. GuidesHow to write what your agent says when the lookup failsEvery agent prompt has a rule for calling the tool and most have no rule for the call coming back empty, so the model composes the most plausible support sentence available and a customer gets an invented depot delay with a date attached. The happy-path-only file, WHEN a customer asks about a delivery THEN call @[get_order_status] and read the result back with a SHOULD apologize if something has gone wrong underneath it, is 0 error, 0 warning, 0 info at B (82/100): there is no diagnostic for a missing world, because the checker cannot know which outcomes your tool has. The guide's four moves are to list the outcomes a customer can tell apart rather than the HTTP codes, declare them as a closed domain the host binds ($REQUIRE variable lookup: one of found, missing, offline, single English words, since a key like no_match is reported as structure/undeclared-tool), write one arm per member, and add the prohibition on the invented cause. Choosing the plane is choosing whether the omission is enforced: the same three-outcome decision written as prose IF and ELSE IF over the same typed variable, with offline unhandled, is 0 error, 0 warning, 0 info under both check and check --strict at C (76/100), while a $SWITCH ON block missing that arm is a blocking structure/non-exhaustive-switch naming the member, @{lookup} can be offline, but no arm handles it. The finished spec is 0 error, 0 warning, 0 info at A (92/100), and the compile-time chain hands the model one instruction set per binding: offline builds to a80fe4ac5d8e, found to 0b4880b58c82, while the unbound template at 1012ff9bf85d ships all three arms as prose and is the shape to avoid. NEVER state a cause for a late delivery is the load-bearing line, because it puts the invented cause in the same slot as every other rule about explaining a delivery, and a matches /^(?!.*(depot|courier|tomorrow)).*$/ assert with at most 3 sentences pins it, at coverage 0/6 offline with a live run needing ANTHROPIC_API_KEY. Deep divesOnce per what?A system prompt is re-read from the top on every turn, so every rule in it is a rule about the reply being composed now, and the words people add to mean otherwise are read by almost nothing. The greeting pair is the shape of it: ALWAYS greet the caller by name beside MUST greet the caller at most 1 time in a conversation is 0 error, 0 warning, 0 info, --strict escalates nothing, B (87/100) with consistency 100, and both lines ship to the model together at 64f52b74da87. The reason is that a bound is not a pole. ALWAYS greet the caller against NEVER greet the caller is 2 blocking logic/contradiction errors, and the identical impossibility written ALWAYS greet the caller against MUST greet the caller at most 0 times in a conversation is silent, because the count layer and the deontic layer never meet. Then the sharper result, which is that the unit is not part of the slot either: MUST request at most 1 verification detail in each reply beside MUST request at least 2 verification details in a conversation is a policy any verification desk runs and 2 blocking logic/numeric errors, and it stays 2 errors in each conversation, per contact and in every reply, while the unscoped pair adds logic/contradiction for 4. Exactly one diagnostic reads the unit at all, prompt/count-alignment at warn, and it fires only when one bound names a unit and the other does not, so applying its advice clears the warning and leaves the errors. The rewrite gives the count to the host: $REQUIRE variable details_verified: integer with a $IF chain resolves at build time, so 0 builds to 2ac704a478e8 and 2 builds to 7fad5f8b6347 with one instruction each, while the unbound template at bd5c8d304c29 ships both arms as prose. The offline suite has the same unit, since a $TEST case is one user turn and a second - input:: row is structure/duplicate-term with the first turn dropped at coverage 0/1. Best practicesBake-off: tests in the prompt vs. a file beside itTwo layouts for one billing agent and its eval suite, inline $TEST blocks against a sibling billing.test.tg, and round one is a tie on every instrument: 0 error, 0 warning, 0 info both ways, B (85/100) with byte-identical facets, coverage 1/3, and the same artifact at 415e35be65a8, since the merged suite is control plane and has no inbound edges. The only trace of the layout anywhere in the toolchain is one parenthesis in test --dry naming the sibling, and scoring that sibling alone returns A (100/100) on four facets, because a file with no rules has nothing to mark down. The separation is failure. The same mistyped expectation, - expect:: contains "account number" written on one line, is a blocking structure/bad-test inline that exits 1, refuses to build with billing.tg refused - nothing written, and takes score to F (73/100) with the grade capped and runtime 97 untouched, while in the sibling the prompt file still checks at 0 error, 0 warning, 0 info, scores B (85/100) and builds 415e35be65a8, with only test --dry and a directory check reaching the defect. Then the failures that belong to the sibling alone. Rename the prompt and the suite detaches in silence, since test on the renamed file prints a tick and no $TEST cases at exit 0 and check on the directory is clean, while typeglish test *.tg --dry is testfile/missing-prompt at exit 1 and typeglish test . is EISDIR, because test takes no directory. A case id duplicated across the two files is testfile/duplicate-case from test and nothing at all from check. And build on a directory writes an artifact for the test file too, e3b0c44298fc being the SHA-256 of the empty string, with its own manifest row; write a rule in the test file and it counts in the prompt coverage denominator at 1/4 while reaching no model. Verdict: inline by default, the sibling when the suite is large or another team owns it, and either way the gate has to name the test files, since no default command reaches them. Deep divesYour bound is for the rule you have not written yetAn ablation over the numbers rather than the rules: one clean broadband support prompt carrying five bounds, and five variants that each replace one bound with the word it stood in for. All five check at 0 error, 0 warning, 0 info, --strict escalates nothing, and three of the five score an identical A (100/100) with byte-identical facets, including the file whose only length rule now reads You MUST keep every reply short. The two that move land at A (98/100) with enforceability 94, and the lever text they print is the advice the post opened with, worth +1 overall, so no floor anybody sets in CI separates the five from the baseline. Then the correction to the setup, which is that vague is not the property that matters. promptly, without delay and straight away are typed durations and conflict with within 5 minutes while quickly, swiftly and as soon as possible are silent; ask one question and ask a single question are counts and conflict with at least 2 questions while ask one question at a time and twice are silent; under 20 pounds is money and a small amount is not. The boundary is the lexicon and not the precision, and the three words that make a rule clearer to a human are exactly what move it out of the counting layer. The second column is the real measurement: append the widening edit a team actually ships to each variant, and the bounded files are 16 blocking errors across five refusals while the de-quantified files are 2, the only survivor being the duration, whose replacement word was itself a bound. So the gate cannot read the current file. Keep one opposing rule per bound, generate the fixture at CI time, and require check to fail: five rows print guarded and exit 0, and swapping one bound for its vague version turns exactly one row UNGUARDED and the exit code to 1. The $IMPORT file spelling of the same fixture is tidier and catches four of the five, because the Z3 numeric plane does not cross a file boundary and the one it drops is the money bound. GuidesBuilding a proactive outage agent, prompt-firstEvery agent prompt in this series answers something. This one speaks first, and both habits that carry an inbound spec break. A six-sentence proactive-outage brief typed straight in is 2 error, 0 warning, 4 info at F (52/100) with consistency 0 carrying the cap, and build refuses to write an artifact, because the handover pasted two clocks into the file: An outage takes 4 hours beside You MUST resolve an outage within 2 hours is 2 blocking logic/time errors naming the slot they collided on, outage duration. The boundary is narrow and worth knowing, since renaming the noun to a repair is silent, swapping resolve for restore is silent because only four completion verbs put a deadline on a duration slot, and widening to within 8 hours is genuinely fine. Neither number survives the rewrite: a restoration estimate in a prompt is a number the model repeats and a customer hears as a promise, so both lines are deleted and one prohibition replaces them. The anti-spam line becomes a count, because MUST send at most 1 message for each outage stage refuses a later at least 2 messages edit with six blocking errors on three codes, while a daily update rule and a whenever engineers make progress rule both land clean, which is two silent out of three and the honest hit rate. Then the part that is specific to speaking first: a $TEST case is a user turn plus expectations and will not compile without the turn, so omitting the row is a blocking structure/bad-test asking for an input line and a bare - input:: is a blocking structure/bad-test saying the row needs the user turn. The opening message therefore has no case and needs a different pin, and the first instinct fails visibly, since a quoted literal in the disclosure rule checks clean, builds to c59443ad7f72, and reaches the model with its quotes stripped and no boundary left between the instruction and the copy. A triple-backtick fence under # Output ships the approved line byte for byte and the artifact hash becomes the regression test. The finished spec is 0 error, 0 warning, 0 info at A (95/100) with enforceability 83 as the honest lever and coverage 1/6, since five of the six rules govern a message nobody sent us and the suite can only reach the one that answers. Three builds give three hashes, and the unbound template is the warning, shipping both switch arms as one line that tells the model both that engineers are working on a fault and that service is back. Deep divesYou did not narrow that rule. You moved it.Every exception in an agent prompt is a few words added to a rule that turned out too strict, and the words decide whether anything downstream can still referee the pair. A transcript policy with one prohibition and two obligations about the same action, NEVER send a transcript beside MUST send a short transcript after the call ends and MUST send the caller a transcript on request, checks at 0 error, 0 warning, 0 info and scores B (85/100) with consistency 100, the facet that exists to measure rules against each other. Delete six words and the same three rules are 2 blocking logic/contradiction errors reading send a transcript. is both required and forbidden, which makes strip-and-recheck the one procedure that tells you from the outside whether your qualifiers were load-bearing. Six controlled files map the boundary and it runs opposite to intuition: nothing at all and a one-sided IF guard are both caught as 2 logic/contradiction errors, because a blanket prohibition applies inside the guarded case too; an adjective on the object is caught on its own code as 1 blocking logic/modifier-contradiction at the column of the modifier, reading the prohibition covers everything the obligation demands; and a preposition (to a verified caller), a dative recipient (send the caller a transcript) and an adverb (immediately send) are all silent, which are exactly the three edits that change no policy. The fold has a direction and the asymmetry is sound: forbidding a short transcript while requiring a transcript is clean because a long one satisfies both, and softening the obligation to SHOULD is clean because a recommendation makes no demand. The fix is not narrowing one side but making both sides impossible to co-apply, and the compiler plane is the strong version: the identical MUST/MUST NOT pair is 2 errors flat, clean in opposite arms of a $IF over a declared boolean, and 2 errors again inside one arm, that time with a witness naming the assignment, when caller_verified = true. Shipped as an arm at A (98/100), where two builds carry one transcript rule each at 9d2b4d23ba3d and aad4178253a0, so the model is never asked which rule wins.8 min Best practicesLegal has your prompt open in a tabNine checks before somebody outside your team reads the file that runs your agent, each with a command behind it, and the first two are about what is being reviewed at all. A .tg file is source, so scaffolding, notes and tests never reach the model and a conditional resolves differently per segment: hand over the artifact and its hash, and note that a file with a proven error refuses to produce one, printing care-agent.tg refused - nothing written at exit 1, which is a better failure than reviewing a source file nobody deployed. Three of the four defects in the file under review are already in the report at 1 error, 1 warning, 1 info and F (64/100), and the severities are close to inverted relative to how a reviewer ranks them. The disclosure rule is the finding the checker does not make: You SHOULD be transparent about being an AI names no action and no moment and does not oblige, while You MUST state that you are an AI assistant in your first reply is a directive a $TEST can pin. You MUST handle personal data appropriately is prompt/vague at info, naming a judgment call with no criterion, and the fix names the field and the operation instead. MUST escalate to a human agent when the caller asks reads like the strongest line in the file, and structure/unused-import is why it is not, because the declared transfer_to_human tool is referenced by no rule and the model cannot discover it. The credential is the blocking one, and three placements of the same token give three verdicts: behind the word Bearer in a rule the model reads it is a blocking security/leaked-secret, in a $SERVICE header it is structure/secret-literal at warn with the @{env.NAME} rewrite attached, and bound to a name with a copula it is silent. For evidence, build once per member of every typed domain and count hashes rather than segments, since uk, de and fr yield two distinct artifacts and not three, while the manifest holds one row per source so the archive has to be the CI log. The gate is two exit codes, where --strict takes one error to two on a file nobody edited and score --min A exits 1 on the rewrite at 88, so B is the honest floor. F (64/100) to B (88/100) with enforceability 57 named as the lever, because half a compliance section is triggered behaviour and a conditional is genuinely less enforceable than a bound. Then it stops, because two clean files settle what a compiler will not sign: the prompt requiring the agent to state it is an AI assistant scores B (89/100) and the prompt requiring it to tell the caller it is a human advisor named Dana scores A (92/100).9 min ReleasesTypeGlish 0.9.0: the deadline meets the duration0.9.0 lands the clock-containment arc, and the half with a demonstrable delta is the takes frame: a sentence stating how long the work actually needs is typed time now instead of prose. A refund takes 5 days beside You MUST resolve a refund within 2 days is 2 blocking logic/time errors on one refund duration slot, reading that the windows never intersect, and the byte-identical six-line file checks 0 error, 0 warning, 0 info on 0.7.2, the newest version anyone could previously install. The fix is a policy decision rather than a syntax repair: within 7 days contains 5 days, so the file goes quiet. Exactly four completion verbs put a deadline on that slot, resolve, close, finish and complete, while process, handle, issue, send, approve, deliver, answer and escalate stay silent, which is defensible because issuing a refund and a refund completing are two different events. One real boundary: the frame crosses with within and under and not with at most or at least. The window half of the arc is unchanged from 0.7.2 in every shape tested: two windows on one slot conflict, a clock time outside a declared window conflicts, and a time inside it checks clean, which is containment doing its job. The second changelog entry is five pointer-SSOT follow-ups, and the practical effect for an npm user is structure/bad-pointer-name, a blocking code they have never seen: @{customer-id} is silent on 0.7.2 and one blocking error on 0.9.0. Upgrade note: 0.8.0 and 0.8.1 were never published to npm, so this jump crosses three releases and, unlike 0.7.2, it adds findings rather than retracting them.8 min Best practicesRewrite: the rules that say usuallyThe vagueness arrives in the handover, because it is how the policy was described: we usually escalate those, we rarely send them to retention, we generally keep it short. A six-rule billing prompt written that way checks at 0 error, 0 warning, 0 info and builds, and the only part of the toolchain with anything to say is the score: C (66/100), enforceability 28, with a lever line naming the cause in one sentence. A frequency adverb is not a hedge, it is a quantity claim over occasions, and the words form a square of opposition: usually, mostly, generally and typically claim most occasions, sometimes and occasionally claim some, rarely and seldom claim few, and ALWAYS and NEVER are the all and none corners. The square is fully wired and proves what it can: Usually escalate a billing dispute beside Rarely escalate it is 2 blocking logic/contradiction errors reading Frequency conflict, required on most occasions but required on few occasions, and ALWAYS apologize beside Sometimes apologize is 3 blocking errors, two logic/quantifier-strength plus logic/force-subsumption on the weaker line. Six rules about six different actions collide with nothing, so nothing is proven, and a file where nothing collides is not a file that has been checked. The surprise is what one word does to a bound. You MUST keep every response to at most 4 sentences beside You MUST write at least 6 sentences is 2 blocking logic/numeric errors, and the identical pair under Generally, Usually, Typically, Mostly or Rarely is 0 error, 0 warning, 0 info, as it is under SHOULD, because a rule that does not obligate does not put its number where a proof can reach it. The rewrite asks per rule what the adverb stood in for: usually and typically were unwritten conditions and became WHEN THEN guards that finally name the numbers (disputes a charge twice, a second outage in one month), rarely was a prohibition with an exception and became UNLESS a caller asks to cancel, generally was a flinch and became MUST, and occasionally genuinely meant up to you and became MAY. C (66/100) with enforceability 28 to B (83/100) with enforceability 94, both files checking identically clean, so the tick was never the measurement and the floor to put in CI is score --min B, which exits 1 on the before file and 0 on the after.8 min TeardownsTeardown: your QA scorecard is not a system promptThe QA scorecard is the most reviewed document a contact centre owns, so pasting it into the agent prompt feels like the opposite of cutting a corner, and it lands as 0 error, 3 warning, 4 info at D (62/100) with directness 53 and style 0. Read the line numbers, not the codes: the findings land on the rows nearest to being usable, and the two rows that open with Did the agent produced nothing at all. A rubric is written in three grammars, none of them a directive. The interrogative asks a grader for a verdict and classifies as prose, the catch-all. The past-tense report is a claim about a third party called the agent, and the agent reading it is you, which is what prompt/unregistered-doer names when the subject drops to a bare Agent. The agentless passive deletes the actor outright, and prompt/agentless-passive fires twice with the fix attached. Four controlled files settle it: against a rule capping greeting at 30 seconds, the directive You MUST greet a caller within 15 seconds is 2 blocking logic/time-strength errors, while Did the agent greet the caller within the first 15 seconds? is 0 error, 0 warning, 0 info, A caller was greeted within 15 seconds is one warning and no proof, and The agent greeted a caller within 15 seconds is silent. The number 15 is in all four and in the solver in one. Turning the dial up does not help: check --strict reaches 1 error, 2 warning, 4 info and the single blocking error is structure/missing-period on the row that forgot a full stop, because strict escalates findings the checker already made and it made none about a question. The rewrite gives every row a subject, a modal and a bound, checks at 0 error, 0 warning, 0 info and scores B (83/100) with directness 99, and three of the seven rows turn out to have been observations a human was trusted to make rather than policies: hold time kept to a minimum becomes at most 30 seconds, a realistic timeframe becomes WHEN you promise a fix THEN you MUST give a calendar date, and empathy was demonstrated becomes acknowledge how a caller feels at least once in every contact.9 min GuidesHow to make your agent ask one question at a timeFour questions arrive in a single paragraph, the customer answers one, and the agent asks the other two again. The rule that causes it is a checklist wearing a rule's clothes: MUST collect the account number, the postcode, and the fault description before you troubleshoot constrains the set of facts the agent holds when it starts troubleshooting, says nothing at all about the shape of a turn, and checks at 0 error, 0 warning, 2 info, clean again under --strict, scoring C (78/100) with enforceability 85. The missing rule is a number. MUST ask at most 1 question in each reply puts the pacing decision in the same slot as every other count in the file, so a later edit widening it to at least 2 questions is 4 blocking errors, 2 logic/action-count and 2 logic/numeric, and even loosening it to at most 3 questions, which is strictly weaker and numerically consistent, is still 2 blocking logic/action-count errors, because a directive carries ONE count for one action. Then the checklist has to come apart into one guarded rule per ask, plus the NEVER rule people leave out that stops a wrap-up message ending in a question, and a $TEST pins the behaviour with matches /^[^?]*\?[^?]*$/ for exactly one question mark and matches /^[^?]*$/ for none, both deterministic and both settled offline by test --dry. A (94/100), with two honest numbers in it: enforceability drops 85 to 80 because a guarded rule really is less enforceable, and coverage reads 1/8 because splitting the checklist multiplied the rules faster than the tests. Two gaps stay open, and both are the door the bound does not cover: a compound ask carries no count, so ask for the account number and the postcode in the same reply re-enters clean, and the same widening edit written SHOULD instead of MUST compiles at 0 error, 0 warning, 0 info.7 min Best practicesWhy your agent promises the refund instead of issuing itThe transcript reads fine, your refund has been approved and will be processed within five working days, and nothing was processed, because the rule the agent read describes a refund happening and never says who makes it happen. Five pasted policy sentences with a refund tool wired up above them are 0 error, 4 warning, 3 info at D (54/100), and the grammar sorts them: prompt/agentless-passive twice where the actor is deleted outright, once as info where the actor survives in a by-phrase and a reader can still recover it, and prompt/nominalized-process where the action is packed into the noun review. The rule the checker never mentions is the emptiest one in the file. It is important to keep the customer informed throughout produces zero diagnostics, including under --strict, and isolated as a one-line file it scores C (70/100) with directness 15 and enforceability 50 against B (80/100) with directness 50 and enforceability 100 for the same instruction written MUST. It also loses the prompt/unintroduced-definite info the active version earns on the same words, because the dummy-it frame never registered an action for the reference to attach to: deleting the doer does not make a rule vague, it removes the rule from the plane where rules are checked. The loudest finding is structure/unused-import on issue_refund, the one thing in this family check --strict escalates to a blocking error and the shadow the missing doer casts, because a tool call is an action, an action needs a verb, and a verb needs a subject. The prose ships byte for byte through build. Rewritten as six MUST rules that each name who acts, including the line the policy document assumed, D (54/100) to A (90/100), carried by directness 23 to 75 and enforceability 50 to 85.8 min Best practicesBake-off: forbid the tool vs. never hand it overTwo ways to stop a support agent issuing refunds, one giving it the tool and forbidding it and one never mentioning the tool at all, and the second is the one everybody picks because a capability the agent does not have cannot be misused. Both check clean and check --strict escalates nothing, and the score separates them on exactly one facet, enforceability 90 against 80 at weight x.21, because You NEVER call @[issue_refund] names an object the compiler resolved and You NEVER issue a refund names a topic. The withholding contender cannot simply write the better rule: the pointer form with no import is a blocking structure/undefined-tool-inline and the bare prose name is a blocking structure/undeclared-tool, so you cannot forbid a tool you did not declare, and there is no third spelling. Round 2 is the edit nobody planned, a returns team adding an automatic refund for faulty handsets eight months later. Both files stop it at the gate and only one stops it in a way that survives the fix text: the declared version is 2 blocking logic/contradiction errors and a policy argument, while the withholding version is structure/undefined-tool-inline whose message says to add the import, and applying exactly that is 0 error, 0 warning, 0 info at A (97/100) shipping You NEVER issue a refund one line above You MUST call issue_refund. Round 3 dissolves the premise: build --bundle shows $IMPORT tool putting nothing on the wire with tools [], the same file with a $TOOL block carrying a full schema and HTTP binding, and both building to the identical artifact hash 20bcc1ea167f, so the file never withheld anything and which tools an agent can call was a runtime configuration all along. The shipped spec declares the tool, forbids it by pointer, and adds the rule neither contender had, because withholding a tool removes the action and leaves the sentence: You NEVER tell a customer that you have issued a refund, pinned with matches /^(?!.*issued a refund).*$/ rather than a negative contains, at A (96/100) and coverage 5/6.8 min Deep divesHalf your prompt arrives at runtimeThe prompt your model reads is your file plus whatever your platform prepends: the retrieved help article, the customer record, the last four turns, a promotion block somebody edits in a CMS. You reviewed one of those and the model reads all of them as one document, so the gate sits on the file while the defects live on the join. Three well written rules citing a section nothing declares is 3 blocking structure/dangling-section-ref errors at F (85/100) with structure 0 and every other facet untouched, because a <tag> is a place the same way @{name} is a value, and a pointer with no target is dangling however well the sentence reads. One $REQUIRE section line clears it to 0 error, 0 warning, 0 info at A (98/100) with no rule changed, and the build shows what the declaration bought: the $REQUIRE is stripped and the <kb_article> tags survive verbatim, so the artifact is a hole with a name on it. Declare a section and never cite it and structure/unused-import fires, an error under --strict; misspell the citation and both halves of the typo report. Then the controlled pair that changes how you review: the same fee-waiver contradiction is 2 blocking logic/contradiction errors at F (79/100) with consistency 0 when the promotion is pasted into the file, and 0 error, 0 warning, 0 info at A (98/100) with consistency 100 when the identical sentence arrives through <current_promotion> instead, --strict included. There is no lenient default and no advisory finding, there is no text. The fix is to move the seam for anything you own: promotion.tg plus $IMPORT file and @<promo.current_promotion> puts the contradiction back as 2 errors on a directory target, alongside an import/unmet-section-requirement warning that states the thesis in the compiler's own words, citations of it stay promises. Two wrinkles, that the program pass runs on directory targets only so a per-file CI loop is this gate with the proofs off, and that a transcluded rule reports at the line of the pointer. The decision rule is whether the text changes per conversation, and the rule that survives either seam is to keep the rules about the injected content in the file, where they are graded.8 min GuidesBuilding a cancellation-save agent, prompt-firstA five-sentence retention brief from a head of CX, compiled into a checkable spec one plane at a time. Typed straight into a file the brief is 0 error, 3 warning, 5 info at D (55/100), and the two findings that matter are typeglish/if-then on a line that pairs always with try to, and prompt/agentless-passive on the cancellation should always be honoured in the end, a sentence that names nobody who honours it. The line the checker cannot see is the expensive one: don't give away the store is a number finance already knows, written as a mood, so it becomes $REQUIRE variable plan: one of free, solo, team with one $SWITCH ON arm per plan. The type is what makes coverage provable, since a switch over an untyped input is a blocking structure/opaque-switch and a missing arm is a blocking structure/non-exhaustive-switch that names the member, so a plan added to the price list breaks the build instead of falling through. One offer becomes a bound rather than a word, where You ONLY make one retention offer is C (77/100) at enforceability 50 and You MUST make at most 1 retention offer in a conversation is B (87/100) at enforceability 100. The exit becomes a tool call, with structure/bare-tool-ref on the name in prose and structure/undefined-tool-inline on a pointer with no target, and both tools request through a $SERVICE billing whose token is @{env.BILLING_TOKEN} rather than a literal. The finished file is 0 error, 0 warning, 0 info at A (93/100) with hardness 100 and annotation 100. Then test --dry reports coverage: 5/6 rules exercised, and the uncovered rule is no_refuse, the one rule in the file that protects the customer rather than the revenue; one case whose expectation reuses the word refuse takes it to 6/6. Last, read the artifact: an unbound template build ships all three ceilings in one line of prose, and build --vars with plan bound to solo ships the single sentence You NEVER offer over 20 percent off.9 min Deep divesSoften a rule and it stops being gradedAn ablation over deontic force, which is the smallest edit anybody makes to an agent prompt: a MUST becomes a SHOULD, a NEVER becomes an AVOID, and no policy changes. On a clean eight-rule broadband support prompt at A (95/100), every single-rule downgrade returns 0 error, 0 warning, 0 info, identical under --strict, and the grade lands on 94, 95 or 96, with two rows scoring above the baseline. The last column explains the other two: hardRules falls from 8 to 7 in every variant, so a softened rule is not marked down, it leaves the population being measured, which is why enforceability can rise when a rule is weakened. Soften all eight and the endpoint is a file where nothing binds: check --strict exits 0 and score reports A (93/100) with enforceability 100 over hardRules 0, because proofGradeRate is a rate over an empty set, while hardness alone drops 100 to 60 and cannot move the grade out of the A band. The larger loss is the proof surface. MUST against NEVER, MUST NOT or DO NOT on one action is 2 error logic/contradiction; change either side to SHOULD, SHOULD NOT or AVOID and the same file is clean, and the Z3 arithmetic goes the same way, with at most 4 sentences against at least 6 sentences silent as soon as one rule is a SHOULD. AVOID is the word to ban: it keeps hardness at 100 and lifts enforceability 85 to 100 while dropping out of the deontic axis entirely, so it neither contradicts a MUST nor is subsumed by a NEVER, where SHOULD NOT at least reports hardness 75. Neither shipped gate fails the all-soft file, so the gate is a third one: read rules and hardRules off the enforceability facet in score --json and fail the build when the ratio drops.8 min Best practicesNobody who wrote this prompt still works hereNine checks for the first day with an agent prompt you did not write, each with a command behind it, and the first one says do not start by reading it. Build it, because the artifact is the text the model receives and it is not the file: with no tier bound, a deterministic $IF chain compiles to one line of English carrying both SLAs, so the live agent is told about the 4-hour promise and the 2-day promise in one breath, while the survey tool and the vip condition are absent from the artifact entirely. Two hashes answer two different questions, sourceSha256 pinning the file in git and artifactSha256 pinning what the model gets, and if the deployed prompt does not match the second one then every conclusion you draw from the file is about a document nobody is reading. Run fmt --check before check, since a hard-wrapped rule is two statements and a bound that never assembled cannot be contradicted. Then read the report as archaeology: 0 error, 5 warning, 3 info at D (53/100) with runtime 71 against hygiene 0, and five of the warnings are the same story three times, an orphan input as structure/unused-import plus clarity/unused-variable, an orphan condition as structure/unused-define plus clarity/unused-definition, and a tool nobody references as structure/unused-tool, whose message is the reason it matters, the model cannot discover a tool no rule mentions. Sort the dead surface by what it implies rather than severity, and resist deleting it on day one. Read the arms against the domain, which is the one place a stale edit becomes a blocking error: an arm for a tier the pricing team renamed is structure/impossible-case at exit 1. Ask what was ever measured, and here test --dry answers no $TEST cases with a tick and no coverage line at all, a different output shape from coverage: 0/3, so nothing in the file has ever been pinned. Then resolve --vars once per member of every typed domain for the set of prompts your agent has actually been. The handover edit is annotations first, since an @@ note is the only change that provably cannot alter behaviour, and the file lands at B (86/100) with consistency 14 to 100, structure 0 to 100 and annotation 0 to 80. The number to keep is what --strict says about the file you inherited: 6 errors on a document nobody edited, which is what a year of being fine looks like with the gate switched off.8 min Deep divesYour prompt says it. The model picks one.Every rule in an agent prompt is about a thing, and most of them do not say which thing. A four-rule returns prompt where one line says a customer may have two open orders and the next says cancel the order is 0 error, 1 warning, 1 info at C (72/100), the warning being prompt/ambiguous-reference: the order points at ONE order, but a claim proves there are 2 or more. The finding is not about the ambiguity, it is about the one sentence that happened to prove it, and three controlled files show how narrow that is: a customer may have two open orders is flagged, an account holds many orders is clean, and a customer may have two escalation paths beside follow the escalation path is clean because the head is two words. The pro-form finding is narrower still. prompt/dangling-reference catches a bare it only when nothing at all in the document can bind it, so a controlled pair differing by one persona line settles it: no-persona.tg is 1 warning and with-persona.tg is clean, because You are a returns agent for Cobalt Mobile introduces a noun and the noun satisfies it. On any prompt with a role line this finding cannot fire. And there is no gate to build, since all three reference findings are advisory and check --strict returns byte-identical output at exit 0 on the same file, so the score is the only instrument that moves, where an ambiguous reference is a full point on the ledger and an unintroduced noun is a quarter. The rewrite changes no policy and only how each rule refers to its object: the order becomes variable order_id: string with every rule written over @{order_id}, the count claim moves into an @@ annotation where it documents the decision and costs zero runtime bytes, and the missing rule gets written down as NEVER cancel an order that @[cancel_order] did not return, so the object has a provenance and not just a name. C (72/100) to A (97/100) at runtime 96 and hygiene 100, and the artifact carries {order_id} as a host-filled hole appearing in both rules that act on that order, which makes a mismatch a diff rather than an interpretation.7 min Best practicesRewrite: the paragraph that lives in three promptsThe second bot is where it starts: somebody opens the billing prompt, copies the top of it, and changes the role line. Three agents later the same house paragraph lives in three files, all three check at 0 error, 0 warning, 0 info and score B (84/100) each, and the line under the tick says what that is worth, program: 3 independent files, no $IMPORT compositions, nothing co-checked. Copies do not stay copies, so two reasonable edits two quarters apart leave one file at at most 6 sentences and another with a without a manager code clause, and the directory still reports 0 error, 0 warning, 0 info, because no diagnostic compares two documents that never reference each other. The rewrite lifts the paragraph into prompts/house.tg and has each agent declare $IMPORT file house.tg as house and write @<house.Constraints> where it used to sit. The refactor is provably free: all three artifacts rebuild at the same short hashes, 025d3cd2ab03, f0af4de34a40 and 353add72e3ef, and every score stays at B (84/100), because the score reads the file the model reads and that file did not change. What changes is the program, since check now reports program: 3 roots, and one line added to house.tg is 2 blocking logic/contradiction errors reported once per participating rule with each naming the other file and line, while billing and faults stay clean so the output doubles as a blast radius. The attribution has a wrinkle worth a minute, since the report on house.tg names retention.tg line 9, which is the @<house.Constraints> pointer rather than the rule. Copy-paste would have caught that same edit if you pasted into all three, which is the whole argument: paste into two of three and the directory is back to 0 error, 0 warning, 0 info with the policy simply absent from the file that needed it. Two limits ship with it. The cross-file pass runs on directory targets only, so typeglish check prompts is 2 errors and exit 1 while typeglish check prompts/retention.tg on the same tree is 0 error and exit 0, which makes a per-file CI loop a gate with the proofs switched off. And not every proof composes: at most 4 sentences in the house file beside at least 6 sentences in the root is clean across files and 2 blocking logic/numeric errors in one.8 min TeardownsTeardown: your escalation matrix is one long lineFourteen lines out of the ops wiki, six of them a routing table, and the checker has sixteen things to say without a single error among them. A markdown escalation matrix pasted under a heading is 0 error, 16 warning, 4 info at C (65/100) with structure 0: structure/missing-period once per row, because a table row is a line with no full stop and a statement boundary is a compile contract, and structure/phantom-column once per space run, whose message names the fix outright, a space run doing alignment's job, layout tables in a fence or a <name> zone. Then the build, which is the whole teardown, since the alignment padding survives and the newlines do not, so six rows of reviewed routing policy reach the model as one line of pipes at 731b5d87d0cc, and typeglish fmt --check answers 1 file already formatted because nothing is malformed, it is just not a table any more. consistency 100 is the second finding, because the acknowledge within 4 hours rule and the 15 minutes row are two clocks on one event and a table row is not a claim. The proof is a controlled pair: reroute service outages to a queue called nowhere in 1 year, keep the column widths identical, and both check and score return byte-identical output. A fence buys the rows back for six characters, 16 warnings to 0 and C (65/100) to C (77/100) with structure 0 to 100, and buys nothing else, since the nonsense row stays invisible and two rows carrying the same trigger with different queues is 0 error, 0 warning, 1 info. So the fix is a modelling decision rather than a formatting one. The Trigger column is a closed set that arrives with the contact: $REQUIRE variable contact_reason: one of bills, faults, legal, then one $SWITCH arm per member, and the identical duplicate trigger is 2 blocking structure/duplicate-case errors saying only the first can run, while a fourth code added to the domain later is a blocking structure/non-exhaustive-switch naming the uncovered member. The rebuild is A (90/100) at 0 error, 0 warning, 0 info with enforceability 55 as the honest lever, member keys spelled bills and faults because billing and outage each draw a prompt/unregistered-doer, and a build with the reason code bound resolves the whole matrix to the one sentence that applies.8 min GuidesHow to get a structured wrap-up out of a support agentReporting cannot join on a free-text summary, so every contact needs a disposition code, and the cheapest place to get one is the last line the agent writes. The version everybody writes first pastes the JSON shape in as an ordinary line and is 0 error, 1 warning, 1 info at C (68/100), the warning being structure/missing-period on the shape itself, which is the checker reporting that your schema is sitting on the prose plane. Build it and the consequence is byte-visible: the line reaches the model as {disposition: resolved, summary: one sentence} at 2ae39fdd4663, every quote mark stripped, because a straight double quote binds a string literal and the sigils of a literal strip once they have done their job. The check was green, the build succeeded, the hash is recorded, and no exit code anywhere corresponds to the JSON you pasted is no longer JSON, so the only way to see it is to read what you shipped. The fix is a triple-backtick fence, one line above the shape and one below: the same file is 0 error, 0 warning, 0 info at B (80/100) with structure 25 to 100 and style 81 to 100, since the stray unparseable sentence stopped being a statement rather than being silenced, and it builds to 0c634c71e1de with the quotes intact. The other verbatim zone costs something in a markdown-structured file, because a <"wrapup"> tag makes XML the structure model for the whole file and both # headings then draw structure/heading-outside-section, 2 warnings for one tag. Then the closed set, which the fence says nothing about: Dispositions ARE one of resolved, escalated, callback, abandoned is a declaration, so it renders into the prompt and binds the name for the checker in one statement, and the honest limit is that it does not police prose, since a rule writing goodwill as the disposition is 0 error, 0 warning, 0 info. The shipped spec pins the format with deterministic matches /.../ asserts that validate fully offline at A (90/100) and coverage 1/3, with enforceability 68 dragged by write nothing after it, a real instruction with no bound to give it, and the defence for that one is the assert rather than an adjective.7 min Best practicesYou said only. Your agent escalated anyway.Every contact-centre prompt has an only in it somewhere and it reads like a closed door, which makes it the least enforced word in the file. A support prompt whose first rule is Only escalate to the retention team, with a complaints rule and a billing rule added months apart by different people, is 2 blocking logic/exclusivity errors at F (66/100), and the message names exactly what it proved, this relation was closed to retention team but billing team is added. Only one of the two breaches is reported: the complaints rule breaks the only just as squarely and is invisible. The facets say why the grade fell, since hardness 100 and enforceability 77 mean the rules are written strongly and consistency 0 carries the whole cap, consistency being the only facet that asks what the rules do to each other. Then change one word. Promote the billing rule from a bare imperative to MUST, the way somebody tightening a prompt would, and the identical policy breach is 0 error, 0 warning, 0 info at B (83/100), builds to b49ed1410b19, and ships both lines to the model one under the other with no ordering between them. Six controlled variants in one run put the boundary beyond doubt: every error came from the file written as two bare imperatives, while a modal on either line (MUST only, MUST, ALWAYS) or a WHEN guard on either line is clean, so the rule written more forcefully is the one that escapes the proof, and in a real prompt written almost entirely in modals and guards on purpose, only is decoration. The rewrite drops only for the prohibitions it implies, one per excluded target, and refuses a tempting false green of its own: NEVER escalate to the complaints team beside a guarded MUST would compile clean, so the ombudsman exception is written as what it actually was, not an escalation but a case opened through @[open_complaint_case]. A (95/100) at enforceability 88 with coverage 3/5. The payoff is the edit that has not happened yet: append MUST escalate to the billing team eight months later and the prohibition version is 2 blocking logic/contradiction errors and no build, where the only version absorbed exactly that edit in silence. The trap is in the fix text, since scoping the new rule clears both errors immediately and is sometimes how you get back to holding a green check over a policy nobody agrees on.8 min Best practicesBake-off: a catch-all arm vs. one arm per memberTwo contact routers over the same typed reason code, one covering three members with a - otherwise:: catch-all and one naming all four, are indistinguishable to every gate you own: 0 error, 0 warning, 0 info and A (93/100) each, with directness 99 against 100 the only number that moves at all. So the choice cannot be settled on the file you have, only on the file you will have. Then a fifth reason code is added to the domain by somebody who was working on the IVR and never opened the router. The enumerated router is a blocking structure/non-exhaustive-switch naming the uncovered member; the catch-all router is clean, builds and ships, and build --vars with reason bound to cancelling reads out what it ships: You MUST route to general against You MUST route to retention, same input, same green check, two different queues, decided by a row somebody wrote as insurance eight months earlier. Wanting both belts is refused, and the wording of the refusal is the whole post: full coverage plus a fallback is a blocking structure/unreachable-otherwise saying the row can never run and it would silently absorb members added to the domain later, which is why it is an error rather than a dead-code warning. So the fallback is decided by the input rather than by taste, in three enforced states: a typed input with every member covered means otherwise is FORBIDDEN, a typed input with members uncovered means it is LEGAL and arms plus fallback are what makes the switch total, and an untyped input means it is REQUIRED, because a $SWITCH over an open domain is a blocking structure/opaque-switch until you add one. A $IF chain inverts it again, since a chain over open predicates cannot be proven exhaustive and leaving $ELSE off is a blocking structure/non-exhaustive-switch handing back a counterexample, no arm handles @{wait_minutes}=0.0. One wrinkle from building the files: arm keys are read as English words, so billing, outage, payments, complaints and renewals each draw a prompt/unregistered-doer info on their own arm row while bills, faults, moves, upgrades, refunds and returns are clean.8 min Deep divesYour prompt is a graph, not a listA prompt defect does not have to sit on a line or inside a pair of lines. Three rules in three sections of a contact-triage prompt (All chargebacks are disputes, All disputes are escalations, No chargeback is an escalation) are 3 blocking logic/syllogism errors at F (72/100), reported once per participating rule with each report naming the other two, and the message names an overlap between chargeback and escalation that appears together in no sentence of the file. Every pair of the three checks at 0 error, 0 warning, 0 info, so a pairwise review cannot find it: the defect is the transitive closure rather than a line, which is why the usual advice to hunt for two rules that fight sends you looking for the wrong shape. Lengthen the chain to four rules across three sections and it is 4 errors, one per participating rule, while leave-one-out over all four variants is 0 error, 0 warning, 0 info, the textbook definition of a minimal unsatisfiable set. Every facet that measures how the rules are written is perfect (hardness 100, annotation 100, structure 100) and consistency 0 carries the whole grade, because consistency is the only facet that is a property of the set rather than of any member. The reporting has a wrinkle worth knowing before you lose ten minutes to it: range.line is the true file line (7, 11, 15) while the with lines 4, 6, 8 list inside the message counts positions in the file with blank lines and @@ annotations removed, and one score report prints both schemes, L4/L6/L8 above the facets and L7/L11/L15 in the ledger rows. Then the fix is a policy question the compiler refuses to answer: all three relaxations clear the errors and they do not cost the same, B (89/100) at hardness 100 for naming the categories apart against B (87/100) at hardness 83 for most disputes and B (85/100) at hardness 70 for some disputes, so the cheapest edit is a hedge and the score is the only instrument that prices it. The shipped rewrite drops the taxonomy for destinations, since categories chain and destinations do not: A (95/100) with no policy changed.8 min Deep divesDelete every test. The score does not move.Leave-one-out ablation pointed at the $TEST suite instead of the rules, and the result is a flat line. A grocery-delivery support prompt with seven annotated rules and six cases checks at 0 error, 0 warning, 0 info, scores A (95/100), and reports coverage: 7/7 rules exercised. Delete any one case and check is still 0 error, 0 warning, 0 info and the score is still A (95/100); delete all six and it is still A (95/100) with identical facets, because a test is not referenced by anything and analysed by nothing, the one region of a .tg file with no inbound edges. check --strict escalated nothing on all eight variants. The only column that moves is coverage, from 7/7 to 6/7 on five deletions and to 5/7 on lookup_first, which was the load-bearing case covering two rules at once. And that number is lexical: - contains "refund" attributes to nothing at 6/7 while - contains "a refund for that item" attributes at 7/7, and does not contain "refund" is not a deterministic assert at all, falling through to the judge at rubric 0.00 under --dry. The denominator is not your rule count either: every matches /…/ expectation adds one to it, taking a seven-rule file to 7/8 and a suite of five junk regexes to 0/12, while check stays clean and the built artifact stays byte-identical. So the post ends with the gate you have to build yourself: test --dry --json, fail when covered is less than total and when the coverage key is absent, which is what a file with no cases returns, and pin the denominator too.8 min GuidesBuilding an identity-verification gate, prompt-firstAn impostor and the account holder reach the agent through the same pipe and produce the same evidence, typed characters, so verification is the one thing in a contact-center prompt that cannot be a judgment the model makes. The five-sentence brief typed straight into a file is D (60/100) at 0 error, 3 warning, 3 info, and the finding that names the whole problem is prompt/agentless-passive on never share sensitive information with someone who is not verified: the doer is deleted, the sentence describes a state and names nobody who put the caller in it. The build turns the gate into an input, $REQUIRE variable identity: one of unverified, verified, adds the rule people skip (You NEVER treat an answer a caller gives as verification), and puts one $SWITCH ON @{identity} arm per state, so adding a third member later is a blocking structure/non-exhaustive-switch and an F (83/100) rather than a caller the prompt has no policy for. Then the two halves of an account get written down as %Open facts% and %Gated facts% list declarations, with a rule against confirming or denying any part of the gated set, because confirming an account exists is the first thing an impostor needs. The honest limit is §5: unguarded, NEVER disclose a balance beside MUST disclose a balance is two blocking logic/contradiction errors, and the identical pair with a WHEN on each compiles at 0 error, 0 warning, 0 info while still firing for an unverified caller, because a guard silences the prover whether or not the guard is right. The shipped spec is A (90/100), coverage 1/4, and its tests pin the refusal rather than the leak, since does not contain is not a deterministic assert and reports rubric 0.00 under --dry.9 min Best practicesSomebody wants the model bill smallerNine checks before an agent prompt moves to a cheaper model, each with a command behind it, and the first one reframes the job: build the same file twice with only the model id changed and both artifacts come back as 237e0fbf0c32, byte-identical, because $CONFIG is scaffolding and never reaches the model. You are not migrating a prompt, you are handing an unchanged document to a reader with less slack. Put the id in the file rather than a dashboard, where it is checked: a via naming no service is a blocking config/dangling-service and a misspelled kind is a blocking config/unknown-kind saying the line was stripped and configured nothing, while a misspelled param is only config/unknown-param at info, so - maxtokens:: 400 sets no cap and check still exits 0. Then take the delegation inventory, which is the advisory list everybody scrolls past: a live support prompt at 0 error, 3 warning, 2 info is D (57/100) with hardness 66, one SHOULD probably dragging four bare MUSTs down, plus prompt/unmeasurable on professional and prompt/vague on appropriately, three decisions the old model made for you. check --strict turns two of those warnings into errors and the exit code from 0 to 1 on a file nobody edited. Take back what the compiler can decide: the template build leaves both tier arms in the artifact as conditional prose at 6c663e579a51, and build --vars resolves the switch to one line at 397966ae7b4e, so the model is not choosing correctly, it is not choosing. Then name the judgment calls that are left, which took the file to B (88/100), hardness 66 to 100 and annotation 0 to 83, with the orphan tier variable given a job instead of a deletion. Ship it on deterministic asserts rather than a judge, since a judge is a model call and the model is what you are changing: test --dry validates offline at coverage 2/4, and test --model claude-haiku-4-5 needs ANTHROPIC_API_KEY and says so when it is missing. Pin the ratchet at the number you shipped, because --min B passes at 88 and would still pass at 80 while --min 90 exits 1, and keep the rollback, which is an artifact hash in the build manifest rather than a redeploy you have never rehearsed.8 min Deep divesYour agent is certain because you said soA system prompt carries two kinds of sentence and only one of them is a rule. You definitely have access to the order history is not an instruction, it is an assertion about the world, and an assertion in the context window is a premise the model reasons from rather than a claim it can evaluate. TypeGlish reads that second kind on its own plane: a mobile-network prompt whose capability line and hallucination guardrail were added two sprints apart is 1 blocking logic/epistemic-conflict at F (65/100), reported once on the later stance at the column of the word cannot, with enforceability 70 and hardness 100 and directness 100 all fine because the rules were never the problem and consistency 0 carrying the whole failure. The axis is narrow and that is the useful part: definitely, certainly, undoubtedly, probably, possibly and might all conflict with cannot, while surely, clearly and obviously are read as emphasis and pass, maybe and perhaps come back as prompt/hedging instead, and may is clean because permission is not possibility, one letter from might which is a blocking error. The negative corner is exactly one word, so can not as two words, can never and could not are all 0 error, 0 warning, 0 info on the identical pair. Then the dangerous direction: an error asks you to keep one stance, and deleting the limit rather than the claim gives B (80/100) with the capability assertion sitting in the artifact verbatim one line above NEVER invent a delivery date. Remove the certainty words entirely and nothing objects at all, since a declared %standard delivery window% IS 3 working days beside MUST call @[get_order] before you state a delivery date is 0 error, 0 warning, 0 info at C (78/100), because nothing reconciles a stated fact with a rule about stating facts and both lines reach the model. The rewrite never tells the agent what it knows: the capability becomes $IMPORT tool plus a rule the prover can hold, the knowledge limit becomes $REQUIRE variable order_id: one of present, missing, and the refusal gets an arm of its own in a $SWITCH with a $TEST behind it. F (65/100) to A (91/100) with no policy changed.7 min Best practicesRewrite: your bad example is a good exampleThe rules in a prompt get argued over and the examples section gets inherited, because it reads like documentation. A mobile-network agent with three signed-off constraints and a prose # Examples block checks at 0 error, 0 warning, 2 info at B (83/100), both findings cosmetic and neither about an example, since a prose block is exempt from rule analysis by design. Build it and two things happened on the way through: every quote mark stripped, because a literal's quotes are a compiler instruction rather than content, and the three lines reflowed into one paragraph. So the artifact contains The Unlimited plan is 30 pounds a month as flat prose inside a prompt whose first rule is NEVER state a specific price. The asymmetry is the mechanism: Never say X is one clause of instruction and X is a fully formed, fluent, in-domain support reply sitting where the prompt says examples live. $EXAMPLE splits the block by audience, emitting - good:: as a User and Assistant pair while - bad:: is held out of the compiled prompt entirely, and a field outside the closed set of three is a blocking structure/bad-example error. One honest limit: nobody reconciles an example against a rule, and a five-sentence good response under at most 3 sentences is 0 error, 0 warning, 0 info, which is why every example kept should carry a $TEST assert. Same content sorted by audience: A (92/100), and both banned strings absent from the artifact.7 min TeardownsTeardown: Hi {first_name}, thanks for contacting usNine lines out of a flow builder, five of them carrying a placeholder, and the checker's two findings are both on line 7, the only constraint with no placeholder in it. A bare {first_name} is not a placeholder, it is a word with a brace either side, and the convention that braces mark a substitution point is an agreement between you and your templating layer that nothing else in the pipeline attended. Add one character per hole and the same nine lines are 5 blocking structure/undefined-ref errors, asking the question the export never answered: runtime variable, or fixed value. Build the export and the braces survive verbatim with the greeting's quotes stripped, so the model is told to open with the characters Hi {first_name}. Either side of a declaration sit two more failures: the orphan variable a panel accumulates, blocking under --strict, and a policy number inside a hole, where at most {refund_cap} beside at least 100 dollars is 0 error, 0 warning, 0 info and the identical file with 50 dollars written out is 2 blocking logic/numeric errors. The coverage is free: declare all five, change no word, and both files build to the same hash. typeglish import lifts four of the five, skipping the one inside a quoted literal. C (75/100) to A (91/100).8 min Best practicesWhy your agent follows half a ruleA newline inside a rule is a statement boundary, not whitespace, so a hard-wrapped rule is a truncated instruction followed by a fragment and both reach the model. A billing prompt reflowed to 80 columns is four rules to a human and seven statements to the compiler: three warnings per wrap, structure/missing-period then structure/bad-indent then structure/wrapped-fragment, summary 0 error, 9 warning, exit 0. Build it and the wrap survives with the continuation stripped of even its indentation. The correctness cost is the third rule: at most 3 sentences wrapped away from its own bound sits beside MUST write at least 5 sentences and the scorecard says consistency 100, because a bound that never assembled is not there to prove. typeglish fmt makes 3 joins, no word changed, and the file comes back with 2 blocking logic/numeric errors. Hence the ordering: fmt --check is the CI gate and it belongs BEFORE check. C (72/100) to A (93/100).7 min GuidesHow to write a procedure your agent follows in orderHalf of contact-centre work is a sequence, and a paragraph gives you nothing to point at. Number the steps and TypeGlish binds a step scope: the ordinals are addresses, so NEVER book an engineer visit before step 2 is a defended reference. A step takes a modal, because a bare imperative lowers weakly, and a list is a sequence rather than a constraint. Four blocking errors guard it: deleting step 2 without renumbering gives structure/missing-step on the list and structure/dangling-step-ref on the rule that outlived it, a pasted duplicate number is structure/duplicate-step, and a bare step 2 across two procedures is structure/ambiguous-step-ref, fixed by qualifying it or by anchoring the step. The anchor is source-only: step #linetest builds as step 1, and after inserting a step above it the same source line builds as step 2. A (93/100), coverage 2/7, six of the seven rules being steps.8 min Deep divesYour agent has never heard of a wallboardA prompt review reads the rules and skips the nouns, which is backwards, because the nouns are the part your company invented. A system prompt carries two glossaries in different syntax, and only one of them ships. $DEFINE word wallboard with - class:: noun and - unique:: true is the compiler glossary: it declares that the lemma names exactly one thing, and it is stripped at build. A declaration is the model glossary and it renders, so The %disposition code% IS the outcome label you attach to a closed contact binds the name and defines the term in one statement. Three jargon terms with no vocabulary work is A (92/100) with 2 prompt/unintroduced-definite findings; the same rules with both glossaries used deliberately is A (96/100), enforceability unmoved, the whole gain in directness 67 to 83. Build it and the split is byte-visible: both declarations reach the artifact, and wallboard appears once, in a rule, with no definition anywhere. Hence the sharp edge: $DEFINE word silences the finding without answering the question.7 min Best practicesBake-off: if unsure vs. if a fact is missingTwo support prompts try to stop an agent cancelling the wrong order and disagree about one thing only: what the rule is allowed to look at. Contender A conditions on the model, IF you are unsure which order the customer means THEN ask a clarifying question. Contender B conditions on the transcript, You MUST ask for an order id before you call the cancel tool. Round 1 is a tie, because the checker reads the action a rule commands and largely leaves the guard alone: both are 0 error, 0 warning, 0 info and check --strict escalates nothing. The score separates them, B (88/100) against A (93/100), the entire spread on the runtime plane at enforceability 53 against 68. Round 2 settles it: how many test cases cover this trigger? B answers two, because a message either contains an order id or it does not. A cannot answer, because unsure is not a set, and offline it produces no verdict at all. Round 3 lifts the trigger onto the compiler plane as a typed input plus a $SWITCH ON block, where the losing arm is absent rather than outweighed.8 min Deep divesYour headings decide which rules get checkedA section heading is not a label, it is a declaration of what kind of text follows, and deleting one moves the analysis. A support prompt clean at B (88/100), copied five times with one structural marker missing from each and no rule text touched. Deleting # Role adds 3 warnings at C (75/100), because the persona line loses its exemption and becomes prompt/unmeasurable. Deleting # Constraints runs it backwards and is the dangerous direction: B (83/100) with a warning becomes A (91/100) with none, because the rules fall into the # Role block where soft language is allowed. Eight facets become six, hardness and directness gone rather than lowered, and the built artifacts differ by exactly one line. The exemption covers rule-quality advisories only: two rules under # Role that contradict each other are still 2 blocking errors and exit 1.7 min GuidesBuilding a ticket-triage agent, prompt-firstA router has no behavior, it has a return type, and the whole build is the discipline of writing that type down first. The one-line brief typed straight into a file is D (61/100), and none of its six findings is the real defect: the file never names the set of teams it routes to. Queue ARE one of billing, technical, sales, retention, unknown is load-bearing rather than a comment, because two lines asserting different members of one declared set are 2 blocking logic/redeclaration errors. Three rules make it a router: bound the output to one word, remove the permission to answer, name the fallback. The set appears again as a typed one of row on $TOOL assign_queue, the enum the runtime validates and the model never reads. And enum members are English words: one of email, chat, voicemail draws prompt/unregistered-doer on the voicemail arm, where the identical file with phone is clean. Shipped at A (91/100) with two contains asserts that test --dry validates offline.8 min ReleasesTypeGlish 0.7.2: the checker takes your word for itA patch that adds one command and then takes findings away. $DEFINE word wallboard with - class:: noun and - unique:: true stops prompt/unintroduced-definite firing on the wallboard, while an undeclared the result in the same file is still flagged; the params are a closed schema of two and five new blocking codes make a malformed row an error instead of a silent no-op. THE RETRACTION LAW lets the cross-file pass withdraw a warning the composition disproves, 1 to 0, with a receipt line instead of a silent gap. The importer repairs #Instructions to # Instructions: 6 errors to 0 on the same markdown. Two false positives retire, and setupTypeGlish gains chrome so the editor surface belongs to the host.8 min ReleasesTypeGlish 0.7.1: the embedded editor bootsA patch with one changelog entry. 0.7.0 published dist/lsp-worker.js so typeglish/monaco would have its language server in the box, but line 1 was still import nspell from 'nspell', and a browser resolving a module worker's imports has no node_modules to answer that from. The worker never loaded. Squiggles kept working because applyDiagnostics compiles on the main thread, while hover, completion, semantic tokens, and six other requests route through the worker and were dead. 0.7.1 bundles it in for 29,166 bytes and the worker boots. typeglish check --json is byte-identical to 0.7.0.4 min ReleasesTypeGlish 0.7.0: your prompts are one programtypeglish check prompts/ now composes files joined by $IMPORT before the proofs run. A shared policy file that says NEVER waive a restocking fee and the agent that transcludes it and says MUST waive one are each clean alone and 2 blocking logic/contradiction errors together, one report per file on the true line of its own rule. Scoped to files that can collide, transitive, and --no-program restores the old behavior. Plus $REQUIRE section, import --fix-citations, the new typeglish fmt, and 99 documented codes instead of 48.8 min Best practicesYou are about to add one more ruleEight checks before a new rule lands in a live prompt, for the steady state rather than the launch. The naive one-liner beats every gate: 0/0/0 and A (93/100) in a file that says NEVER promise a delivery date, because a delivery date within 24 hours is a different slot. Drop the qualifier and it is 2 blocking errors. Then check --strict (exit 0 against exit 1), score --min 95 not --min B, logic/force-subsumption, coverage 0/3 to 2/4, and an artifact diff of exactly two lines.9 min Deep divesTwo rules, no words in commonA rule is keyed to the action it names, not the words you typed, so MUST display the internal reason code and MUST withhold it are one slot at opposite poles: 2 blocking logic/contradiction errors, F (71/100), enforceability 100 beside consistency 0, and nothing to grep for. Two NEVERs collide the same way. But the fold is keyed, not blanket: 17 of 153 pairs conflict across three merged keys, so redact beside display scores B (87/100) with consistency 100.7 min TeardownsTeardown: the policy doc somebody pasted into your agentA policy is written about a process; a prompt is written to an agent. Paste one into the other and you get 0 errors and 7 info findings: one prompt/implied-doer, six prompt/unintroduced-definite. The definite article is a lookup, so the model resolves “the returns window” from its own priors. A prose block does not hide it and --strict does not raise it. Introduce the nouns and C (71/100) becomes A (95/100).7 min Best practicesRewrite: two clocks on the same promiseWithin 2 hours and within 30 minutes on one rule is not belt and braces, it is four blocking logic/time-strength errors calling the looser bound dead weight. “Immediately” is the zero interval, so qualifying it loosens it. Enforceability 100 next to consistency 0, F (70/100). Name the two events instead of guarding them: A (99/100).6 min Best practicesWhy your agent will read out your API keyA key in the prompt is a key you published: the prompt is context the model can quote, and it travels into git, traces and screenshots. security/leaked-secret fires twice on one line, the shape warns and the vendor format blocks, and the grade is capped at F (73/100) with security 0. Put the name in a $SERVICE header and the value in the environment: A (90/100), and the artifact holds no key at all.7 min GuidesHow to connect an agent tool to a real API$IMPORT tool declares a tool the host already owns; $TOOL defines it here with typed inputs and a - request:: binding, and a $SERVICE holds the base URL and auth header once. Two tools, one service, A (93/100). build --bundle shows the split: the model reads a bare tool name, the runtime gets the schema and the resolved URL.8 min Best practicesBake-off: deterministic asserts vs. an LLM judge for agent testsTwo $TEST suites for one prompt, same case, same intent: one as deterministic asserts, one as prose a judge grades. check and score cannot separate them, both land at A (100/100). test --dry --json can: an assert lands in asserts, judged prose collapses into rubric, so only one suite can fail without a model. Ship both instruments in every case.7 min Deep divesYour prompt has a second readerThe model only ever reads the compiled artifact; the next engineer reads the file. Two prompts that differ only in their @@ notes grade A (95/100) and B (83/100) with identical runtime facets, and build to byte-identical artifacts with the same hash. Half your file is addressed to a reader the model never sees.7 min ReleasesTypeGlish 0.6.0: paste the prompt you already haveThe migration release. typeglish import converts the prompt you already run into checked .tg without changing a word, lifts {placeholders} to typed variables, and prints a worklist by plane. The score card now separates runtime 79 (what the model reads) from hygiene 20 (source only), and the Hardness Doctrine scores how binding your language is.8 min GuidesBuilding a multilingual support agent, prompt-firstThree languages, one policy set, English-only disclaimers. Declare the locale as a typed input instead of asking the model to detect it, and put only the rules that really vary in a $SWITCH: D (62/100) to A (90/100), one artifact per language, and adding Italian is a compile error rather than a silent gap.8 min Deep divesDelete a line. See who notices.Eleven lines deleted from an airline prompt, one at a time. Four refused to compile, one warned, six went through in silence, including the line that says what the agent is. A deletion is visible only when the deleted line participated in a reference.7 min Best practicesYour agent said the wrong thing. Now what?Triage in cost order, with a command behind every step. Reproduce the transcript as a $TEST, then run check --strict, not check: a plain check reports 0 errors while strict turns the same file into 2, and both are the bug. Then look for a seam, read the warnings you have ignored, and hash the live prompt against the build manifest. Edit last.8 min Deep divesHalf your rules are about somebody elseRules are indexed by who they are about, so a prompt that drifts between You MUST and the agent MUST has written two rule sets nothing compares. It scores A (95/100) with consistency 100 holding two flat contradictions; rewrite two lines in the second person and the same file emits 6 errors.7 min TeardownsTeardown: the system prompt an AI wrote for youA returns-agent prompt in the house style of a generating model: an em-dash splice, an emoji, decorative bullets, a math symbol. Twelve errors in eight lines and consistency still reads 100, because the marks stop the checker reaching the logic. Sweep them and the real conflict proves out with logic/numeric.7 min Best practicesRewrite: a brand voice that cancels itself outAn adjective is a claim on an axis, so "our tone is professional" and "our tone is casual" is a proven contradiction, and "punchy" is a claim the checker cannot see at all. Name the midpoint you meant, make the rest measurable: F (48/100) to A (92/100).7 min GuidesHow to turn parts of your system prompt on and offOne agent serves many contexts and some rules only apply in some of them. Guard a heading with when= and it is inert (structure/guard-on-heading); put the guard on a tag section instead, then prove it with build: the voice-only block is present for voice and simply absent for chat.6 min Best practicesWhy your agent approves refunds it shouldn'tAn agent that refunds above its limit usually has the cap written twice, at two amounts, so the model picks one per conversation. Declared as a money fact, the two caps can't both hold and logic/measure blocks the build. State the cap once and pin the escalation with a $TEST.6 min ReleasesTypeGlish 0.5.0: the language server shipsThe engine leaves the playground. Run typeglish lsp and any LSP editor gets the one engine - diagnostics, semantic tokens, hover, completion, and rename, the same checks CI runs. The paint travels with it: colour now teaches the English class, and the world model stops minting objects out of $-commands.4 min Deep divesYour system prompt is an API contractA system prompt declares an interface with its runtime: the inputs it requires, the tools it is promised, the shape it returns. A reference to something never declared is a dangling pointer the compiler blocks, so a renamed field is a red build, not an empty greeting.6 min Best practicesBake-off: typed inputs vs. free-text for tier-gated rulesTwo prompts branch on the customer's tier; one types the input as a closed set, the other leaves it a string. They tie on score, until a value is misspelled: only the typed input turns the typo into a failed build with structure/impossible-case.6 min GuidesHow to keep your AI support agent on topicAgents answer off-topic questions because a prompt with no scope has an open default. The fix is an allowlist, not a longer blocklist: name what the agent covers with ONLY, give the off-topic case a real WHEN/THEN action, and pin a refusal with a $TEST.7 min GuidesBuilding a refund agent, prompt-firstFrom a one-line brief to a spec the checker passes. A refund agent is authority: route by a typed refund reason so a forgotten case is a red build, draw the auto-approve versus escalate line explicitly, and pin the escalation with a $TEST.7 min ReleasesTypeGlish 0.3.0: the function-word floorThe closed-class words now paint: articles, prepositions, the quantifiers every and each, and the bare copula. 'Every refund is approved' and 'No refund is approved' provably conflict with logic/quantifier, while an adverb or preposition that splits two rules apart keeps a false conflict from firing.5 min Best practicesWhy your agent gets the time zone wrongBusiness-hours and SLA rules written as bare clock times never say whose clock, so the model guesses a zone at runtime and quotes the wrong hours to customers elsewhere. The checker flags it with prompt/naive-time and prompt/mixed-timezone; the fix makes the zone a typed variable.6 min Deep divesSay it once, or say it's optionalRestating a strong rule in softer words (ALWAYS, then usually) reads to the model as permission to skip it, because always already entails usually. TypeGlish proves the strength conflict with logic/quantifier-strength and blocks the build.6 min ReleasesTypeGlish 0.2.3: possessives that chainThe possessive now nests: write the owner's owner's field in plain English and point at it with a dotted @{a.b.c}. Multi-word names bind as one entity that can own a property, backed by one generated catalog of nouns and prepositions.5 min TeardownsTeardown: a voice agent system prompt, line by lineA phone-line support bot written like a chat prompt: emoji the text-to-speech engine speaks aloud, two length rules that can't both hold on one turn, and a hedged escalation. Every defect is a real compiler error.7 min Best practicesThe pre-launch checklist for a contact-center agentSeven gates to clear before a support agent goes live, each tied to a command: compiles clean, clears a score floor, every tool reachable, one scoped escalation path, sad paths tested, nothing vague, and the artifact pinned in CI.8 min Deep divesYour system prompt has a compile timeA prompt has two planes: the runtime prose the model reads and the compile-time scaffolding it never sees. The checker catches the line that leaks across, and build shows the exact text the model receives.6 min Deep divesThe arithmetic in your promptEvery number in a prompt is a constraint a solver can check. When two limits can't both hold, the model quietly satisfies one per reply. TypeGlish proves the conflict with logic/numeric before it ships.6 min Deep divesWe pulled a good prompt apart, one rule at a timeLeave-one-out ablation on a support prompt that scores an A: two deletions raised the grade and pulling the data-breach guard changed nothing. The score measures how checkable a rule is, not how much it matters.7 min GuidesHow to write escalation rules for an AI support agentMake the handoff a tool call, scope every trigger so two rules can't contradict, tier severity with a $IF chain, and pin the handoff with a $TEST the checker enforces.7 min Best practicesRewrite: a bloated support prompt, cut to rules that checkA support prompt that reads perfectly can still score a D. Rewritten as measurable MUST/NEVER rules, the same prompt goes from D (54) to B (89) - shorter and stricter at once.7 min Best practicesWhy your agent won't use the tool you gave itAn agent ignores a tool when no rule tells it when to call one. The checker flags it as structure/unused-tool, and the fix is one WHEN rule with an @[tool] pointer.6 min ReleasesTypeGlish 0.2.2: 'the' names an object, and the docs go liveThe definite article "the" becomes an object declaration - name a thing in plain English and it is a real, referenceable symbol - and the full docs go live at typeglish.dev/docs.5 min GuidesBuilding an appointment-booking agent, prompt-firstA booking agent is mostly rules about when not to book. We take one from a vague brief to a spec the checker passes, letting the compiler catch the two mistakes almost everyone makes.8 min Best practicesBake-off: prose conditionals vs $SWITCH for multichannel agentsTwo prompts, same per-channel behavior for voice, chat, and email. Both compile, until someone adds SMS and only one of them refuses to build.6 min ReleasesTypeGlish 0.2.1: the playground ships to productionA plumbing patch: the browser playground gets its production deploy config (sub-path base, ES-module workers, Vercel), and the README gains the npm, CI, and license badges that tie the repo to the package. No language changes since 0.2.0.3 min ReleasesTypeGlish 0.1.0: the first releaseWhat shipped in the first release: the Z3-backed checker, $TEST evals, typed $CONFIG objects, $IMPORT modules, the $IF/$SWITCH conditional layer, the TG score, the CLI, and the MCP server.7 min Best practicesSay what you mean: eight prompting rules that survive productionEight prompting rules that hold up under real traffic: measurable constraints, no hedged modals, one instruction per rule, and testing what actually matters.9 min TeardownsTeardown: a support agent’s system prompt, line by lineA line-by-line teardown of a real production support-agent system prompt: where it drifts, the contradictions it hides, and how $TEST pins the fixes in place.7 min Deep divesFew-shot examples are a type system you haven’t written downEvery example in a prompt makes a claim about the shape of a correct answer. $EXAMPLE blocks make that implicit type explicit and checkable.6 min GuidesWhere the rules go: a field guide to instruction placementTwo prompts with identical rules can behave differently because the rules sit in different places. How placement affects attention, and how named sections make structure the compiler’s job.5 min Deep divesYour prompt argues with itselfThe three species of prompt contradiction, how a model silently picks a winner at runtime, and how the checker proves conflicts with logic/contradiction instead of guessing.6 min