← Tidelines/Best practices

Legal has your prompt open in a tab

Nine checks before somebody outside your team reads the file that runs your agent, each with a command behind it. The last one is where a compiler stops being any help at all.

by TypeGlish team9 min read#best-practices
Consistency is not compliance.

TL;DR Hand over the built artifact and its hash rather than the .tg, because the file is not the text anyone was served, then fix the four things a reviewer always finds: a disclosure rule written as a preference instead of a directive, a personal-data rule that names no field, a human-handoff tool no rule references, and a credential in the prompt. The file here is F (64/100) with a blocking security/leaked-secret and refuses to build; the rewrite is 0 error, 0 warning, 0 info at B (88/100). Then stop, because the prompt that impersonates a human scores A (92/100) and the one that discloses scores B (89/100).

The request never arrives as a review request. It arrives as a calendar invite with two people you have not met, or as a message asking whether the bot "says anything about being a bot." Transparency duties for AI systems that talk to people are live in the EU from August 2026, plenty of contact centres were already doing it under their own policy, and the net effect is the same either way: somebody who does not write prompts now needs to read yours.

This list is the order to run the commands in before that happens. It is not legal advice and it cannot be, because none of the questions being asked of you are answerable by a compiler. What a compiler can do is settle the mechanical half, which is most of what goes wrong: whether the thing you are handing over is the thing that ran, and whether the rules you point at are rules at all.

care-agent.tg - the file that got scheduled✗ F (64/100)
<$CONFIG>
  $IMPORT tool transfer_to_human
  $REQUIRE variable region: one of uk, de, fr
</$CONFIG>

# Role
You are a support agent for Cobalt Mobile.

# Constraints
- You SHOULD be transparent about being an AI.
- You MUST handle personal data appropriately.
- You MUST escalate to a human agent when the caller asks.
- You MUST authenticate with Bearer sk-live-4f9c2b7e81d0a3c6.

$SWITCH ON @{region}
  - uk:: You MUST quote prices in pounds.
  - de:: You MUST quote prices in euros.
  - fr:: You MUST quote prices in euros.
A plausible file. Every one of the three compliance-shaped lines was written by somebody trying to do the right thing, and all three of them are the wrong shape. The fourth line is what happens when a tool integration gets debugged inside the prompt.

§1Establish what is being reviewed

1. Send the artifact, not the file. This is the one people get wrong before anything else, and it invalidates the whole review. A .tg file is source. Commands, notes and tests never reach the model, pointers flatten, and a conditional resolves differently per segment, so the document you email is not the document anybody was served. build produces the text that was, and pins it.

tg build care-agent.tg - refused
$ npx typeglish build care-agent.tg 2>&1 | head -2
typeglish build: care-agent.tg refused — nothing written
care-agent.tg:2:3  warn   structure/unused-import  Imported tool "transfer_to_human" is never used.
$ npx typeglish build care-agent.tg >/dev/null 2>&1; echo $?
1
2. Nothing gets reviewed until it builds. Which is item two, arriving early because the compiler put it there. A file with a proven error refuses to produce an artifact and exits 1, so there is no version of this prompt you can hand over as the current text. That is a better failure than the alternative, where somebody reviews a source file nobody deployed.

§2The four rules a reviewer will look for

3. Read the findings before anyone else does. Three of the four problems in this file are already in the report, which is the cheapest possible way to find them.

tg check care-agent.tg - output
care-agent.tg:2:3  warn   structure/unused-import  Imported tool "transfer_to_human" is never used.
care-agent.tg:11:33  info   prompt/vague  Vague — "appropriately" names a judgment call without the criterion to judge by, so nothing can check it. Replace it with a measurable form ("at most 3 sentences", "within 24 hours", "cite the source").
care-agent.tg:13:30  error  security/leaked-secret  Leaked secret — this looks like a real bearer token. Remove it and rotate the credential; reference it as a {variable} instead.

 1 file — 1 error, 1 warning, 1 info
One error, one warning, one info, and the severities are almost inverted relative to how a reviewer would rank them. The credential is the blocking one. The unreferenced handoff tool is a warning. The personal-data rule that checks nothing is an info you would scroll past.

