01 · The core promise

What stops an agent from silently breaking my code?

A whole class of change that quietly compiles in TypeScript — a missing case, an unhandled error, a renamed field — becomes a compile error the agent has to fix before it can ship.

Why it matters

Agents don't usually break code loudly. They break it confidently: they add a state, drop a branch, or rename a field, everything still type-checks, and the bug surfaces in production. The expensive failures are the ones that compiled and were wrong.

Glyph closes that gap by turning “you probably handled this” into “the compiler proved you did.” Tagged unions are sealed, so match must cover every case. Errors are values you have to unwrap, not exceptions that vanish. And every type carries a runtime descriptor, so data crossing a boundary is checked against its shape.

See it

Add a state to a union, and every place that handled the old set stops compiling — pointing at the exact spot to update:

status.glyph — one new variant, and the compiler finds every gap
type Status = Loading | Ready | Failed(string)   // + Cancelled

fn label(s: Status) -> string {
  return match s {
    Loading => "…",
    Ready => "done",
    Failed(msg) => msg,
    // no arm for Cancelled ⇒ this match no longer compiles
  }
}
glyph build
[E0200] non-exhaustive match on `Status`: missing variant `Cancelled`
   ╰─ add an arm for `Cancelled`, or an `else` arm to catch the rest

The equivalent TypeScript switch compiles clean and returns undefined at runtime. Same for errors: a function that returns Result<T, E> is meant to be handled with match or propagated with ? (and the compiler checks the error type lines up). And if you drop a Result on the floor — call it as a statement and ignore what it returned — Glyph warns you (E0217), because a discarded error is a swallowed failure. You silence it deliberately with let _ = ..., never by accident.

Exhaustiveness isn't just for unions. A match on a number or string with only literal arms can't cover an unbounded domain, so Glyph requires an else (E0218) rather than letting the uncovered value fall through to a runtime throw. The rule is the same everywhere: a match that doesn't account for a case doesn't compile.

The same "make it type-level, not convention" instinct covers PII. Put @redact fields: [ssn, dob] on a type and the compiler generates a redact() on its descriptor that masks those fields, so json.stringify(User.redact(u)) can't leak them. Name a field the type doesn't have and it's a compile error (E0219), not a redaction that silently does nothing.

Where it stands

Shipping today

Exhaustive match over sealed unions and over number/string (E0218), Result + the ? operator with error-type checking, a warning when a Result is discarded (E0217), @redact PII masking on the type descriptor, runtime type descriptors, and @example tests that run at build time.