October, A month of Monad: agents, terminals, and a wasm target

October update

Monad LanguageOct 19, 2026

Here is a summary of what I have been doing recently.

What Monad even is

Monad is a purely functional, dependently typed systems language that
compiles to native binaries through LLVM. No VM, no interpreter at the end of
the pipeline — a Monad program is a binary. The compiler is written in Monad
and compiles itself, which keeps us honest: any surface change has to survive
the compiler building its own successor. It is very much an alpha, and the
maturity matrix says so area by area.

A taste of the language — structs, do-notation over IO (straight out of examples/):

open IO {println}

struct Counter { count : I64 }

def say_hello : IO Unit {
    println "Hello from do block!"
}

def count_through_bind (c : Counter) : IO I64 := do {
    let q <- IO.pure c;
    return q.count
}

The month in numbers

Since mid-September: 463 commits on main. git log --numstat claims about 131,000 lines added — a large slice of that is test assets and repro fixtures, but the delta is real: a new TUI toolkit, an interactive coding agent, a wasm backend, and a streams overhaul, all in the language itself.

Weaver: an agent written in the language

The headline project is weaver, an interactive coding agent built in Monad. It talks to model providers (Ollama, Mistral, OpenRouter, Anthropic, plus a mock for tests), mounts MCP servers, and runs a slash-command loop with an approval policy. Here is the entire safety model in one pure function:

/// The decision table, the whole policy in one function.
pub def Policy.decide (cap : Capability) (mode : Mode) : Decision :=
    match mode {
        Mode.manual => Decision.ask,
        Mode.plan =>
            match cap {
                Capability.read => Decision.allow,
                _ => Decision.refuse "plan mode: only read tools run",
            },
        Mode.allow_edit =>
            match cap {
                Capability.exec => Decision.ask,
                Capability.net => Decision.ask,
                _ => Decision.allow,
            },
        Mode.auto => Decision.allow,
    }

Every cell of Capability x Mode is pinned by tests. The gate that renders prompts is the only impure piece — the policy never does I/O. That is the kind of separation the language pushes you toward anyway, and building an agent turned out to be a brutal, excellent dogfood test: JSON parsing, TLS, diff rendering, TOML settings, provider wire formats, retries. Three months ago half of that story didn't exist.

tui: an Elm architecture in 40 lines

Weaver's interface is a second project: the tui mote, an Elm-style framework — a pure update over events, a Widget view, and a run_appthat owns raw mode, diffed redraws, and the terminal restore. The counter example is a whole app:

def counter_update (count : I64) (e : Event) : Pair I64 (Cmd Event) :=
    match e {
        Event.key k => counter_key count k,
        _ => Pair.pair count Cmd.none
    }

def counter_view (count : I64) : Widget :=
    Widget.column [
        sized (LayoutSize.fixed 1) (label "Counter Demo"),
        sized (LayoutSize.fill 1) (border BorderStyle.plain (label (I64.to_string count))),
        sized (LayoutSize.fixed 1) (label "+ / - to change, q to quit")
    ]

def main (args : List String) : IO Unit :=
    run_app (App.app 0 (fn (e : Event) => e) counter_update counter_view (0 - 1))

This month it grew mouse support, wheel scrolling, a multiline text input, async turns (spinner, live redraws, buffered input while the model thinks) — which needed one new runtime native, a non-blocking fiber poll, wired through all four touchpoints the native system demands. Weaver's TUI now sits entirely on it.

wasm: a second backend

The runtime split into a portable core and platform files, the compiler grew a wasm32 target with link modes and native gating, and there is now a wasi/web proof, a loader, and a CI leg. init/ — the pure, portable mote — now genuinely means it: the same code that runs as a native binary runs in the browser.

Under the hood

  • Streams: FdStream and ByteBuf overhauled file, pipe, TCP and stdio I/O behind one Stream class — plus the unglamorous bugs: SIGPIPE restored in fork children, stdin read unbuffered so poll sees pending bytes.
  • Type checker: instance resolution got several real fixes — HOF arguments now solve their type from later sibling arguments, instance-level constraint dicts resolve at constrained calls, and constructor tags key by resolved owner instead of a guess.
  • Macros: #[derive_handler] on defmacro lets users author their own derives in Monad, alongside the built-in BEq, BOrd, Debug, and Lens.

Being honest about the LLMs

A lot of this month's code was written with LLMs, and I don't want to pretend otherwise. The workflow is agent-driven: I write the plan, the agent writes and runs the code, I review, and the commits carry the reasoning in their messages. Weaver itself is an LLM client, so the language has to be a good host for exactly this kind of tooling.

What keeps it honest is that the LLMs work inside the same checker you do. A model's plausible-but-wrong code dies at the type gate, not in production. The termination checker rejects "mostly fine" recursion. Self-hosting means an agent regression shows up when the compiler fails to build itself. None of that replaces review — the policy table above is small because a human kept it small — but the compiler is a genuinely useful second reviewer, which is the most you can ask of any collaborator.

Try it

Follow instructions to install monadup at the homepage

monadup default
monad run examples/hello.mo

Nightlies are up for x86_64-linux, aarch64-darwin, aarch64-linux and an
experimental riscv64. It's alpha software — breaking changes are a feature
for now, and the docs say what works and what
doesn't.

Never miss a post

Get new writing sent to your inbox.

Subscribe