4. The disclosure rule has to be a rule. This is the finding the checker does not make, and it is the one a reviewer will land on first. You SHOULD be transparent about being an AI names no action, no moment and no words, and SHOULD does not oblige, so there is nothing in it to enforce and nothing to test. Compare a directive with all three: You MUST state that you are an AI assistant in your first reply. Same intent, and now it has a subject, a modal, a verb and a place in the conversation, which is what makes it pinnable by a $TEST instead of a matter of opinion.

5. Name the field, not the category. Handle personal data appropriately is the single most common line in a compliance section and it is prompt/vague for a reason the message states plainly: it names a judgment call without the criterion to judge by. Personal data is a category with about nine kinds of handling attached to it, and a rule that spans all nine cannot be checked against any. What a reviewer needs, and what a checker can hold, is the field and the operation: You MUST redact every payment card number before you quote a message back. That is the same move as turning a QA rubric row into a directive, applied to policy language instead of quality language.

6. The right to a human needs a tool behind it. MUST escalate to a human agent when the caller asks reads like the strongest line in the file, and structure/unused-import is the reason it is not: the prompt declares a transfer_to_human tool and no rule mentions it, so the model cannot discover it. The sentence describes a handoff and the file provides no way to perform one. Point the rule at the tool with @[transfer_to_human] and the warning goes with the defect.

7. Know which credential shapes the checker can see. The bearer token above is caught, and it is worth knowing that this is the shape that gets caught rather than assuming a blanket sweep. Three files, one credential, three different verdicts.

tg check secrets/ - one credential, three placements
secrets/in-header.tg:1:1  warn   structure/unused-service  $SERVICE crm is never used by any tool's request or via:: transport.
secrets/in-header.tg:4:5  warn   structure/secret-literal  This looks like a credential literal — never put secrets in the file; use @{env.NAME} instead.
secrets/in-prose.tg:5:30  error  security/leaked-secret  Leaked secret — this looks like a real bearer token. Remove it and rotate the credential; reference it as a {variable} instead.

 3 files — 1 error, 2 warning, 0 info
program: 3 independent files — no $IMPORT compositions
The same string, sk-live-4f9c2b7e81d0a3c6, three ways. In a rule the model reads, behind the word Bearer: a blocking security/leaked-secret. In a $SERVICE header, where it is at least on the machine plane: structure/secret-literal, a warning with the @{env.NAME} rewrite attached. And in-declaration.tg, which binds it with a copula as Crm_token IS sk-live-4f9c2b7e81d0a3c6., does not appear above at all. The blocking case is the one where the credential would be read out to a caller, which is the right thing to block on and is not the same thing as a secret scan.

§3Produce the evidence

Now the part that a reviewer will actually keep. Three artifacts, and none of them is the prompt.

8. Enumerate every prompt that shipped, and count the distinct ones. The question you will be asked is what a specific customer was told, and the answer is not "the prompt." Build once per member of every typed domain, and count hashes rather than segments.

tg build care-agent-v2.tg --vars - once per region
$ for r in uk de fr; do npx typeglish build care-agent-v2.tg --vars "{\"region\":\"$r\"}"; done
 built .typeglish/dist/care-agent-v2.txt ← care-agent-v2.tg (3759c82528ed, full)
 built .typeglish/dist/care-agent-v2.txt ← care-agent-v2.tg (a3dee2ab248e, full)
 built .typeglish/dist/care-agent-v2.txt ← care-agent-v2.tg (a3dee2ab248e, full)

$ jq '.builds | length' .typeglish/build-manifest.json
1
Three regions, two prompts. de and fr come back as the identical hash because their switch arms carry the same sentence, so the honest count of documents to review is two and not three. The second command is the limitation to plan around: the manifest holds one row per source file, so three builds leave one row, and the archive of what shipped when has to be your CI log rather than this file.
the de artifact - what a German caller's agent was told
# Role
You are a support agent for Cobalt Mobile.

# Constraints
- You MUST state that you are an AI assistant in your first reply.
- You NEVER claim to be a human.
- You MUST redact every payment card number before you quote a message back.
- WHEN a caller asks for a person THEN you MUST call transfer_to_human.
- You MUST call log_contact before you close a contact.

You MUST offer an EU privacy notice when a caller asks what Cobalt Mobile does with their data.
Eleven lines, every one of them a sentence somebody outside your team can read and hold you to. The $SERVICE, the $TOOL schema, the @@ notes and the $TEST are all absent, because none of them was ever sent to the model. This is the document under review.

