Methodology

The two fixpoints, made one construct.

μ and ν are dual operators on monotone functors. μF is what you build; νG is what you observe. A Munu contract is the explicit witness of their interaction — the only place in the language where induction meets coinduction.

§ 01 — Foundation

μ and ν are dual operators on monotone functors.

In conventional languages, μ is privileged — you write data, and ν is hacked on (a loop, a thread, a generator). Each fights a different battle with the runtime.

In Munu, both are first-class. A contract is the term-level witness that an algebra (μF → A) and a coalgebra (B → νG) agree on a boundary. The kernel never sees this; it sees only bytecode that steps when bound.

μ
least fixpoint — inductive
Shape
finite, built
Tactic
recursion · pattern match
Examples
Nat, List, AST, parse tree
Type rule
LUB (⊔) — joins of types
algebra · coalgebra
ν
greatest fixpoint — coinductive
Shape
infinite, observed
Tactic
corecursion · observation
Examples
Stream, FSM, server, supervisor
Type rule
GLB (⊓) — meets of types
§ 02 — Contracts

Contracts all the way down.

Every contract runs on its own lightweight thread in the kernel. A contract binds mu functions (what you build) and nu functions (what you observe) under a supervised behaviour. The do: clause governs local failure recovery. The must: clause is the obligation the supervisor enforces. When a thread crashes, it crashes only that contract — the supervisor decides whether to absorb the failure and deliver a degraded result, restart, or escalate.

When an LLM's generated code crashes at runtime, the supervisor catches the thread failure, formats a structured diagnostic, and feeds it back through the same dataflow variable the caller is waiting on. The LLM sees what went wrong, fixes the code, and retries — without losing context. The harness turns a thread crash into a conversation.

do: — weathers the storm

Local failure recovery

The do: formula compiles into the contract's own Mealy table. When the contract crashes, the coalgebra intercepts the Fail event. If the Mealy says Restart, unbound variables go TempFail and the contract restarts — it weathers the storm at its own layer.

must: — enforces obligation

Supervisor deadline

The must: formula feeds into the parent supervisor's Mealy table as a deadline. If a child breaches its must: clause, the supervisor fires MustBreach — the child's variables go PermFail. TempFail is local recovery. PermFail is contract violation.

supervised_pipeline.uv
// timing envelope: 95% within 100ms, 99% within 500ms
cdf fast_response = [(100ms, 0.95), (500ms, 0.99)]

mu nand(a: bool, b: bool) -> bool {
    (true, true) => false,
    (_, _) => true
}

nu nand_combine(a_s: [bool], b_s: [bool]) -> [bool] {
    ([a | at], [b | bt]) =>
        [nand(a, b) | nand_combine(at, bt)]
}

pub contract NandGate(a: [bool], b: [bool]) -> [bool]
    behaviour {
        do:
            nu X.
                [fail(self, _)] <restart(self)> X
                and [_] X
        must:
            nu Y.
                [fail(self, _)]~fast_response
                    <restart(self)> Y
    } {
    spawn nand_combine(a, b)
}

// contracts compose contracts
pub contract AndGate(a: [bool], b: [bool]) -> [bool] {
    let nand_out = spawn NandGate(a, b);
    spawn NandGate(nand_out, nand_out)
}
§ 03 — Runtime

Every variable is a dataflow variable.

Every variable in the kernel has a 6-state lifecycle. Variables are single-assignment — once Bound, they never change. Reads block until the value arrives. There are two paths through the FSM: eager variables that are allocated unbound and computed immediately, and lazy variables whose producer parks until a consumer demands the value.

UNBOUND WAITNEEDED NEEDED BOUND terminal TEMPFAIL PERMFAIL terminal read() → suspend demand wake bind() crash restart + bind() escalate bind / restart demand wake read / suspend failure

Failure states: When a contract's do: fires Restart, its unbound variables go TempFail — recoverable when the producer restarts and binds. When a child breaches its must: or the supervisor escalates, variables go PermFail — terminal, no recovery.

Eager dataflow: Unbound → Needed → Bound

When a contract calls a nu function or another contract, the callee runs on its own thread. The result variable starts Unbound. If the caller reads it before the callee finishes, the caller's thread suspends until the value is bound.

eager_example.uv
nu produce(n: i64) -> [i64] {
  n => [n * 2 | produce(n + 1)]
}

mu head_of(xs: [i64]) -> i64 {
  ([h | _]) => h
}

pub contract Eager(n: i64) -> [i64] {
  let stream = spawn produce(n);  // spawns produce on its own thread
  spawn head_of(stream)           // blocks until first element is Bound
}

Eager — contract reads stream head before it's bound

stream.head = ?Unbound
contract Eager let stream = spawn produce(n); spawn head_of(stream) Ready
nu produce n => [n * 2 | produce(n + 1)]
Step 0 / 5

Trace

— press step to advance —

Lazy dataflow: WaitNeeded → Needed → Bound

A lazy mu function returns immediately with a WaitNeeded variable — the body doesn't execute. When something demands the result (arithmetic, match, or a read), the body wakes and runs.

lazy_example.uv
// body does not run until result is demanded
lazy mu compute(n: i64) -> i64 {
  (n) => n * 2
}

mu use_result(n: i64) -> i64 {
  (n) => {
    let result = compute(n);  // returns WaitNeeded — body does not run
    result + 5              // demands result → body wakes, returns 25
  }
}

pub contract Lazy(n: i64) -> [i64] {
  spawn use_result(n)
}

Lazy — body doesn't run until demanded

result = ?WaitNeeded
lazy mu compute n => n * 2 Parked
mu use_result let result = compute(10); result + 5 Ready
Step 0 / 4

Trace

— press step to advance —
§ 04 — Types

A ψ-term lattice with a 6-state FSM.

Gradual typing without an escape hatch. The checker proceeds by subsumption (⊑), never equality. GLB is ν (greatest fixpoint); LUB is μ (least fixpoint). The μ–ν duality at term level repeats at type level — same operators, same lattice.

⊔ LUB · μ ↑ ⊓ GLB · ν ↓ any ψ ψ-term open τ concrete sealed never
hover a node — the lattice is partially ordered by subsumption
§ 05 — LLM Diagnostics

Errors written for the LLMs, not the human.

The compiler is the first reader; the LLMs are the second; the human, when they choose to look, is the third. Every diagnostic emits a structured envelope with the failing constraint, the relevant positions in the AST, a candidate-fix vector, and the smallest counter-example that violates subsumption.

This is the language-level invariant that makes Munu an AI harness: the models never have to guess what the compiler meant — the constraint and a witness for its violation are both addressable terms.

conventional · ambiguous
error[E0308]: mismatched types
  --> src/main.uv:14:18
   |
14 |     let n: u64 = raw
   |                  ^^^ expected u64, found ψ
   |
   = help: add a cast
munu · structured · addressable ⌘ LLM-ready
{
  "code":       "M.subsume.fail",
  "operator":   "⊑",
  "expected":   "u64",
  "got":        "ψ",
  "site":       "src/main.uv:14:18..14:21",
  "constraint": "ψ ⊑ u64",
  "witness":    { "raw": "-3.14" },
  "fixes": [
    { "k": "insert_cast",  "term": "raw as u64" },
    { "k": "widen_target", "term": "i64" }
  ]
}