How to write a procedure your agent follows in order
Half of contact-centre work is a sequence: verify, diagnose, resolve, log. Most system prompts describe that sequence in a paragraph, which reads fine and gives you nothing to point at, check, or safely edit six weeks later.
TL;DR Write the sequence as a numbered list under a heading and TypeGlish binds it as a scope: each step gets an address, other rules can reference it (before step 2), and four blocking errors defend the structure, structure/missing-step for a gap, structure/duplicate-step for a repeated number, structure/dangling-step-ref for a pointer to a step that no longer exists, and structure/ambiguous-step-ref when two sections both have one. Point at a #anchor instead of a number and the compiler keeps the reference correct through every renumber.
Here is the procedure, as it usually arrives: Check who you are talking to, then look at the line, and book an engineer if the line is bad. Offer a credit at the end when the outage ran longer than a day. That is a real sequence, correctly ordered, and a model will mostly follow it. It is also four instructions with no handles. Nothing else in the file can say never do the third thing before the second thing, because the third thing has no name. Delete one clause and nothing anywhere notices.
§1A procedure is a scope, not a paragraph
Number the steps and the file changes shape. TypeGlish reads a numbered list under a heading as a step scope: the ordinals are bound, each step becomes a thing the rest of the document can reference by number, and the section is the boundary that number lives inside.
# Role You are a fault-triage agent for Northwind Broadband. # Instructions 1. MUST ask for a postcode and an account PIN before any account change. 2. MUST run a line test on every reported fault. 3. MUST book an engineer visit when a line test fails. 4. MUST offer a service credit when an outage ran longer than 24 hours. # Constraints - NEVER book an engineer visit before step 2. - MUST keep every chat reply to at most 4 sentences.
before step 2 is a reference, and a reference is the thing a checker can defend.Two habits are worth adopting here before anything else. The first is that a step is a statement like any other, so it takes a modal. A bare imperative (Run a line test) parses, ships, and lowers weakly; the same step written MUST run a line test is a hard rule the prover can hold. Numbering a list of vague imperatives buys you structure and no enforceability, which is a common way to end up with a tidy prompt that proves nothing.
The second is that a list is a sequence, not a constraint. Writing 1, 2, 3 tells the model an order and tells the checker nothing about whether that order is mandatory. If step 2 must really precede step 3, say so in # Constraints, as its own rule. That rule is what makes the ordering enforceable, and it is also the thread that snaps loudly when somebody edits the list underneath it.
§2The gap, and the pointer that outlived the step
Six weeks later, the line test moves into the platform and runs automatically before the conversation starts. Someone deletes step 2. They do not renumber, because renumbering a list in a prompt feels like the sort of cosmetic change that causes bugs.
# Role You are a fault-triage agent for Northwind Broadband. # Instructions 1. MUST ask for a postcode and an account PIN before any account change. 3. MUST book an engineer visit when a line test fails. 4. MUST offer a service credit when an outage ran longer than 24 hours. # Constraints - NEVER book an engineer visit before step 2. - MUST keep every chat reply to at most 4 sentences.
gap.tg:6:1 error structure/missing-step Missing step 2 - the sequence jumps from 1 to 3. gap.tg:10:1 error structure/dangling-step-ref Reference to step 2, which is not defined. paste.tg:6:1 error structure/duplicate-step Duplicate step "2" - 2 steps carry this number, so a reference to it is ambiguous. Renumber one of them. (conflicts with line 6) paste.tg:7:1 error structure/duplicate-step Duplicate step "2" - 2 steps carry this number, so a reference to it is ambiguous. Renumber one of them. (conflicts with line 5) ✗ 2 files - 4 error, 0 warning, 0 info
structure/missing-step is about the list: the numbering jumps. structure/dangling-step-ref is about the rest of the file: something points at a step that is not there.Both block, and blocking is the right call for a reason worth sitting with. A gap in a numbered list is genuinely ambiguous, and the model resolves the ambiguity without telling you. Is 1, 3, 4 a list with a step missing, or a list somebody renumbered halfway? Those are different prompts, and a warning would let you ship whichever one the model guessed.
The second file in that output, paste.tg, is the same failure from the other direction: someone pastes a new step in and gives it the number of the step above. Two steps numbered 2, one blocking structure/duplicate-step on each. The message says why plainly: a reference to it is ambiguous. The list still reads fine to a human, who will simply carry on counting.
A numbered list is a set of addresses. Renumbering is an API change.
§3Two procedures, one step 2
Real agents run more than one procedure. Identity verification is one sequence, fault diagnosis is another, and they are separate for good reasons: different owners, different triggers, different failure modes. Steps are scoped per section, so both can count from 1 and nothing complains, which is exactly what you want.
What does complain is a reference that could mean either of them.
# Role You are a fault-triage agent for Northwind Broadband. # Identity 1. MUST ask for a postcode and an account PIN. 2. MUST record a verification result on a contact record. # Fault 1. MUST run a line test on every reported fault. 2. MUST book an engineer visit when a line test fails. # Constraints - NEVER book an engineer visit before step 2.
split.tg:13:1 error structure/ambiguous-step-ref Step 2 exists in 2 sections, so this reference could point at any of them. Qualify it with its section (<name> step 2), give the step a #anchor, or renumber so only one section uses 2. ✗ 1 file - 1 error, 0 warning, 0 info
The qualified form, <Fault> step 2, is the direct fix and reads well in a rule. The anchor form is the one to reach for when the list is still moving.
# Role You are a fault-triage agent for Northwind Broadband. # Identity 1. MUST ask for a postcode and an account PIN. 2. MUST record a verification result on a contact record. # Fault 1. #linetest MUST run a line test on every reported fault. 2. MUST book an engineer visit when a line test fails. # Constraints - NEVER book an engineer visit before step #linetest.
structure/dangling-step-ref, the same error a deleted step gives you.§4The anchor never reaches the model
An obvious worry about anchors: you have just put a piece of syntax into the middle of an instruction the model is going to read. Build the file and check.
✓ built .typeglish/dist/anchored.txt ← anchored.tg (3e477bbd6a07, full) # Fault 1. MUST run a line test on every reported fault. 2. MUST book an engineer visit when a line test fails. # Constraints - NEVER book an engineer visit before step 1.
step 1. The compiler resolved the name to the ordinal that step currently holds, so the model reads an ordinary numbered instruction.Now insert a step above it, because the team decides the agent should restate the reported fault before it tests anything. In the source, the rule in # Constraints does not change at all.
✓ built .typeglish/dist/anchored2.txt ← anchored2.tg (1f2ba4b11a1a, full) # Fault 1. MUST confirm the fault a customer is reporting. 2. MUST run a line test on every reported fault. 3. MUST book an engineer visit when a line test fails. # Constraints - NEVER book an engineer visit before step 2.
before step #linetest. Different artifact line, before step 2. The reference is maintained by the compiler rather than by whoever edited the list, which is the entire argument for anchors.This is the same compile-time and runtime split that governs @@ notes and every $-command, laid out in your system prompt has a compile time: the file is source, the artifact is what ships, and a name that exists only to keep the source honest costs the model nothing.
§5The finished procedure
Put it together: two step scopes, one anchored reference, a reason above every line, and a test that pins the ordering rule rather than trusting it.
# Role You are a fault-triage agent for Northwind Broadband. # Identity @@ ask_first: no account change happens on an unverified caller 1. MUST ask for a postcode and an account PIN. @@ log_it: the audit trail is the only proof verification happened 2. MUST record a verification result on a contact record. @@ dead_end: a caller who cannot verify still deserves a route out 3. MUST offer a callback when you cannot confirm an identity. # Fault @@ restate: the reported fault and the real fault are often different 1. MUST confirm the fault a customer is reporting. @@ evidence: the line test is the only evidence that beats a guess 2. #linetest MUST run a line test on every reported fault. @@ dispatch: a failed line test is what a visit is for 3. MUST book an engineer visit when a line test fails. # Constraints @@ order: a visit booked before the test is a wasted truck roll - NEVER book an engineer visit before step #linetest. @@ brevity: chat replies over 4 sentences stop being read - MUST keep every chat reply to at most 4 sentences. $TEST visit_needs_a_test - input:: My broadband is down, just send an engineer today. - expect:: - contains "line test" - at most 4 sentences
triage-final.tg - A (93/100) proven errors: none tiers: base+z3 planes runtime 92 (what the model reads) · hygiene 95 (source only) facets enforceability 72 x.21 · hardness 100 x.12 · directness 99 x.08 · consistency 100 x.17 structure 100 x.12 (hygiene) · annotation 89 x.12 (hygiene) · style 100 x.08 · security 100 x.08 ✓ triage-final.tg coverage: 2/7 rules exercised · visit_needs_a_test - "My broadband is down, just send an engineer today." (not run) ✓ contains "line test" ✓ at most 4 sentences
Enforceability sits at 72 and that is the honest ceiling for this file. Three of the seven rules are conditional (when a line test fails, when you cannot confirm an identity), and a conditional is less enforceable than a flat obligation because the guard is prose the prover largely leaves alone. That is a real trade, not a defect: the alternative is an agent that books an engineer for every caller.
§6Common questions
- How do I make an AI agent follow steps in order?
- Number them, and then write down the ordering you actually care about as a separate rule. A numbered list under
# Instructionsgives the checker a scope: it binds 1, 2, 3 as steps and gives each one an address other lines can point at. The order itself is still a rule you have to state, because a list is a sequence and not a constraint:NEVER book an engineer visit before step 2is the line that makes the ordering enforceable, and it is also the line that breaks loudly if step 2 ever stops existing. - Why does the checker say a step in my prompt is missing?
- Because the numbering jumps.
structure/missing-stepis a blocking error that fires when a sequence goes 1, 3, 4, and it almost always means somebody deleted a step and left the numbering alone. The reason it blocks rather than warns is that a gap is ambiguous in a way the model resolves silently: it can read as a step you forgot to write, or as a renumbering you forgot to finish, and those are different prompts. - Can two sections of my prompt both have a step 2?
- They can, right up until something references step 2. Steps are scoped per section, so an identity procedure and a fault procedure can both count from 1 with no complaint. A bare reference to
step 2across two such sections isstructure/ambiguous-step-ref, a blocking error, and there are two fixes: qualify the reference with its section, as in<Fault> step 2, or put a#anchoron the step and point at that instead. - Do step anchors show up in the prompt the model reads?
- No. The anchor is source-only. Write
step #linetestin the file, build it, and the artifact saysstep 2, because the compiler resolves the anchor to whatever ordinal that step currently holds. Insert a new step above it and the source line does not change while the built line becomesstep 3. That is the whole reason to use anchors: the reference is maintained by the compiler instead of by whoever last edited the list.
The reason step references earn their keep is the finding in delete a line, see who notices: of eleven deletions from a working prompt, only the ones that participated in a reference were visible to the checker at all. A step nobody points at is exactly as deletable as any other line. Point one rule at it and the deletion becomes two blocking errors instead of a quiet behaviour change nobody can date.