9. Make the control an exit code, and write down why each rule exists. A reviewer asking "how do you know this stays true" is asking for a control, and a command with an exit code is the cheapest one that exists. Two of them, on the file before the rewrite and after it.

tg check --strict / tg score --min - the gate
$ npx typeglish check care-agent.tg | tail -1
 1 file — 1 error, 1 warning, 1 info
$ npx typeglish check care-agent.tg --strict | tail -1
 1 file — 2 error, 0 warning, 1 info

$ npx typeglish score care-agent.tg --min B >/dev/null
typeglish score: 64 (F) is below the --min floor B (=80) — proven errors cap the grade at F
$ echo $?
1

$ npx typeglish check care-agent-v2.tg --strict >/dev/null; echo $?
0
$ npx typeglish score care-agent-v2.tg --min A >/dev/null
typeglish score: 88 (B) is below the --min floor A (=90)
$ npx typeglish score care-agent-v2.tg --min B >/dev/null; echo $?
0
--strict promotes the unreferenced handoff tool from a warning to a blocking error, one error becoming two on a file nobody edited, which is the setting you want on anything with a compliance section: an advisory is a decision somebody deferred, and deferring is what a control is for. The --min failure line goes to stderr, so it survives >/dev/null. Pin the floor at the number you shipped and not at an aspiration: --min A exits 1 on the rewrite at 88, so B is the honest gate.

The rationale record is the other half of item nine and it costs nothing at runtime. An @@ note is compile-time only, so it is the one place in the file where you can write down which policy a rule came from without adding a token to the prompt. It is also the annotation facet, so the score notices.

care-agent-v2.tg - the reviewable version✓ B (88/100)
<$CONFIG>
  $REQUIRE variable region: one of uk, de, fr
  $IMPORT tool transfer_to_human
</$CONFIG>

$SERVICE crm
  - base:: https://api.cobalt.example
  - headers::
    - Authorization:: Bearer @{env.CRM_TOKEN}

$TOOL log_contact
  - description:: Records the outcome of a contact against the account.
  - input::
    - account_id:: string
    - outcome:: one of resolved, escalated, abandoned
  - request:: POST crm /contacts/@{account_id}

# Role
@@ role: billing and account support for a mobile network
You are a support agent for Cobalt Mobile.

# Constraints
@@ disclosure: the transparency duty is one sentence in the first reply, so it is written as one
- You MUST state that you are an AI assistant in your first reply.
@@ no_impersonation: a caller who believes they reached a person discloses more than they meant to
- You NEVER claim to be a human.
@@ card_data: handle personal data appropriately named no field, so it checked nothing
- You MUST redact every payment card number before you quote a message back.
@@ human_route: the right to a person is a rule with a tool behind it, not a sentiment
- WHEN a caller asks for a person THEN you MUST call @[transfer_to_human].
@@ record: every contact leaves a row, because an audit reads rows and not transcripts
- You MUST call @[log_contact] before you close a contact.

$SWITCH ON @{region}
  - uk:: You MUST offer a UK privacy notice when a caller asks what Cobalt Mobile does with their data.
  - de:: You MUST offer an EU privacy notice when a caller asks what Cobalt Mobile does with their data.
  - fr:: You MUST offer an EU privacy notice when a caller asks what Cobalt Mobile does with their data.

$TEST disclosure_first_reply
  - input:: Hi, is this Dana?
  - expect::
    - matches /AI|assistant/
0 error, 0 warning, 0 info at B (88/100), and F (64/100) to B (88/100) is the whole arc. The credential moved to @{env.CRM_TOKEN}, the handoff rule points at its tool, the personal-data rule names a field, the disclosure rule became a directive, and every rule carries the note explaining which decision it came from. enforceability 57 is the lever and it is honest: half of a compliance section is triggered behaviour, and a conditional is genuinely less enforceable than a bound, so this shape of file does not reach A.
tg test care-agent-v2.tg --dry - the disclosure case
$ npx typeglish test care-agent-v2.tg --dry
 care-agent-v2.tg  coverage: 1/6 rules exercised
  · disclosure_first_reply — "Hi, is this Dana?" (not run)
       matches /AI|assistant/
 1 prompt — 0 failed
