Bake-off: import the old prompt vs. retype it
Everybody arrives at a prompt compiler holding a prompt. One route runs the converter and works the list it hands back; the other opens a blank file and writes the policy again. The grades come out three points apart, and the three points are not the story.
TL;DR Import wins, but only if you treat the import report as a first draft and not a verdict: the imported file with its worklist cleared is C (79/100) and reaches A (90/100) on @@ notes alone against A (93/100) for a from-scratch rewrite that silently dropped two rules, while typeglish import printed 0 errors and builds clean with --gate exiting 0 on a variant that check answers with 2 blocking logic/numeric errors and build refuses outright.
This is the first decision in the job and it never gets written up, because it happens on day one and by day three nobody remembers making it. You have a system prompt. It works, mostly. It has been edited by four people and one of them has left. You want it under a checker. Do you convert it, or do you write it again properly now that you know what the language wants? The honest case for retyping is strong: the old file is the reason you are here. The honest case for importing is also strong, and it is not the one you would guess.
§1The contenders
One legacy prompt, in the shape these actually arrive in: a markdown file out of a repo, or a text box in a bot platform, or a Google Doc. Eight rules, two placeholders, a tool named in passing, and the usual typography.
# Calder Retail - Returns Assistant You are the returns assistant for Calder Retail. You help customers return items, check refund status, and understand the returns policy. ## Rules - Always greet the customer by name: Hi {first_name}! - Keep replies to ≤ 3 sentences. - Be warm and be concise. - If unsure, ask a clarifying question. - Never promise a refund date — say "within 5 working days" instead. - Look up the order with get_order before you discuss a return. - The returns window is 30 days. ## Escalation If the customer is angry or the order value is over {escalation_threshold}, transfer to a human.
Contender A runs typeglish import legacy.md and then works the worklist it prints, changing nothing the worklist did not name. Contender B opens a blank .tg and writes the same policy the way the language wants it, tools declared, inputs typed, bounds spelled out. Four rounds.
§2Round 1: what the converter actually does
$ typeglish import legacy.md --out returns.tg imported -> /tmp/imp/returns.tg 4 repairs (structure/missing-period x1, structure/special-character x1, structure/em-dash x1, structure/wrapped-fragment x1) · lifted: first_name, escalation_threshold 0 errors · 1 warn · 4 info (was 0 · 5 · 6) the worklist: syntax L14 info typeglish/if-then: IF needs a THEN - write IF <condition> THEN <action>. L21 info typeglish/if-then: IF needs a THEN - write IF <condition> THEN <action>. optimization L13 warn prompt/unmeasurable: Unmeasurable rule - "be <quality>" names no observable action, so nothing can check compliance. Name a concrete action, or move it to a... L7 info prompt/unintroduced-definite: "the returns policy" retrieves something this document never introduces - a model must guess which policy is meant... L7 info prompt/unregistered-doer: A bare generic doer never enters the world model... ✓ builds clean - next: typeglish build /tmp/imp/returns.tg
{placeholder} became a $REQUIRE variable), and the worklist of things it will not touch, grouped by plane. Five findings out of eleven, which is the honest number: it fixed the form and left every judgement to you.That grouping is the useful part. syntax is mechanical: two prose conditionals that stated a condition and never stated a consequence. optimization is policy: an unmeasurable rule, a definite noun with no referent, a doer nobody registered. The split is a work plan, and it is a better one than a flat list of thirty findings, which is what you get if you paste the same file into a blank .tg and hit check.
Now the repairs themselves, which is where the round is decided.
$ typeglish import legacy.md --out returns.tg --diff the repairs (--diff): + $REQUIRE variable first_name + $REQUIRE variable escalation_threshold + # Calder Retail - Returns Assistant - You are the returns assistant for Calder Retail. You help customers return - items, check refund status, and understand the returns policy. + You are the returns assistant for Calder Retail. You help customers return. + Items, check refund status, and understand the returns policy. ## Rules - - Always greet the customer by name: Hi {first_name}! - - Keep replies to ≤ 3 sentences. + - Always greet the customer by name: Hi @{first_name}! + - Keep replies to at most 3 sentences. - Be warm and be concise. - If unsure, ask a clarifying question. - - Never promise a refund date — say "within 5 working days" instead. + - Never promise a refund date - say "within 5 working days" instead. - Look up the order with get_order before you discuss a return. - The returns window is 30 days. … - If the customer is angry or the order value is over {escalation_threshold}, ... + If the customer is angry or the order value is over @{escalation_threshold}, ...
The hard-wrapped opening paragraph came back as two sentences, and the first one is a sentence the author never wrote:
You help customers return.
The structure/wrapped-fragment repair terminated the line and capitalised the next one, which is exactly right for a hard-wrapped rule and is what makes the repair worth having: a bound on one line and its condition on the next really are two statements the model reads separately, and joining them is the fix. On a hard-wrapped paragraph the same repair produces a complete declarative sentence with the wrong meaning, followed by a fragment starting Items, check refund status. The checker then says nothing about either half, because both sit inside prose, and prose is exempt from rule analysis by design.
So round 1 goes to A on volume and to B on trust, and the takeaway is a flag rather than a verdict: run import with --diff, and read the prose hunks of that diff with your finger on the screen. Two of the four repairs in this file landed inside prose and only one of them was harmless.
§3Round 2: the report is not a check
The import report ends with a green tick and the words builds clean, and there is a --gate flag documented as exiting non-zero when errors would block a build, which reads like a CI hook. Take one line of the legacy prompt and make it the thing every inherited prompt eventually has, a second length rule set by somebody else.
# legacy.md, with one line added under ## Rules - Keep replies to ≤ 3 sentences. + - Always write at least 6 sentences so the customer feels heard. $ typeglish import legacy.md --out g1.tg --gate imported -> /tmp/imp/g1.tg 4 repairs (...) · lifted: first_name, escalation_threshold 0 errors · 1 warn · 4 info (was 0 · 5 · 6) ... ✓ builds clean - next: typeglish build /tmp/imp/g1.tg # exit 0 $ typeglish check g1.tg g1.tg:12:1 error logic/numeric Conflicts with line 9. Numeric conflict - "at most 3 sentences" and "at least 6 sentences" can't both hold. g1.tg:13:1 error logic/numeric Conflicts with line 8. Numeric conflict - "at most 3 sentences" and "at least 6 sentences" can't both hold. ✗ 1 file - 2 error, 1 warning, 4 info # exit 1 $ typeglish build g1.tg typeglish build: g1.tg refused - nothing written # exit 1
import --gate reports 0 errors, prints builds clean and exits 0. check on the file it just wrote exits 1 on two blocking logic/numeric errors, and build refuses to write anything. The import report is a report on the conversion, and the conversion succeeded.Round 2 to B, and it is the round that should change what you type. The gate you want in CI is typeglish check and typeglish score --min on the committed .tg, run as their own step after the import, every time. Reading the import tick as a pass is the same false green Hi {first_name}, thanks for contacting us gets from a bot-platform export: a report about the pipe, mistaken for a report about the water.
The mechanical rule, if you want one line for a runbook: import answers did the conversion work. Nothing else does.
§4Round 3: the grades, and what the gap is made of
Back to the clean legacy prompt. Contender A works the worklist and stops: two prose conditionals get their THEN, the unmeasurable rule moves into the # Role prose block where soft language is allowed, the definite gets its article softened, and the wrapped paragraph is rejoined by hand. No rule is reworded and no rule is dropped. Contender B writes the file from the policy.
$REQUIRE variable first_name $REQUIRE variable escalation_threshold # Role You are the returns assistant for Calder Retail. You help customers return items, check refund status, and understand the returns policy. Be warm and be concise. # Rules - Always greet the customer by name: Hi @{first_name}! - Keep replies to at most 3 sentences. - IF unsure THEN ask a clarifying question. - Never promise a refund date - say "within 5 working days" instead. - Look up the order with get_order before you discuss a return. - The returns window is 30 days. # Escalation - IF the customer is angry or the order value is over @{escalation_threshold} THEN transfer to a human.
<$CONFIG> $CONFIG modality chat $IMPORT tool get_order </$CONFIG> $REQUIRE variable first_name $REQUIRE variable escalation_threshold: integer # Role @@ role: the doer every rule below is addressed to You are the returns assistant for Calder Retail. # Constraints @@ greeting: the name is a runtime hole the host fills - ALWAYS greet a customer as @{first_name} in your first reply. @@ brevity: three sentences keeps a returns reply scannable - MUST keep every reply to at most 3 sentences. @@ no_date: a promised date binds the warehouse, and the window does not - NEVER promise a refund date. @@ window_wording: the sanctioned phrasing, so the agent has something to say instead - ALWAYS say "within 5 working days" when a customer asks when a refund lands. @@ lookup: the order has to be real before the agent discusses it - MUST call @[get_order] before you discuss a return. @@ returns_window: the policy fact, stated once The %returns window% IS 30 days. @@ escalate_angry: a heated customer gets a human - IF a customer is angry THEN transfer the contact to a human agent. @@ escalate_value: the value threshold the host supplies - IF an order value is greater than @{escalation_threshold} THEN transfer the contact to a human agent. $TEST refund_date - input:: What day exactly will my money be back? - expect:: - contains "within 5 working days" - at most 3 sentences
$ typeglish check returns-fixed.tg && typeglish score returns-fixed.tg ✓ 1 file - 0 error, 0 warning, 0 info returns-fixed.tg - C (79/100) proven errors: none tiers: base+z3 planes runtime 89 (what the model reads) · hygiene 50 (source only) facets enforceability 61 x.21 · hardness 100 x.12 · directness 98 x.08 consistency 100 x.17 · structure 100 x.12 · annotation 0 x.12 style 100 x.08 · security 100 x.08 lever annotation 0/100 (up to +12 overall) - Put a "@@ why" note directly above each statement ("@@ name: why" also names it). $ typeglish check returns-fresh.tg && typeglish score returns-fresh.tg ✓ 1 file - 0 error, 0 warning, 0 info returns-fresh.tg - A (93/100) proven errors: none tiers: base+z3 planes runtime 91 (what the model reads) · hygiene 100 (source only) facets enforceability 68 x.21 · hardness 100 x.12 · directness 98 x.08 consistency 100 x.17 · structure 100 x.12 · annotation 100 x.12 style 100 x.08 · security 100 x.08 lever enforceability 68/100 (up to +7 overall)
annotation 0 against 100 at weight x.12 and enforceability 61 against 68 at x.21. consistency, hardness, directness, structure, style and security are identical. Both files check clean and both build.So take the gap apart. Add a @@ note above each rule in A, changing not one word of policy:
$ typeglish score returns-fixed.tg # after adding 7 @@ notes returns-fixed.tg - A (90/100) proven errors: none tiers: base+z3 planes runtime 89 (what the model reads) · hygiene 94 (source only) facets enforceability 61 x.21 · hardness 100 x.12 · directness 98 x.08 consistency 100 x.17 · structure 100 x.12 · annotation 88 x.12 style 100 x.08 · security 100 x.08 lever enforceability 61/100 (up to +8 overall) $ typeglish build returns-fixed.tg ✓ built .typeglish/dist/returns-fixed.txt ← returns-fixed.tg (e849cbd21f61, full) $ typeglish build returns-fresh.tg ✓ built .typeglish/dist/returns-fresh.txt ← returns-fresh.tg (702a5a82d396, full)
C (79/100) to A (90/100) on comments, and hygiene 50 to 94. Eleven of the fourteen points were never in the prompt the model reads: runtime stayed at 89 through the whole edit. The remaining three points are enforceability 61 against 68, which is the wording, and that is a real difference honestly earned.Round 3 to B on points and to A on cost. Eleven of fourteen points are documentation, which is a thing you should write anyway and which the import route is unusually well placed to do, because you still have the old file open and can see who wrote what. The last three points are the difference between Always greet the customer by name and ALWAYS greet a customer as @{first_name} in your first reply, and that is exactly the kind of edit worth doing rule by rule rather than in a single sitting. On the annotation facet and which plane it lives in, your prompt has a second reader is the longer argument.
§5Round 4: what each route loses
Both files check clean, so the toolchain has stopped talking. Compare the artifacts instead.
$ cat .typeglish/dist/returns-fresh.txt # B # Role You are the returns assistant for Calder Retail. # Constraints - ALWAYS greet a customer as {first_name} in your first reply. - MUST keep every reply to at most 3 sentences. - NEVER promise a refund date. - ALWAYS say within 5 working days when a customer asks when a refund lands. - MUST call get_order before you discuss a return. The returns window IS 30 days. - IF a customer is angry THEN transfer the contact to a human agent. - IF an order value is greater than {escalation_threshold} THEN transfer the contact to a human agent. $ cat .typeglish/dist/returns-fixed.txt # A # Role You are the returns assistant for Calder Retail. You help customers return items, check refund status, and understand the returns policy. Be warm and be concise. # Rules - Always greet the customer by name: Hi {first_name}! - Keep replies to at most 3 sentences. - IF unsure THEN ask a clarifying question. - Never promise a refund date - say within 5 working days instead. - Look up the order with get_order before you discuss a return. - The returns window is 30 days. # Escalation - IF the customer is angry or the order value is over {escalation_threshold} THEN transfer to a human.
0 error, 0 warning, 0 info. There is no diagnostic for a rule that is not there.That is the real cost of the blank page, and it is not a scoring artefact. Nothing in a compiler can tell you that the eighth rule of a prompt you retyped from memory is missing, because the compiler has never seen the prompt you were remembering. Both of those two rules deserved to be argued about, and the difference between arguing about them and forgetting them is whether the old file is beside you as a list.
A loses something too, and it is a specific thing worth naming, because it is the one part of the import verdict that is not earned.
# A as imported: get_order appears in prose, and nothing declares it - Look up the order with get_order before you discuss a return. ✓ 1 file - 0 error, 0 warning, 1 info # the identical line, with $IMPORT tool get_order added above returns-fixed.tg:21:26 error structure/bare-tool-ref "get_order" is a tool, but this mention is bare prose - the compiler cannot bind it. Point it with @[get_order] (a checked reference), or quote it ("get_order") to speak the name as plain text. ✗ 1 file - 1 error, 0 warning, 0 info
A (90/100) file into a blocking error with a fix attached. Part of every clean import verdict is earned by things the compiler was not told about.Which makes the tool declaration the highest-value edit available after an import, and the one to do first. A tool the prompt only mentions is a tool the prompt cannot be checked against: no binding, no schema, no @[pointer], and no error when a rule sends the agent at a tool that was renamed last quarter. The same asymmetry runs through forbid the tool vs. never hand it over, where you cannot forbid a tool you never declared.
§6What ships
Import, and then do four things in this order. The verdict is not close once the fourteen-point gap is broken into eleven points of documentation and three of wording, but it is conditional on treating the import as a draft.
1. Read the diff, prose first. typeglish import legacy.md --diff. Three of four repairs in this file were unarguable; the fourth rewrote a paragraph into a sentence the author never wrote. The rule bullets are where the repairs are safe and the prose blocks are where they need a human.
2. Check and score as a separate step. typeglish check then typeglish score --min B on the written file, never the import tick, and those two in CI. import --gate exited 0 on a file build refuses.
3. Declare the tools, then fix what that reveals. One $IMPORT tool line per tool the prompt mentions, and expect the error count to go up. That is the checker starting to read a part of the file it could not see.
4. Annotate before you reword. Eleven of the fourteen points are @@ notes, they cost nothing in the artifact, and the day after an import is the only day you still know why rule six exists. Rewording is the three-point job and it can wait for the rule you next need to change.
And one note for whoever is holding a bot-platform export rather than a markdown file: typeglish import takes a .vf Voiceflow export and writes a git-ready prompts/ fileset with a manifest instead of a single file, which is a different shape of migration than the one in this post and the same discipline: read the diff, check the result, declare the tools.
§7Common questions
- Should I import my existing system prompt or rewrite it from scratch?
- Import it, then treat the import as a first draft rather than a result. On the same eight-rule legacy prompt, the imported file cleared of its worklist scores
C (79/100)and the same file with a note above each rule scoresA (90/100), againstA (93/100)for a from-scratch rewrite: eleven of those fourteen points areannotation, a hygiene facet the model never reads, and the last three areenforceability, which is wording. The from-scratch file bought its three points partly by dropping two rules it did not remember, and nothing in the toolchain reports a missing rule. Import keeps every line, gives you a worklist to work through, and gives you a diff to review. - Does typeglish import change the wording of my prompt?
- It repairs form and never a word, and one of the form repairs can still change what a sentence says. Lifting
{first_name}to@{first_name}, rewriting a less-than-or-equal symbol asat most 3 sentencesand replacing a typographic dash with a spaced hyphen are all safe. The one to read carefully isstructure/wrapped-fragment: on a hard-wrapped paragraph, You help customers return / items, check refund status came back as You help customers return. / Items, check refund status, which is a complete sentence with the wrong meaning followed by a fragment. Runtypeglish importwith--diff, and read the prose sections of that diff line by line. - Does a clean typeglish import mean my prompt has no errors?
- No. The import report is a report on the conversion, not a check on the result, and the two can disagree. On one variant of this prompt,
importprinted0 errorsand the linebuilds clean, andimport --gateexited 0, whiletypeglish checkon the file it had just written exited 1 with 2 blockinglogic/numericerrors on at most 3 sentences against at least 6 sentences, andtypeglish buildansweredrefused, nothing written. Always runcheckandscoreon the imported file as a separate step, and put those two in CI rather than the import. - Why did declaring my tool add an error to a file that was already clean?
- Because a tool name in bare prose is only checkable once the compiler knows it is a tool name. Look up the order with get_order before you discuss a return is
0 error, 0 warning, 1 infoin a file that never declaresget_order, and the identical line is a blockingstructure/bare-tool-referror the moment$IMPORT tool get_orderappears above it, with the fix asking for@[get_order]or a quoted literal. An import cannot discover your tools, so part of any clean import verdict is earned by things the compiler was never told about. Declaring them is what makes the checker stricter, and it is the first edit worth making after an import.
The reason this bake-off has a winner at all is that the two routes are not symmetric in what they can lose. A bad import leaves a defect in the file, where a checker, a score floor and a diff can all reach it. A bad retype leaves a rule out of the file, where nothing can. The whole toolchain is built to talk about text that is present: contradictions between statements, bounds that cannot hold, pointers with no target. It has no opinion at all about the sentence you meant to type. That is why the old prompt is worth converting rather than replacing, and it is the same reason delete a line, see who notices ends up recommending a test rather than a review.