muro-lang.dev / docs
For agents
In brief: two jobs. Do not mix them. This page is the operational contract. Humans can read it. You must.
Job A — write a Muro program
Do
-
Write a
.murofile against Grammar. -
Put new programs in
examples/unless asked otherwise. -
Check with
mix muro.check path.muro. If the answer isout of fuel, the checker gave up reducing; it is not a type error. Retry withmix muro.check --fuel 20000 path.muro. -
Read every error. The checker reports each definition that fails, in book order, separated by a blank line. Parse and check errors start with
line:col. Conversion prints surface types (Fin n, not#0). A?printsexpectedandcontext; fill it. -
Copy syntax from
examples/, not from memory. - Keep every binder typed. Write every motive.
-
pred,plus,inj-suc,plus_suc,sym, andcongare already in scope (lib/muro/prelude.muro). Defining one of those names replaces it, and drops any prelude definition that refers to it. -
Use only tags
run,run internal,spec,evidence. -
Write a library for many environments with
paramentries, and check it through a file that supplies them withdefordataof the same names and tags (Params).mix muro.checkon the library itself names the params it is missing. -
Leave
?only while a term is unfinished. A hole always fails and is not in the finished book.
Do not
-
Edit
agda/. -
Edit
lib/muro/*.ex. -
Edit
lib/mix/tasks/. - Add a third representation (no raw HOAS as inductive syntax).
- Invent tags, implicits, metavariables, or tactics.
- Promote a spec to evidence or evidence to run.
-
Treat this manual as ExDoc. Elixir API docs are
@moduledoc/mix docs.
A definition is in the book as soon as Muro.Parser.parse/1 returns it. The checker sees the whole book. Forward references are allowed. Emit keeps run only.
Skeleton:
-- comments start with --
def plus : run Π (n : Nat) → Π (m : Nat) → Nat :=
λ (n : Nat) → λ (m : Nat) →
match n motive (λ _ → Nat)
| 0 => m
| suc np => suc(plus np m)mix muro.check examples/your_file.muroEmit (after check):
{:ok, src} = Muro.emit_file("examples/your_file.muro", Foo)
IO.puts(src)Job B — change the type theory
Read Extending the kernel. Order is mandatory: Agda syntax → subst → check → make agda → Elixir mirror → mix test and mix muro.check.
Users still only run Mix. If you are doing Job A, you are not doing Job B.
Authority
| Question | Answer |
|---|---|
| What is the language? | This manual, especially Grammar |
| What does the parser accept? |
lib/muro/parser.ex |
| What does the checker accept? |
lib/muro/check.ex, specified by agda/Muro/Check.agda |
Is Agda a certificate for a .muro file? |
No. mix muro.check is. |
| Canonical book? |
examples/half_ok.muro = Muro.Example.book/0 = agda/Muro/Example.agda |
File map
manual/ this book (muro-lang.dev). Not ExDoc.
examples/*.muro programs
examples/cshrl/ CSHRL: lib/ on params, envs/ supply them
lib/muro/parser.ex .muro → named FOAS
lib/muro/ast.ex named FOAS, to_db
lib/muro/print.ex surface syntax, locations, hole messages
lib/muro/subst.ex de Bruijn subst
lib/muro/check.ex Elixir mirror of ⊢
lib/muro/session.ex session / conform, mirror of decide and finishes; `rec X. S` unfolds one action and a repeated head conforms
lib/muro/talk.ex one description of an exchange, mirror of seen
lib/muro/choreo.ex a global type projected to each role pair, mirror of project
lib/muro/place.ex place / scheme / move, mirror of decide
lib/muro/prelude.ex book behind every checked file
lib/muro/prelude.muro pred, plus, inj-suc, plus_suc, sym, cong
lib/muro/load.ex import: a relative path, pkg:, and std:
lib/muro/std.ex the ordinary library, embedded at compile time
lib/muro/std/*.muro bool, le, list, maybe, ord
lib/muro/emit.ex run → Elixir source
lib/muro/repr.ex a repr: check the host shape and emit decode/1, decode/2, and encode/2. Not a kernel term.
lib/muro/emit/exunit.ex closed equations → one ExUnit module (`--backend exunit`, `kind: :exunit`)
lib/muro/emit/otp.ex a conformance, and a reading, is a GenServer; a meeting starts the two processes as each other's peer; a `rec` is a tail call on `drive` (`--backend otp`)
lib/muro/emit/fsm.ex a session endpoint is a state machine; the transport stays with the host (`--backend fsm`, `kind: :fsm`)
lib/muro/emit/node.ex a cross, and a cross reading, is one call and one monitor (`--node`)
lib/muro/example.ex same book as Agda
lib/muro/mutate.ex mutants of run definitions (`mix muro.mutate`)
lib/mix/tasks/muro.check.ex
lib/mix/tasks/muro.mutate.ex
agda/Muro.agda public re-export
agda/Muro/Syntax.agda Tm n (de Bruijn)
agda/Muro/Subst.agda wk, sub, inst
agda/Muro/SubstLemmas.agda renaming / substitution algebra
agda/Muro/Env.agda Sig, Ctx, uses, mode order (shared by Check and ⊢)
agda/Muro/Check.agda the decision procedure: structural on the term, fuel only where it reduces
agda/Muro/Session.agda affine session judgment; a payload is a nat, a bit, unit, a pair, a record, a vector whose length is a received nat, a nat at most a bound the sender owes evidence for, or a non-indexed declared data type; a received bit selects one of two arms; a name is a session declared earlier, opened on the declaration prefix with no fuel; a parameter is a nat; %1 must finish; decide searches, skel is the OTP actions
agda/Muro/Rec.agda equi-recursive `rec X. S` unfolded to a guarded coinductive protocol (`--safe --guardedness`); a guarded self-call of a parameterised session is the same coinduction (`stream`); `dual∞`, conformance, and meet are bisimulations; a declared data payload inside `rec` meets when `sent` holds; `Finishes` stays finite, so a jump is not `%1`. `agda/Muro.agda` imports it, so that file carries the same pragma
agda/Muro/Dual.agda the other end of a session; send swaps with recv, a dependent receive swaps with a dependent send, an evidence send swaps with an evidence receive, an indexed bit keeps its index and each arm dualises, and the payload type stays; `session name := dual name` follows it
agda/Muro/Meet.agda two processes on one channel; a send meets a receive of the same payload type and the value inhabits it; a received nat instantiates a vector length; an evidence payload meets as a nat; a literal bit meets the arm of that bit; a variable meets a receive at its innermost binding; `meet name : left with right` follows it
agda/Muro/Talk.agda one description of an exchange; say names one payload and one continuation; `session name := seen party with party in talk` follows it
agda/Muro/Choreo.agda a global type; `g ↾ r / s ≡ b` is the binary session of one role with another; `project-sound`, `project-complete`, and `pairwise-dual`; `--safe`; not a Tm
agda/Muro/Seated.agda a party of a description sits at a place; `seat name : party at place in talk` follows it; the same place stays; two places cross once
agda/Muro/Held.agda a meeting at the parties' places; the same place stays; two places cross once; a `.muro` file does not write it
agda/Muro/Arrow.agda %1 on a Π binder must be used; both marks are the affine quantity
agda/Muro/Linear.agda multiplicity on a session endpoint; λ¹ must be used once; ⊸ accepts or returns it; the protocol is `SExp`, so `rec` and a declared data payload are endpoints; `dualS` keeps both; `--safe --guardedness`; not a Tm
agda/Muro/Place.agda placement coeffect; ambient by default; one boundary; run has no seat
agda/Muro/Located.agda a session at a place; `endpoint` and `read` follow the sitting; `--node` follows a cross reading; `--backend otp` follows the process
agda/Muro/Tag.agda constructor tags (Check compares tags before structure)
agda/Muro/Spine.agda application spines (constructor / dty applications), spine views
agda/Muro/Frag.agda the ⊢ fragment as a predicate on terms, signatures, contexts
agda/Muro/Reduction.agda parallel reduction ⇛, confluence
agda/Muro/Convert.agda whnf strategy ⟶ and conversion ≈ for ⊢
agda/Muro/Data.agda IsData, InstParams, BrTy: what the data rules are stated with
agda/Muro/Judgement.agda inductive ⊢ with uses (core fragment, with data / match / ν)
agda/Muro/Typing.agda declarative ⊨ (no uses); substitution, preservation
agda/Muro/Wall.agda mode wall lemmas
agda/Muro/Consistency.agda Empty-nf, progress, preservation at Empty; Empty-evid is not proved
agda/Muro/Soundness.agda Check says yes → ⊢ derives it, on the fragment
agda/Muro/Soundness/Conv.agda whnf-sound, conv-sound
agda/Muro/Soundness/Views.agda views, isData-sound, GoodSig, instParams / index clashes on a telescope
test/muro_check_test.exsInstall
mix muro.check, mix muro.mutate, mix muro.emit, mix muro.gen, mix muro.init, and mix muro.deps.get run in any directory once the archive is installed, with or without a mix.exs.
A project without Elixir uses the self-contained executable: mise use -g github:murolang/muro@0.20.15. If mise answers No versions found or a 404 from mise-versions.jdx.dev, run mise settings set use_versions_host false first. The commands are muro check, muro emit, muro gen, muro init, muro skills add, muro deps.get, muro mutate, and muro ui. muro ui path.muro prints how to install muro_visor and does not start the explorer. The page is Visual explorer. The manifest is muro.toml or [package.metadata.muro] in Cargo.toml. The executable page is The executable.
Install it from Hex:
mix archive.install hex muroAlternatively, from a checkout of this version:
mix deps.get
mix archive.build
mix archive.install muro-0.20.15.ezFrom GitHub, Mix fetches the default branch and builds the archive:
mix archive.install github murolang/muro
--force skips the install question.
mix muro --version prints Muro v and the version of the copy that ran. Inside a project that depends on muro, the project’s copy runs after app.start. Inside any other project, or outside a project, the archive’s copy runs and the host is neither compiled nor started. The version line names the copy that ran.
A script run with mix run --no-start does not see the archive. Archives are on the code path only for Mix tasks. Use mix muro.check and mix muro.emit.
mix muro.emit path.muro prints the Elixir module. mix muro.emit path.muro > path.ex saves it. mix muro.emit path.muro --backend exunit prints one ExUnit module for the book’s closed equations, including a library whose params are not supplied. --imports with that backend also collects the files the book imports directly. The page is Closed equations as tests.
--backend zig writes <name>.zig next to the file. zig translates it. The proof stops at the run image.
--backend c writes C. The Mix task builds a NIF when the working directory is the loaded Mix project. Add {:elixir_make, "~> 0.10", runtime: false} to the project deps, or run the task inside a project that depends on muro. Without elixir_make, that NIF stops and names the dependency. Outside the loaded project the task writes the .h and the .c only, and so does the executable. nx and makeup are optional. Checking and emitting Elixir do not load them.
Committed modules
A project commits the modules muro gen writes. The .muro file is the source. Do not edit the generated file by hand. Change the .muro file and run muro gen.
muro skills add writes this block into AGENTS.md, and into CLAUDE.md when that file already exists. The command lines and the example anchor are filled from the project. The markers are part of the block. Each marker is a whole line. A trailing carriage return is allowed. An inline mention is left as prose. The file’s line endings stay.
Muro has two jobs: spec and evidence. run is what is emitted.
The wall: a spec never becomes evidence. Evidence never becomes a run.
{{commands}}
Every binder is typed and every motive is written.
Generated files are not edited by hand.
Nearest example: manual/examples.md#{{example}}.
muro.exs at the project root, a :muro key in mix.exs, muro.toml, or [package.metadata.muro] in Cargo.toml names each book and the target path. Elixir, fsm, and exunit entries also name the module. An entry may add [kind: :exunit, impl: Adapter, imports: true], or [kind: :c], [kind: :rust], [kind: :zig], or [kind: :asm]. imports: is valid only on an exunit entry. muro: is the version the books are checked with. muro gen and mix muro.gen --check refuse to run when it differs from the running version. The message names both versions.
mix muro.init creates muro.exs and priv/muro/. It prints a note that points at muro skills add. It does not write AGENTS.md. muro init --backend stores that backend as the manifest’s top-level backend. muro init writes the one manifest the directory calls for. If muro.exs, muro.toml, or [package.metadata.muro] is already present, it prints a diff of that file in its own format and leaves every file as it is. mix muro.init --ci github writes .github/workflows/muro.yml. The job installs the archive with mix archive.install hex muro <version> --force, with the version from the manifest, and runs mix muro.gen --check. muro init --ci github installs the executable and runs muro gen --check.
The page is Committed modules. Package imports (import "pkg:...", muro_deps:, mix muro.deps.get, muro.lock) are Packages. The ordinary library is import "std:file.muro" (Library). It is compiled into the compiler. It is not the prelude, and it is not a package.
Commands
mix deps.get
mix test
mix muro.check
mix muro.check examples/half_ok.muro
mix muro.mutate examples/half_ok.muro --only half
mix muro.emit path.muro --backend exunit
mix muro.emit path.muro --backend exunit --imports
mix muro --version
mix muro.init
mix muro.gen
mix muro.gen --check
mix muro.deps.get # fetch muro_deps/ and write muro.lock
make agda # Job B only; needs vendor/agda-stdlib v2.3
On success, mix muro.check prints the version that checked the book, then one line per temporal declaration, then one line per projected pair of each choreography, then:
Muro v0.20.15
All terms check. Evidence never becomes a run.
A book with no temporal declaration and no choreography prints those two lines and nothing between them. A book that imports packages prints conforms to <name> <version> (sha256 <hash>) after that sentence, one line per package it loaded. A book that imports none, and mix muro.check with no path, print only those two lines.
Muro.Check.check_sig/2 returns :ok on a well-typed book, or {:error, msg} with every definition error in book order. check_sig(book, fuel: n) sets the fuel for each definition. Definitions are checked concurrently; the fuel is the bound, not a wall-clock timeout.
Representations (do not add a third)
| Layer | Form | Where |
|---|---|---|
| Parser / pretty / emit | named FOAS |
lib/muro/{parser,ast,emit}.ex |
| Check / subst | de Bruijn |
lib/muro/{check,subst}.ex, agda/Muro/{Check,Subst}.agda |
| Examples and metatheory |
de Bruijn Tm n |
agda/Muro/Syntax.agda, agda/Muro/Example*.agda |
Elixir constraints when you are in Job B: ASCII identifiers only. Do not define local hd/1. Guards cannot call ordinary defp helpers. repr entries are host-side declarations checked by Muro.Repr, not part of the Agda calculus.
What is not in the language
Type : Type, cubical, tactics, implicits, unification, metavariables (a ? always fails; it does not unify), extra quantities, + on Stream or Either, typing raw Elixir, emitting spec or evidence.
Full list: Limits.
Source on GitHub
· fetched from murolang/muro manual/