← Tidelines/Best practices

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.

by TypeGlish team12 min read#best-practices
One prompt. Two doors.

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.

legacy.md - the file you have
# 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.
Nothing unusual and nothing malicious. A hard-wrapped opening paragraph, a comparison symbol, a typographic dash, two brace placeholders, a bare tool name, and two rules (be warm and be concise, if unsure, ask) that a checker is going to have opinions about.

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

tg import legacy.md - the report✓ 4 repairs
$ 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
The report has three parts worth reading separately: the repairs it already applied, the lifts it inferred (each {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.

tg import --diff - the repairs, shown
$ 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}, ...
Three of the four repairs are unarguable. The symbol becomes a bound the solver can read, the typographic dash becomes ASCII, and both braces become pointers with declarations above them. Read the second hunk again.

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.

the same import, one extra legacy rule✗ disagreement
# 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
Three commands, one file, two verdicts. 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.

A: returns-fixed.tg - imported, worklist cleared✓ C (79/100)
$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.
B: returns-fresh.tg - written from the policy✓ A (93/100)
<$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
round 3 - tg check and tg score, both routes
$ 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)
Fourteen points apart, and the two facets that moved are 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:

A, annotated - the same rules, with reasons✓ A (90/100)
$ 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.

tg build - what reached the model, both routes
$ 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.
Both artifacts carry the whole prompt each source file describes, and B is missing two rules the legacy file had: be warm and be concise and if unsure, ask a clarifying question. Both were dropped on the way through the blank page, for a good reason in each case, and neither drop is reported by anything, because both files are 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.

the same line, before and after declaring the tool
# 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
An import cannot discover your tools, so it never declares them, so a tool name in prose is just a word. Declaring it turns the same clean 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 scores A (90/100), against A (93/100) for a from-scratch rewrite: eleven of those fourteen points are annotation, a hygiene facet the model never reads, and the last three are enforceability, 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 as at most 3 sentences and replacing a typographic dash with a spaced hyphen are all safe. The one to read carefully is structure/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. Run typeglish import with --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, import printed 0 errors and the line 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. Always run check and score on 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 info in a file that never declares get_order, and the identical line is a blocking structure/bare-tool-ref error the moment $IMPORT tool get_order appears 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.
Field note

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.

∿ washed up Aug 28, 2026 ∿