Skip to main content
mz is the compiler that implements the Mzizi language: toolchain, not the language itself. Today it is the Phase 0 front end: lex → parse → resolve → lower → IR, plus a contract evaluator (“lower” here means into the IR for a component). Two things lower to Rust: a service, which mz build turns into a local Rust + axum package, and a program, which mz build and mz run turn into a Rust package with no dependencies. No component lowers to Rust yet. It is the centre of the toolchain. It is a single Rust binary with no dependencies at all, which is a deliberate house convention rather than an oversight — a compiler whose check loop must stay sub-second on a modest laptop should not start life with a dependency tree to build first, and the small things it needs (JSON emission, SHA-256) are fully specified and therefore hand-rollable with verifiable correctness.

Getting it

There is no release, no published crate, and no installer. The crate is version = "0.0.0" with publish = false.
Run cargo test --workspace from the repository root for the whole suite: 765 tests in 29 suites at 2b36c8f, 634 of them in the compiler crate and the rest in the benchmark harness, runner and probe crate. compiler/src is 33,540 lines. All 765 pass there, and the eight example programs run through mz run against their .expected files.

The commands

With no command given, the first positional argument is treated as a file and check is assumed.
Documented elsewhere, not implemented here. RFC-0003 §5 lists mz refs, mz path, mz patch and mz diff under “designed here, next in implementation order”. They are not built, and neither are mz test, mz fmt, mz run --agent or mz harness plugins (what still has to be built). The binary dispatches exactly the commands in the table above: check, fix, contract, outline, build and run, plus hash and ir for the IR and harness. It dispatches on the language harness’s command table, and its usage line is generated from it. Asking for anything else prints the usage line and exits 2. mz outline, mz hash and mz ir refuse a service or a program with exit status 2, because neither has an IR yet.

Exit status is a contract

So the loop can branch on status without parsing output: mz run is the exception, because 1 belongs to the program it runs: it exits with the program’s own status. See its table.

mz check --agent: the protocol

This is the surface an agent should use. RFC-0001 §4 states it as a versioned contract, and four properties define it.
1

Whole-program, all at once, deterministic order

Statement-per-line plus end-anchored blocks let the parser resynchronise at every line. The recovery target is at most one diagnostic per true author error — never a cascade, and never “fix one to see the next”. That is the direct answer to FM-5: a compiler that stops at the first parse error turns one mistake into five agent turns.
2

NDJSON, one diagnostic per line

Each line is a complete JSON object carrying code, severity, file, span as [start_line, start_col, end_line, end_col], say, and an optional fix.
3

say is written for a reader with zero file context

It quotes the offending source inline, so the agent needn’t re-read the file to understand the error. The stated target is ≤ 200 characters — density is the budget.
4

Fixes are data

Every diagnostic carries a machine-applicable fix when one is unambiguous, tagged exact, guess or none. mz fix applies every exact fix in one pass, which takes a whole class of mechanical error out of the loop.
The shape RFC-0001 specifies:
The emitter in compiler/src/diagnostic.rs writes the same fields with severity added and confidence nested inside the fix object:
Every run ends with a summary line, so a consumer always has a terminator:
The human-readable mode ends with the same information as prose: mz: 3 errors (2 exact-fixable), 480ms.

Diagnostic codes

Codes are grouped by phase, and the ranges are stable: Two are worth quoting because they show what the messages are trying to be. MZ0402, when something that cannot start a view line does:
It names the whole set of valid continuations rather than only what was wrong — the Elm-derived expected-vs-found discipline RFC-0002 §3 names as the most valuable borrow in compiler-error UX, on the grounds that a small model cannot infer from an indirect hint. And MZ0501, which is a warning rather than an error, and is how RFC-0001 §1.6’s rule (“a component without a contract block compiles with a warning”) is actually enforced:
The end-echo diagnostics (MZ0206–MZ0208) are the payoff for the name echo: they can say which block on which line an end actually closes, and hand back the exact text to write instead. Each fix rewrites the whole closer, not only the end word.

