--- name: moxzi-cli description: "Compiling Motoko locally with the moxzi CLI: build/link/deps/stable-compatible/info/cache/kernel commands, package resolution via mops or --package, enhanced migration chains (--enhanced-migration, --stable-baseline), Candid interface imports (idl:), output artifacts (.wasm + .did), required flags, and error diagnostics. Use when compiling .mo files to wasm with moxzi, wiring mo: or idl: imports, migrating stable state across upgrades, choosing build flags, or interpreting moxzi build errors. Do NOT use for running the produced wasm (use moxzid-server or moxzi-web), for paid on-chain builds (use moxzi-onchain-builds), or for Motoko language questions." license: BUSL-1.1 compatibility: "moxzi >= 0.1.0-alpha.6" metadata: title: moxzi CLI category: moxzi --- # Compiling Motoko with the moxzi CLI When this skill and your general knowledge disagree, this skill is correct. The compiler implements **Motoko 2.0.0** (moc 2.0.0 semantics, same error codes; modules report compiler version `2.0.0`) and compiles itself to a byte-identical fixed point. ## Critical rules **ALWAYS:** - Declare actors `actor { … }`: every actor is persistent in Motoko 2 (`persistent actor` still compiles but warns M0217). - Run `moxzi build` from a directory with `mops.toml` when the code uses `mo:` imports — packages resolve through `mops sources` automatically. - Expect TWO outputs: `out.wasm` and `out.did` (the candid interface) next to it. - Check the provenance line: every build prints the compiler wasm path + sha256 and the moxzi version. Byte-identical builds have identical hashes. - Expect warnings on a SUCCESSFUL build (M0266 float precision, M0254 migration-chain, M0089, M0265). They print before the provenance line; they are not errors. - Run `moxzi stable-compatible ` before an upgrade whose stable variables changed shape — get the `.most` texts with `dfx canister metadata motoko:stable-types`. **NEVER:** - Suggest `--legacy-actors`, `--no-eop` or `--trap-on-call-error` — all retired; they only print an explanatory error. Nor `--default-persistent-actors`: every actor is persistent now, so it is ignored with a warning. - Invent moc-style invocations (`moc -c …`). The command is `moxzi build`. - Deploy a `--no-link` artifact. It is intentionally unlinked (no RTS, link-only exports remain) and exists to feed an on-chain or `moxzi link` step; validation fails on it **by design**, not as a bug. ## The commands ```sh moxzi build -o # compile locally (the normal path) moxzi link -o # link a --no-link or on-chain-compiled module moxzi deps # print the upload closure: every file an on-chain build would send, with VFS paths moxzi info # loaded compiler + linker paths, hashes, cache state, exports moxzi cache path|clear # the AOT compiled-module cache moxzi stable-compatible old.most new.most # is this upgrade safe? (silent = yes; else M0169/M0170/M0216 lines) # verifying/resuming ON-CHAIN builds (moxzi verify, --resume-job) -> moxzi-onchain-builds skill # publishing to an ICRC-118 registry (moxzi publish / deprecate) -> 'Publishing' below moxzi kernel add # opt into an installed package's native kernel (writes the [kernels] sha256 pin) moxzi kernel build -o .kernel.toml # Rust crate -> reproducible kernel file moxzi kernel verify # rebuild from declared source, MATCH/DIFF per pin moxzi kernel sdk # write the moxzi-kernel SDK (for cargo generate-lockfile / editors) # all kernel work -> the moxzi-kernels skill ``` ## Hello world (complete, works as-is) ```sh cat > hello.mo <<'EOF' actor { public query func greet(name : Text) : async Text { "Hello, " # name # "!" }; }; EOF moxzi build hello.mo -o hello.wasm ``` Expected output shape: ``` hello.mo -> hello.wasm + hello.did (220356 bytes, 1 file(s), 59 steps, 0.7s) compiled by ./compiler.wasm (sha256 7f7b48…), moxzi 0.1.0-alpha.5 ``` `hello.wasm` deploys to the IC as-is (`dfx canister install --wasm hello.wasm`), runs under `moxzid`, and runs in a browser via `moxzi-web`. ## Toolchain resolution The compiler and linker are wasm files, found in this order: 1. `--compiler ` / `--linker ` flags 2. `MOXZI_COMPILER` / `MOXZI_LINKER` environment variables 3. the search path (a release tarball ships `compiler.wasm` + `linker.wasm` beside the binary) `moxzi info` reports what was actually loaded, with hashes. ## Package resolution ```sh # preferred: a mops project — nothing to pass cd my-project # has mops.toml moxzi build src/main.mo -o main.wasm # explicit: like moc --package, repeatable moxzi build main.mo -o main.wasm --package base /path/to/base/src ``` `--root ` sets what `/src` maps to (default: nearest ancestor with `mops.toml` or `dfx.json`, else the entry file's directory). Error paths are reported under `/src`. A Candid interface imports as a module: `import C "idl:path/service.did"` binds every type the file declares (PascalCased) plus `Self`, the service's actor type — annotate an `actor ("…")` reference with `C.Self` to call a canister you have no source for. ## Publishing to an ICRC-118 registry ```sh moxzi publish out/Main.wasm --registry l55bi-3aaaa-aaaai-atk4a-cai \ --namespace com.example.myapp --version 1.0.0 \ --canister xcn6l-iqaaa-aaaai-raq6q-cai # the compiler that built it: carries provenance [--previous 0.9.0] [--repo URL --commit SHA] [--create] [--allow-offchain] [--dry-run] moxzi deprecate --registry --namespace --version 1.0.0 --reason "…" # --undo to clear ``` - Publication is OPEN but gated twice. **Namespace:** `com.example.myapp` needs `com.example` (or a deeper prefix) claimed by the publisher at the ICRC-86 domain registry (`qnodb-fiaaa-aaaao-qqdwq-cai`; TXT record `_icrc86.`). **Storage:** every chunk is charged to the publisher's byte cap (default 0); storage is bought, 30 XDR per 70 MB for three years. Both are done on the registry UI at https://registry.astroflora.xyz/publish-access; a refusal names the exact step. - After the upload the CLI asks the registry to PULL the compiler's attestation (keyed by the LINKED sha256) and prints the verdict: `verified` = built on-chain by that compiler; `unverified` = the compiler never linked these bytes (a local build publishes fine and is simply unverified); `DIVERGENT` = an open divergence report outranks every attestation. The CLI never asserts provenance itself; it only reports what the registry pulled. - A `--compact-rts` artifact is REFUSED (off-chain runtime build) unless `--allow-offchain`. - `--previous` must name a version the registry already holds; the CLI refuses to publish a root by accident. `--commit` is only auto-filled from a clean working tree. ## Flags that matter | Flag | Use | |---|---| | `--debug` | debug build | | `--no-idl` | skip writing the `.did` | | `--no-link` | stop before linking (feeds `moxzi link` or an on-chain link) | | `--timings` | per-phase timing | | `-v` | forward the compiler's own debug output | | `--stack-budget ` | scheduler yield ceiling; 0 = canister default (200). Off-chain, raising it is the difference between minutes and hours on large projects — yields are checkpoints, output bytes do not change | | `--force-gc` | collect at every message boundary; caps off-chain heap growth at instruction cost (fine locally) | | `--no-index-fast-path` | turn OFF the checked array/blob index optimization, which is **on by default** since alpha.6. The reference configuration: the same checked indexing `moc` emits | | `--index-fast-path` | explicitly enable the default (compatibility flag) | | `--wasm-tail-call` | emit wasm `return_call`/`return_call_indirect` in tail positions. Opt-in; a moxzi extension, so output is not byte-identical to `moc` | | `--candid-fast-path` | constant-fold the Candid type-table header when known at compile time. Opt-in; also an extension | | `--stable-regions`, `--max-stable-pages `, `--stabilization-instruction-limit `, `--measure-rts-stack` | moc-compatible passthroughs | | `--enhanced-migration ` | migration-chain mode: every `*.mo` in DIR is a link (filename order), each exporting `public func migration(old : {…}) : {…}`. Stable vars then declare types WITHOUT initializers — values come through the chain; an initializer is M0250. Upgrades fast-forward past links already applied | | `--stable-baseline ` | the deployed canister's own `.most` signature. Turns "the chain doesn't produce field X" from a warning (M0254) into an error (M0267), plus the ordinary upgrade verdicts (M0169/M0170/M0216). The check resumes at the migration the baseline records as applied — fields a past migration dropped are NOT demanded back | | `--public-metadata ` / `--omit-metadata ` | metadata visibility (names: candid:service, candid:args, motoko:stable-types, motoko:compiler). Default is dfx's (candid:* public); passing --public-metadata REPLACES that default | | `--remote --canister ` | build on a compiler canister instead — see the `moxzi-onchain-builds` skill | | `--accept-kernel =sha256:` | accept a third-party native kernel by its exact bytes (repeatable; recorded per user; `--yes` does NOT accept kernels) — see `moxzi-kernels` | | `--link-locally` | with `--remote` and kernels: link on this machine if the canister can't (no linked-hash attestation) | ## actor* (alpha.6): many addressable actors in one canister **`actor*` IS BEHIND ONE FLAG**, and that is the first thing to check when an `actor*` program will not compile: ```sh moxzi build app.mo --experimental-local-actors 256 -o app.wasm ``` `--experimental-local-actors ` reserves N local actor slots and is required for ANY `actor*`. A long sequential `await*` chain over thousands of children also wants `--experimental-private-gc` on every target, the replica included. **An `actor*` is persistent by default, as an `actor` is** (since alpha.9; alpha.6 to alpha.8 needed `--experimental-local-actor-persistence`, which is now ignored with a warning). Its state, its identity and the handles to it survive an upgrade of the actor that holds it. `transient actor*` opts one out: ```motoko actor { actor* class Counter(initial : Nat) { // kept across upgrades var n = initial; public func add() : async* Nat { n += 1; n }; }; transient actor* class Scratch() { // starts over on every upgrade var hits = 0; public func hit() : async* Nat { hits += 1; hits }; }; let counter = Counter(0); // hold persistent instances in STABLE fields transient var scratch = Scratch(); // rebuilt by each upgrade transient let probe = transient actor* { // inline form public func ping() : async* Text { "pong" }; }; }; ``` - A handle to a `transient actor*` kept in a stable field outlives the instance: after an upgrade, calling it traps "local actor retired". - A persistent instance kept only in a `transient` field is constructed again by every upgrade while the old one keeps its slot; enough upgrades and the upgrade itself traps "local actor capacity exceeded". Put persistent instances in stable fields; the compiler warns (`M0199 … transient field \`c\` constructs a persistent actor*`) when one is constructed straight into a `transient` field. - `persistent actor*` is redundant: warning M0217, as for `persistent actor`. - A persistent `actor*` class is found again after an upgrade by its NAME (its declaration path in its module; a library's classes are qualified by the package), so moving or renaming the file is fine. To rename the class, give it an id first, upgrade, then rename: `public (with id = "counter") actor* class Counter …` — the id must be a text literal and is the class's identity from then on. Two persistent classes with the same name in one program are refused until one has an id. An id also lets an anonymous `(with id = "worker") actor* { … }` persist. Dropping an id is a different class: the upgrade is refused as memory-incompatible, with the state intact. No package import is needed: the runtime is injected by the compiler (you will see `moxzi:compiler-runtime` in diagnostics). `mo:local-actors/Actor` is only for the `Actor.id` helper. `moc` cannot compile these programs at all. **Publishing a library that uses `actor*`** — say so in its `mops.toml`, and consumers never type the flags: ```toml [moxzi] version = ">=0.1.0-alpha.7" # semver; pin a pre-release while in alpha features = ["local-actors"] # what it needs, not how much ``` `mops.toml` ships inside the published package and the registry never parses a `[moxzi]` table (NOT `[requirements]`: the registry refuses any name but `moc`/`lintoko` there). A consumer's `moxzi build` reads every dependency's `[moxzi]` table (the flat `.mops/` tree and local path deps), refuses with the package named if the version does not match or a feature is unknown, and adds the flag with a `note:`. (The old `local-actor-persistence` feature still resolves and adds nothing: persistence is the default.) The root project's `[moc] args` (what mops passes to `moc`) is applied too and wins over a feature's default — that is where the slot count goes: `[moc] args = ["--experimental-local-actors", "4096"]`. A typed flag always wins. `moxzi deps Main.mo` prints the resolved `reqs :` table. `MOXZI_NO_MOPS_ARGS=1` turns the splice off and says so. A project that declares nothing gets byte-identical output. Convention and diagnostics: `docs/mops-requirements.md`. **Running a package's own `mops test` against moxzi:** `[toolchain] moc = "/abs/path/to/moxzi/scripts/moc-shim"` (a value starting with `/` is a path) plus `pocket-ic = "10.0.0"` (mops ≥3 requires a pocket-ic pin ≥9.0.0), then `MOXZI_BIN=… mops test --mode replica`. The same `[toolchain] moc` pin serves `mops build`/`check`/`watch` (mops 3 removed `DFX_MOC_PATH`). `-r` and `-wasi-system-api` modes are refused by name. Verified on ic-mops 2.13.2 and 3.3.0. What the CLI does with it: ```motoko public actor* class Counter(creator : Principal, initial : Nat) = me { var n = initial; public func bump(a : Nat) : async* Nat { n += a; n }; // internal: await*, no message public shared(msg) func add(a : Nat) : async Nat { // external: a real message if (msg.caller != creator) throw Error.reject("nope"); await* bump(a) }; public query func read() : async Nat { n }; }; var counters : [Counter] = []; // ordinary values; they outlive an upgrade ``` - The emitted `.did` gains container façades `Counter_add(id, …)` / `Counter_read(id, …)`, so dfx and agents reach a subactor by passing its principal. `--no-idl` skips this. - `Actor.id(c)` is the subactor's principal. Store it, hand it out, turn it back into a typed reference later. Stale and retired ids fail closed rather than hitting a reused slot. - An `actor* class` survives upgrades with state AND identity. Adding a method is free. Changing a stored variable's TYPE requires a per-class migration: `public (with migration = M.run) actor* class …`. - The compiler emits a small client module too, so a calling canister writes `Counter(service, id).add(5)` instead of spelling the façade by hand. ## Native kernels (alpha.10) A package may back Motoko functions with compiled Rust. The build links a kernel ONLY when the application's own `mops.toml` pins it: ```sh mops add moxzi-hash && mops install moxzi kernel add moxzi-hash # writes [kernels] moxzi-hash = "sha256:…" moxzi build main.mo -o app.wasm # "kernel: moxzi-hash sha256:… […] [tier-1]" ``` Unlisted kernels print `note: package X provides a native kernel; it runs its Motoko code unless you opt in…` and the Motoko body runs. A changed kernel stops the build with `the installed kernel is sha256:A, but your [kernels] pin is sha256:B` (review, then rerun `kernel add`). Third-party kernels stop the first build until accepted. Everything else (writing kernels, ABI, errors, constant time) -> `moxzi-kernels`. ## Error diagnostics Everything is source-located (`/src/file.mo:line.col-line.col`) with moc's error codes. | Symptom | Meaning | Fix | |---|---|---| | ``[M0217] redundant `persistent` keyword`` (warning) | `persistent actor` | drop `persistent`: every actor is persistent in Motoko 2 | | `[M0131] variable … is declared stable but has non-stable type` | a field of an actor holds a function, a mutable object, … | mark it `transient` | | `local lowering error [M0199], an actor* that survives upgrades needs a name to be found again …` | an anonymous `actor* { … }` (e.g. returned from a function) | make it an `actor* class`, bind it with `let`, or write `transient actor*` | | `local lowering error [M0199], non-persistent data field: f …` | a persistent `actor*` field holds something that cannot persist (a `var` holding a function) | mark the field `transient`, or declare the actor* `transient actor*` | | `expected 'actor*' after 'transient' (a declaration is transient only as a field)` | `transient` on a declaration inside a block | `transient` belongs on fields, and on `actor*` | | "local actor retired" at run time after an upgrade | a stable field holds a handle to a `transient actor*` | construct a new one after the upgrade, or drop `transient` from the class | | `[M0020] unresolved import mo:foo/Lib` | package not resolvable | run in a mops project, or add `--package foo ` | | `[M0050] literal of type Text does not have expected type Nat` | ordinary type error | fix the code; codes match moc's | | `expression nested too deeply (limit 128)` / `type nested too deeply (limit 128)` | machine-generated deep nesting hit the parser depth guard | chunk generated expressions/types | | entry is `module { … }` | not an error — a library build is a valid build | expected | | `[M0250] … cannot have an initializer` | stable var initialized under `--enhanced-migration` | delete the initializer; produce the value from a migration file | | `[M0267] … requires field X … write a migration that produces it` | the chain (checked against `--stable-baseline`) does not explain a stable field | add a migration file producing it, or fix the baseline | | `[M0004] file … does not define a service` | `idl:` import of a `.did` with no `service` | import a file that declares one | | validate fails on a `--no-link` artifact | it is unlinked BY DESIGN | run `moxzi link` first | ## Quick reference - Build: `moxzi build entry.mo -o out.wasm` → `out.wasm` + `out.did` - Toolchain: `--compiler`/`--linker`, else `MOXZI_COMPILER`/`MOXZI_LINKER`, else search path - Packages: mops project auto-resolves; else `--package NAME PATH` (repeatable) - Every actor is persistent (Motoko 2); `persistent actor` still compiles, with a redundant-keyword warning - Related skills: run the artifact → `moxzid-server` or `moxzi-web`; trustless build → `moxzi-onchain-builds`; native kernels → `moxzi-kernels`