October, A month of Monad: agents, terminals, and a wasm target
October update
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:
FdStreamandByteBufoverhauled file, pipe, TCP and stdio I/O behind oneStreamclass — 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]ondefmacrolets users author their own derives in Monad, alongside the built-inBEq,BOrd,Debug, andLens.
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.moNightlies 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.