A caller opens by asking whether they have reached a person, and the reply has to contain AI or assistant. Deterministic, offline, and the one piece of evidence in this whole list that is about behaviour rather than about text. coverage: 1/6 is the honest number and it is the obvious next piece of work.

§4What the compiler will not sign

Then stop, because the instruments run out here and it is better to know exactly where. Two files, both clean, both scored.

who/impersonates.tg - clean, and scored higher✓ A (92/100)
# Role
You are a support agent for Cobalt Mobile.

# Constraints
@@ persona: the brand wants a first name on the transcript
- You MUST tell the caller that you are a human advisor named Dana.
@@ brevity: three sentences keeps a chat reply scannable
- You MUST keep every reply to at most 3 sentences.
The sibling file, who/discloses.tg, is the same shape with the two disclosure rules from the rewrite above: MUST state that you are an AI assistant in your first reply and NEVER claim to be a human. Both files check at 0 error, 0 warning, 0 info. The one that impersonates a person scores A (92/100) at enforceability 85. The one that discloses scores B (89/100) at enforceability 70.

Nothing is broken there. Tell the caller that you are a human advisor named Dana is a more enforceable instruction than state that you are an AI assistant in your first reply, because the first names a concrete thing to say and the second has a qualifier the scorer reads as a condition. The score is measuring how checkable each rule is, exactly as documented, and it has no opinion whatsoever about which one you should ship.

A checker proves your prompt agrees with itself. Nobody has built one that knows what you are allowed to say.

So the boundary is clean: everything above item nine is mechanical and belongs in CI, and the decision about what the rules should be belongs to people, one of whom is about to read your file. The nine checks are how you make sure they are reading the right document, and that every line in it is a line you can be held to. Watch the exceptions in particular, because a qualifier added to soften a compliance rule often moves it somewhere nothing can referee it.

§5Common questions

What do I hand over when someone asks to review my AI agent's system prompt?
The built artifact, plus its hash, plus the source file it came from. The .tg file is source: scaffolding, notes and tests never reach the model, and a conditional resolves differently per segment, so the file is not the text anyone was served. typeglish build writes the artifact and records sourceSha256 and artifactSha256 in .typeglish/build-manifest.json alongside the compiler version, the vars used and the check result. Hand over the artifact for what was said and the source for why it was said.
Does a system prompt need to say the agent is an AI?
Disclosure duties for AI systems that interact with people are live in the EU from August 2026, and many contact centres were already doing it under their own policy, so a reviewer will look for it. Whether it applies to you is a question for your own counsel, not for a compiler. What a compiler can tell you is whether you wrote it as a rule at all: You SHOULD be transparent about being an AI names no action and no moment, while You MUST state that you are an AI assistant in your first reply is a directive with a subject, a modal and a place in the conversation, and it is the version a $TEST can pin.
How many different system prompts is my agent actually running?
Count distinct artifact hashes, not segments. Build once per member of every typed domain and compare: a prompt with $REQUIRE variable region: one of uk, de, fr builds to two distinct artifacts, not three, because the de and fr arms carry the same sentence and come back as the same hash. That is the honest answer to which instructions a given customer was given. Note that the build manifest holds one row per source file, so three builds of one file leave one row and the archive has to be your CI log.
Can a prompt checker prove my agent is compliant?
No, and it is worth being blunt about the gap. The checker proves internal properties: that the rules do not contradict each other, that every reference resolves, that a credential is not sitting in the file, that a bound is a bound. It has no idea what your obligations are. Two clean files settle it: one that requires the agent to state it is an AI assistant scores B (89/100), and one that requires the agent to tell the caller it is a human advisor named Dana scores A (92/100), because the second rule is more enforceable. The score measures whether a rule can be checked, never whether it should exist.
Field note

The reason this list is worth running before the meeting rather than during it is that almost every finding in it is embarrassing in a specific way: not wrong policy, but policy that was never written down as anything a machine or a person could act on. Be transparent about being an AI. Handle personal data appropriately. Escalate to a human when the caller asks. Three sentences that would pass any review of intent, and all three describe an outcome nobody arranged. The prompt is the last place in the stack where that stops being a document problem and becomes a behaviour problem, and it is the one place a reviewer can read directly. Make the file boring first. Then have the conversation about what the rules ought to say, which is the part that is actually their job and not yours.

∿ washed up Aug 16, 2026 ∿