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:
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 } }
[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.
Getting sharper
Deeper flow-narrowing and richer validation for generic and imported types are on the roadmap — extending the same guarantee to more of the type system.