typeglish
v0.2.2
Published
The TypeGlish language + compiler — turn English into logically consistent, checkable programs for LLMs.
Maintainers
Readme
TypeGlish
Turning English into logically consistent, controlled, maintainable, executable programs for LLMs.
A controlled language with the rigor of a programming language — operators, declarations, types, and a static consistency checker — that compiles English to clean, XML-tagged prompts. Think the TypeScript of English: you still write English, but a grammar, a type system, and a solver have your back.
The long-term goal: make English safe for agents to work with — a static analyzer for the instructions, policies, and knowledge agents consume, run at corpus scale to catch contradictions the way a compiler does for code. This repo is the rules and the compiler for TypeGlish, plus a playground to build and work on the language. See ROADMAP.md for where it's headed.
Why
A prompt is a program written in English — but English has no compiler. Nothing tells you when two instructions contradict each other, when a constraint is impossible to satisfy, or when a reference is undefined. TypeGlish adds that missing layer.
$REQUIRE variable customer_name: string
# Role
You are a customer support agent for Acme. The customer's name is @{customer_name}.
# Constraints
- NEVER state a specific price; ALWAYS direct pricing questions to sales.
- MUST keep every response to at most 3 sentences.
- IF you are unsure THEN say so AND offer to escalate.- Operators — modals (
MUST/NEVER/SHOULD…), control flow (IF … THEN … OTHERWISE), connectives (AND/OR/NOT), and the copula (IS/ARE/AM). Case-insensitive. - Statement terminators — every statement ends with
.!?(a lead-in may end with:); unterminated is a compile error, one-click fixable — the semicolon rule, one level up. The terminator never leaks into a bound value:Tone IS warm.bindswarm. - Layout is syntax — the indent unit is 2 spaces and indentation mirrors section nesting (a nested tag one unit deeper, its content one deeper than that); one space between things. Alignment columns, trailing whitespace, and tabs are compile errors with one-click fixes — "LLM spacing" doesn't compile. Blank lines, by contrast, are free: the compiler packs the emitted prompt — consecutive prose statements join onto one line, a run of blank lines collapses to a single break, and structure (lists, headings, tags, fences) keeps its own line. Fences and literal zones stay verbatim, and cosmetic indent is stripped at compile, so the model reads clean packed text either way.
- A strict character set — the instruction plane is plain ASCII text: em-dashes, curly quotes,
arrows, bullets, math symbols, emoji, and invisible characters are compile errors with
one-click ASCII fixes. Not aesthetics: each mark hides meaning from the checker (a curly
apostrophe conceals a possession contradiction;
≤ 3hides the boundat most 3proves). Human language never flags — accents, scripts, currency stay legal — and symbols keep their literal homes (<examples>,<"…">zones, fences, quotes). - Declarations —
Persona IS a support agentbinds a reusable object AND renders (binding and instruction are the same statement);@{Persona}expands the value elsewhere (case-insensitive). A declaration whose pointer also expands puts the value in front of the model twice — the checker flags it (prompt/duplicated-declaration). - Types —
Animals ARE one of alligator, orangutandeclares a closed, mutually-exclusive set. - String literals —
"…"escapes to literal prose: no operators parsed inside, quotes stripped on compile,@{refs}still expand. - Sections & structure — group with
#headings or<xml_tags>(same-line and inline content welcome);<$name>strips control-plane scaffolding,<?name>strips just the wrapper tag,<"name">keeps its body verbatim (a literal zone for reference tables and syntax samples — quotes are the one escape, here at section scale), and<examples>is the literal zone by name. Structure is enforced: content must live in a section,#headings must sit inside XML sections once you use any, and section names must be distinct and meaningful — duplicate/near-duplicate names error, vague ones warn. - Definition lists —
term:: definitionrows for glossaries and cheat-sheets: position-insensitive to author (agents never break alignment), parse-literal (samples inside never open zones or bind) yet content-checked — one statement per line holds inside a body (the editor wraps; a hard-wrapped continuation is a compile error), terminators and the character set apply — and the compiler owns the emitted layout (nested sub-bullets, fenced samples) so the model reads clean markdown regardless of authoring; duplicate terms flagged. - Statement annotations & references —
@@ a notedocuments the statement below it (a docstring that compiles away and is inert to every analysis tier);@@ name: …also names that statement. References are typed sigils, each validated at compile time:@{variable}reads host state,@<section>inlines a section,@[tool]references a tool — inline it emits the checked bare name, alone on a line it surfacesname — description(a dangling reference is an error, and an exact tool name in bare prose is too: point it or quote it). - A consistency checker — propositional + SMT (Z3). Catches
MUST escalatevsNEVER escalate, and numeric contradictions likeat most 3 sentencesvsat least 5 sentencesthat propositional logic provably can't decide. Its quantifier layer gives the English of quantity its logic: the square of opposition (all/some/no/most/only), set sizes (there is only one toolvsthere are many tools; nouns fold by lemma, sopeople⇄person), action counts (call five toolsvscall six tools— one count per directive slot), supply vs demand (there is only one toolvscall five tools), definite reference (the toolwith two tools declared), and transitive syllogism chains. Claims key by what they are about (noun + restrictor tail); frame, subject, and owner are refinement fields that must agree where present but never exempt a claim from checking. Directives default to the ADDRESSEE — a bare rule,You must…, a declared alias (You are Claude.→Claude must…), and a declared role (You are a support agent.→the support agent must…) are one subject's rules, while an undeclaredthe assistantstays scoped (no guessing; opt nouns in via glish.tgc$CONFIG addressee one of …) andthe useris never you. Guards parse in both spellings —IF … THEN …and the comma form English writes (If the user is angry, never quote prices.);Don'treads asDO NOT. - A predicate system — actions parse into
verb + theme + roles + tool bindingagainst a curated catalog, so the checker also proves verb-level facts: synonyms merge (output/print), antonyms conflict (MUST outputvsMUST suppressflips the badge), bare imperatives are obligations, andfire book_appointment≡call book_appointment. - A semantic auditor (advisory) — per-line residual value and overlap, scored at the atom level by a local NLI model with an LLM-judge escalation (every verdict distilled as training data for an offline replacement). Heavy duplication warns; proposed cuts are behaviorally verified; a greedy reducer consolidates toward a minimal, orthogonal prompt.
- The TG score — one proof-backed number per prompt (
typeglish score, CI-gateable with--min B). Proven errors gate to F; graded facets measure enforceability (the checker's own visibility), consistency, structure, annotation coverage (every statement should carry a@@note saying what it's for), style, security, and density; every deducted point cites a finding + fix (the ledger is the to-do list). Calibrated against an authored good/bad corpus with a tested separation margin. $TEST— the file carries its own eval suite — cases whose- input::/- expect::bullets live beside the rules they test, or in an auto-discovered sibling<name>.test.tg, and compile away entirely. Deterministic asserts (contains,matches,at most N sentences) are code-checked; prose expectations are judge-scored. Run them withtypeglish test(--dryvalidates the suite + reports rule coverage fully offline; live runs needANTHROPIC_API_KEY); feeds coverage (prompt/untested-rule), the profile, promptfoo emit, portability, and the optimizer.$EXAMPLE— the file carries its own few-shot — one exchange per block (- input::/- good::, optional- bad::), the same bullet record. Each- good::compiles into the prompt as a labelled few-shot exchange;- bad::is held out — never shown to the model, so it can't be imitated. A malformed block is astructure/bad-exampleerror.- The profile — per-statement attribution, keyed by content hash (survives comments, reordering;
an edited statement is a new hash, so staleness is impossible): what a line says (IR, predicates),
what duplicates it (residual), which cases exercise it, and the measured ablation Δ per case
(
npm run profiles -- --behavioral, budget-confirmed; repo dev tool). Those fold into a per-statement verdict — potency (Δ) crossed with redundancy (residual) and coverage → keep / cut (implied or dead) / test, with attribution and honest degradation when a tier hasn't run.npm run annotate(repo) writes it into the margin as//@machine comments — compile-stripped, human comments untouched; the editor shows it on hover. - Portability, measured —
npm run portability(repo dev tool) runs the suite across a model panel (promptfoo owns the matrix; TypeGlish owns zero dependencies) and reports cross-model agreement WITH per-model pass rates beside it — consistency ≠ correctness is encoded, not footnoted. - A dependency map — the playground's Map view renders the prompt as the graph it already is:
the depended-upon symbols (inputs with their domains, tools, variables, tests) on one side, the
section skeleton with a reference chip on every wired line on the other, and hover to light up an
edge — where a symbol is used, or what a line depends on. Broken edges (a
<closing>that resolves to nothing, an unused import) are marked in place, from the same resolution engine as the squiggles, so the map can never disagree with the checker. Deterministic and free. - A fix list, ranked by score impact — the playground's Fixes panel is the score ledger made actionable: every deduction below 100, ordered by the points it costs, pinned to its line, with a one-click fix where one exists. Deterministic and free — it updates as you type, and it never spends a model call. This is "highlight what needs changing," computed, not judged.
- A GEPA optimizer with the compiler in the loop —
npm run optimize(repo dev tool; a deliberate CLI opt-in — the editor highlights, the human changes) runs reflective prompt evolution (per-instance Pareto, minibatch acceptance) where provably-broken mutants are rejected free before any eval spend, mutants that touch their own$TESTblocks are rejected as fitness hacking, and the accept rule breaks ties toward fewer tokens — the bloat spiral of naive optimization loops structurally loses. Every model call (evals AND reflections) is charged against the budget, so the pre-run quote is a hard spend ceiling; the result lands as a new.optimized.tg, never overwriting the seed. - Conditionals, resolved — deterministic
IFchains / guarded sections make a prompt a template:(source, state) → prompt. Two stages, one seam (prepareTemplate): render is state-free and total — an unresolved chain compiles to conditional English (If voice is true: … / Otherwise: …), never a leaked sigil; resolve is state-dependent and partial — it collapses only the branches the bag can decide. The playground's values panel exposes it: set an input and the Compiled view resolves that chain to the one branch the model gets; leave it unset and it stays conditional. - A compiler —
text → parse → IR → check → emit:@{references}expand, comments /@@notes /$-commands /$TESTblocks strip,$EXAMPLEblocks emit their good few-shot (bad held out), and structure is preserved as authored —# headingsstay markdown,<tags>stay tags (the two are never interconverted; they carry different signals). - Agent files — a single
.tgfile can be a whole agent:$CONFIGsets the model and settings,$TOOL/$SERVICEdefine tools as YAML-shaped code with HTTP bindings, andcompile()emits the prompt plus an agent bundle (settings + JSON-Schema tool definitions).npm run tg:run agent.tgruns it live — tool-use loop, bindings, and{env.*}secrets included.
The full language reference is in SPEC.md.
Quickstart
The package ships runnable examples in examples/ — real agents, checked in CI so they can never rot.
Five commands from install to a deployable prompt:
npm i -D typeglish
npx typeglish check node_modules/typeglish/examples/support-agent.tg
npx typeglish check node_modules/typeglish/examples/support-agent-draft.tg
npx typeglish score node_modules/typeglish/examples/support-agent.tg --min B
npx typeglish build node_modules/typeglish/examples/support-agent.tg
npx typeglish test node_modules/typeglish/examples/support-agent.tg --dry
npx typeglish --explain prompt/hedgingThe two support agents are the same agent twice: the draft is hedged prose ("Try to avoid…",
"kind of short") and the linter lights it up; the final commits to modals and scores an A. build
writes the deployable artifact plus a hash manifest; --explain tells you what any finding means and
how to fix it. Then write your own agent.tg and run the same loop.
The repo
src/core/ the language + compiler (pure TS — runs in Node, browser, and tests alike)
typeglish-grammar.ts the grammar (Peggy / PEG)
parser.ts, ast.ts parse a clause → statement AST
ir.ts lower the AST → logical-form IR (directives, conditions, numeric bounds)
layers/ ONE FILE PER SEMANTIC LAYER — assignment, possession, definiteness,
quantity, frequency, deontic, order, redundancy, references, statements,
style, spelling; the registry (layers/index.ts) welds the roster
world.ts the world model: the object graph + aliases + addressee → buildWorld()
diagnostics.ts the orchestrator: build the world once → run every layer's checks
prompt.ts render + PromptDoc (what the model reads; the ablation API)
compile.ts render → semantic XML + the agent bundle
tool.ts $TOOL / $SERVICE definitions (tool schemas + HTTP bindings)
agent.ts the runner: the tool-use loop + HTTP bindings (Node-side, like z3)
solver/z3.ts the SMT consistency checker (deontic + numeric)
predicate.ts, verbs.ts the predicate system: action parser + the curated verb catalog
semantic/ the advisory tier: atoms, NLI engine, judge + distillation, reducer
src/lib/ the published surface — the `typeglish` bin (check / build / score / test / resolve /
reference / mcp / claude-hook; cli-surface.ts is the table --help derives from),
build.ts (artifact + hash manifest + the scaffolding failsafe), deploy-gate.ts,
claude-hook.ts, test-run.ts (the $TEST runner), project.ts (glish.tgc discovery),
`typeglish/monaco`, `typeglish/spell`
src/mcp/ the MCP head — `typeglish mcp`: five stateless tools over stdio, results
byte-shaped like the CLI --json (the LSP pattern, one transport over)
lsp/ the stdio LSP head (editor language services)
plugin/ the Claude Code plugin — skill + PostToolUse check hook + PreToolUse deploy gate
(served by .claude-plugin/marketplace.json at the repo root)
docs/agents/ TYPEGLISH.md — the GENERATED agent language reference (AGENTS.md-style consumers)
examples/ runnable sample agents — shipped in the package, compiler-locked in CI (examples.test.ts)
src/web/ the playground — a Monaco editor that consumes `src/lib` (it's the first consumer).
ONE app, TWO surfaces (hosted.ts): localhost = the dev cockpit (Copilot, Test runs,
the dogfooded copilot.tg seed); any build = the PUBLIC playground (curated seeds,
no server features). brand/ is the Sandcastle design system vendored from
typeglish.dev (npm run sync:brand)
server/ the Node side (LLM calls + the server-side Z3 logic check) as Vite middlewareChanging the language? Every operator, type, character rule, and diagnostic code lives in exactly one
single-source-of-truth module — see the SSOT registry in CLAUDE.md (invariant #1). Edit
the SSOT, run npm run gen:syntax, and let the drift-lock tests tell you what else to update; a hardcoded copy
anywhere else fails CI.
Install
Zero-setup — the CLI has no required runtime dependencies (the Z3 proof tier is an optional
dependency npm fetches by default; --omit=optional skips it), so npx just works:
npx typeglish check prompts/*.tg [--json] # exit 1 on any provable error
npx typeglish build prompts/*.tg # passing files → .typeglish/dist/*.txt artifacts
# + a hash manifest (.typeglish/build-manifest.json)
npx typeglish score prompt.tg --min B # the TG score; exit 1 below the floor (the CI gate)
npx typeglish resolve prompt.tg --vars '{"tier":"premium"}'
npx typeglish --explain logic/contradiction # what any diagnostic code means
npx typeglish mcp # the same tools over MCP stdio (for shell-less agent hosts)build is the deploy contract: the artifact — compile(source).xml — is what a prompt-taking system
(a system prompt, an assistant API, Voiceflow) should receive, and the manifest proves any payload
byte-for-byte ("this exact prompt came out of a passing compile"). A file with a blocking error
refuses to build; nothing failing can enter the manifest.
In a project, npm i -D typeglish gives you the full checker: z3-solver (the SMT proof tier —
contradictions, unsatisfiable bounds, exhaustiveness) is an optionalDependency, so npm installs it
by default and degrades gracefully where it can't. Spell-check stays opt-in:
npm i -D typeglish # full tier out of the box (z3 auto-installs)
npm i -D typeglish --omit=optional # lean install — skip the 33MB z3 WASM
npm i -D nspell dictionary-en dictionary-en-gb # + spell-check (the typeglish/spell module)Without z3-solver, check runs the synchronous tier (still the full language/consistency/structure
check) and says so on stderr — a degraded checker is never silent. Requires Node ≥ 20.19.
Embed in your product (Monaco)
Any product with a Monaco editor gets the full TypeGlish experience — syntax highlighting, the object-record hover, completion, live consistency squiggles, object-reference highlighting, and list/operator authoring — in a couple of calls:
import * as monaco from 'monaco-editor';
import { setupTypeGlish, attachTypeGlish } from 'typeglish/monaco';
import 'typeglish/monaco.css';
setupTypeGlish(monaco); // once per monaco instance
const editor = monaco.editor.create(el, { language: 'typeglish', theme: 'typeglish-dark' });
attachTypeGlish(monaco, editor); // per editor → squiggles, highlights, authoringNeed just the compiler (no editor)? The core runs anywhere — Node or browser:
import { compile } from 'typeglish';
const { diagnostics, consistent, xml } = compile(source);Every rule lives in the framework-agnostic core; typeglish/monaco only wires it to Monaco's
APIs. The playground in src/web/ is the first consumer of exactly this surface.
Develop
npm install
npm run dev # the playground at http://localhost:5273 (VITE_HOSTED=1 previews the public surface)
npm run sync:brand # re-vendor the Sandcastle design tokens + logo from typeglish.dev
npm run typecheck
npm test # ~2,300 unit tests cover the language, checker, score, and semantic kernel
npm run check -- prompt.tg [--json] # compiler errors + warnings, agent-friendly (exit 1 on errors)
npm run score -- prompt.tg [--full] [--min B] # the TG score + ledger (exit 1 below --min: the CI gate)
npm run tg:test -- prompt.tg [--dry] [--min 0.7] # run the suite (inline + sibling .test.tg): per-case pass/fail
npm run fmt -- prompt.tg [--write] # join wrapped fragments, split compound rules
npm run profiles -- prompt.tg [--behavioral --yes] # per-statement attribution (static free; Δ costs, confirmed)
npm run annotate -- prompt.tg [--write|--clean] # write the profile into //@ margin comments
npm run emit -- promptfoo prompt.tg # the file's $TEST suite as a promptfoo config
npm run portability -- prompt.tg --models a,b # cross-model agreement (then --results out.json)
npm run optimize -- prompt.tg --budget 24 [--estimate] [--json] # GEPA; --estimate quotes cost (free), --json = agent-readable result
npm run semantic -- prompt.tg # per-line residual/overlap audit (add --reduce, --actions)
npm run semantic:bench # regenerate the golden-pair benchmark reportThe compiler core has no browser dependencies, so the grammar, the IR, and the solver behave identically in Node, the browser, and Vitest.
Agents in the loop
The compiler talks back to agents at the moment their work SETTLES — the turn boundary — never
mid-typing (half-written text is legitimately broken). The mental model everywhere is TypeScript for
prompts: the .tg is source in git, the deployed prompt is a build artifact, and the gate between
them is mechanical.
In Claude Code, install the plugin and the whole loop runs itself:
/plugin marketplace add Bradenream/TypeGlish /plugin install typeglish@typeglish- A skill teaches the workflow — author
.tg→ check → score → build → deploy the artifact verbatim — with the generated language reference as its deep material. - A PostToolUse hook re-checks every settled
.tgedit and feeds blocking diagnostics straight back to the model as fix-now feedback (advisories ride along as context; clean files stay silent). - A PreToolUse deploy gate hashes outgoing prompt payloads (Voiceflow MCP tools by default;
configurable via
.typeglish/deploy-gate.json) against.typeglish/build-manifest.json: a prompt no passing build produced is denied with the fix in the message, and a verified one still goes through the normal permission flow — the gate never auto-approves. Escape hatches:TYPEGLISH_DEPLOY_GATE=off|ask.
- A skill teaches the workflow — author
In the playground, the Copilot gets a turn-end compiler report: when its edits land, the merged diagnostics are diffed against the turn-start snapshot and reported — as a ⚙ row in the transcript and as compiler truth in the model's history. A turn that introduced errors triggers one automatic fix round (toggleable, never recursive).
Any other agent with a shell (Cursor, a CI bot, …) gets the same truth from
npx typeglish check|score|build --json— data on stdout, diagnostics on stderr, exit codes as gates,typeglish --explain <code>for what any code means. Wire the same loop into any hook system.Shell-less hosts (claude.ai, any MCP client) get the compiler as five MCP tools —
npx -y typeglish mcp— check / build / score / explain / reference, results byte-identical to the CLI's--json. A host with no filesystem passessourceinline;buildreturns the artifact + its sha256 without persisting.Teaching the language:
typeglish referenceprints the generated agent reference (docs/agents/TYPEGLISH.md is the same document, committed for AGENTS.md-style consumers), so an agent that has never seen TypeGlish can learn it in one read.