Codes added after pilot 2

Pilot 2’s 7B model brought React habits the compiler did not name, and several view mistakes gave a cascade instead of one error on the right line. These codes are the fixes, audited in benchmarks/READINESS.md. Each message below is quoted from compiler/src/lex.rs or compiler/src/parse.rs. MZ0313 quotes both numbers, so the agent needn’t work out the scale itself:

Program codes (MZ09xx)

A program is checked with the codes RFC-0013 §16 reserves, MZ09xx (53 of them), and reuses 27 shared codes (MZ0101–MZ0106, MZ0204–MZ0208, MZ0301–MZ0303, MZ0306, MZ0310, MZ0407, MZ0411, MZ0701, MZ0704, MZ0707, MZ0708, MZ0710, MZ0711, MZ0712, MZ0714 and MZ0808). MZ0301, MZ0302, MZ0303 and MZ0704 were registered in the language harness in the second release of 8 October 2026 (mzizi#96), which moved them off the pending list. MZ0960, MZ0961, MZ0963 and MZ0964 are new in the third release of 8 October 2026 (mzizi#99, collections). No component or service diagnostic changed when programs landed. The fourth release, tagged v0.8.0, adds no code: MZ0915 also reports a literal empty split separator, MZ0919 no longer lists text’s methods, which are built, and MZ0962 reports int(s) and float(s) on text with a guess to parse_int and parse_float. This table is generated from the language harness, not written by hand: it is mz harness definition at language main 2b36c8f (tagged v0.12.0), each MZ09xx entry’s severity, the tool that reports it, its say text and the fix kinds it is recorded as raising. “Or none” means some reports of that code carry no fix. The say text is the entry’s own, which the harness writes by hand and does not compare with the checker; mz harness entry <code> prints an entry with the source that triggers it. MZ0990–MZ0992 come from mz run, and MZ0991 and MZ0992 are printed by the running program on standard error.

Deep nesting is one diagnostic

Blocks nest at most 64 deep. The first block past that depth is the error MZ0411, reported once per file, and the compiler skips every such block without recursing into it. Before the cap, 5,000 nested view elements or handler blocks made mz abort with a stack overflow. compiler/tests/robustness.rs runs check, contract, fix, outline, the IR and service lowering over seeded random edits, random tokens, random bytes, nesting up to 10,000 deep and 1 MiB lines, and fails on a panic or a hang. The deepest .mz file in the repository nests 9 levels. A program has a tighter budget: its blocks and expressions nest at most 32 deep together, and an expression’s tree of binary operators at most 64, because a level of a program cost 12 to 16 KiB of stack in a debug build. Past that is the same single MZ0411.

mz fix: apply every exact fix

mz fix <file.mz> checks the file, applies every exact fix to it in place, then checks the result again. The output and the exit status describe the file as it now is.
  • One pass. It applies the exact fixes the first check reported, once. It does not loop until nothing changes. The second check says what is left.
  • guess fixes are never applied. Where two exact fixes would overlap, the checker keeps one as exact and demotes the other to guess, so the edits never collide. A demoted fix may come back as exact on the next run.
  • --agent works here too. mz fix --agent <file.mz> prints the second check as NDJSON, exactly as mz check --agent would.
  • The exit status is the second check’s: 0 with no errors left, 1 with errors left, 2 if the file could not be read or written.
RFC-0001 §4.3 described this command before it existed. It landed after pilot 2, whose 7B model resubmitted a byte-identical file in 15 of 16 Mzizi repair attempts. READINESS.md records that applying mz fix to any Mzizi candidate from either pilot never raised its error count. Whether it helps an agent converge is for the benchmark to measure, and it has not been measured.

mz contract: does it do what it says

check and contract are separate on purpose. The charter measures two different things: “a syntax/compile error is not itself a defect for this metric… the defect rate measures what gets past the compiler wrong.” One exit code covering both would make the Phase 0 defect metric unreadable. mz contract <file> evaluates the component’s own contract block against its own declarations: enum variants and their data columns, defaults, slots and the view. A failed clause exits 1, exactly as a compile error does, so a harness can branch on status alone. Across the nine primitives there are 29 clauses, and CI evaluates every one.
What it does not do: it checks nothing rendered, and it does not compare a component with a reference implementation. That comparison is the benchmark harness’s job (benchmarks/harness). For a service, mz contract runs it in process (RFC-0011 §6–§7). Each example clause is one request, and each ensure clause is tested over a generated set of requests: tested, not proven. Over the example service:
A size’s height is now read from its class. Pilot 2’s 7B buttons wrote icon class "size-14" height 48: the class renders 56px, the contract read the 48, and mz contract passed. Now a variant row may leave height out, and the evaluator takes the height its first plain h-N or size-N class token renders (N × 4 pixels). A row that writes a height which disagrees with its class fails mz check with MZ0313. The rule is the one the benchmark harness scores with, and both test suites check it on the same examples. Tokens with a prefix or an arbitrary value, such as md:h-10 or h-[56px], are not read. See the pilot results.

mz build: lower a service or a program

mz build <file.mz> --out <dir> lowers a service to Rust (RFC-0011 §8), or a program to a Rust package with no dependencies (RFC-0013 §14), the package mz run builds. It refuses a component (exit 2) and a file with errors (exit 1).
The output is an ordinary local Cargo package: the runtime (RFC-0011 §6 as one Rust function), the generated route table and handlers, and one #[tokio::test] per example. It pins axum =0.8.9 and tokio =1.53.1. CI’s lowering job builds the example service, runs its 18 generated tests, starts the server and sends a request over a socket. What it is not: there is no Workers, Containers or WebAssembly target, and no component lowering. A generated service package runs locally as a native server. See what still has to be built.

mz run: run a program

mz run [--release] <file.mz> checks a program, lowers it to a Cargo package with no dependencies in a per-user cache directory, builds it with cargo build --offline --quiet, and runs it with the terminal’s standard input, output and error. Standard output belongs to the program; everything mz run writes of its own goes to standard error.
It exits with the program’s own status, so 1 does not mean “errors” here: CI’s lowering job runs every example program, examples/hello.mz, fib.mz, numbers.mz, control.mz and errors.mz, through mz run and compares its output with the committed .expected file. mz run --agent is not built and exits 2. See Programs.

mz outline: the representation for dependencies

When editing component A that uses B, an agent needs B’s interface, not B’s body. Reading the file gets both, plus comments and unrelated functions — RFC-0003’s barrier RB-2. The outline carries: the first doc line, the component name, declared capabilities, every prop with its type and default, every enum with its variant names only, the names of fn declarations, and a marker for whether a contract exists. It drops variant column data, view bodies and function bodies — which is where the bulk of a component’s bytes are. It is emitted as valid Mzizi, deliberately: a reader who can read the language can already read this, so there is no second format to learn and no second parser to keep in step. Measured over the nine primitives and the two component examples, the worst case is 38% of source (spinner.mz); the test in compiler/tests/ir_measured.rs fails above 75%.

Compile speed is a protocol property

RFC-0001 §4.6 makes this explicit: “A slow compiler fails Phase 0 no matter how good its errors are.” The stated budget is sub-second incremental for a single-component change, and the benchmark is specified to record it per iteration. What has been measured so far is much narrower: parse and lower of all eleven component .mz files in the repository takes about 10 ms in a debug build, against a 400 ms test budget. That is a floor on a tiny corpus, not the incremental-compile figure the charter asks for. See Status.

Paths in the agent output

mz check --agent prints each diagnostic’s file exactly as it was given. In pilot 2 the benchmark runner passed absolute paths, so the 7B model read about 13,500 tokens of path strings. That was the first fix pilot 2 listed, and it is fixed in the runner rather than the compiler: the runner’s default file-name normaliser replaces the candidate’s path with its bare file name before the model sees it, for every arm. If you drive mz from your own loop, pass a relative path.