Libraries, Not Keywords

· 9 min read

This is part of a short series answering David Barbour’s questions about Koru. Several of them turn out to share a shape — should this be a language feature? should “session” be a keyword instead of “event”? do you have a built-in notion of singletons? — and our answer keeps coming out the same way, so it’s worth saying once, plainly, and then pointing the other posts here.

The answer is: usually it shouldn’t be a language feature. It should be a library — and the work is making the library substrate strong enough to carry it.

That’s not a dodge or a shortfall. It’s a bet about where power should live in a language. David’s instinct, coming from formal calculi, is to grow the grammar: if effects compose into protocols, name that, type it, put session in the language. Koru’s instinct is the opposite — keep the grammar small, and put the power in a few composable compiler primitives that libraries build on. So when he asks “should this be a feature,” our honest answer is often “we wouldn’t make it one at all.” Here’s why we can say that with a straight face.

Where the language ends is not where you think

Start with the most radical version of the claim, because it sets the scale: a keyword in Koru is just an event you don’t have to fully qualify. That is the whole of it. if is a keyword — but “keyword” here carries no meaning beyond “you may write this event without its module path.”

~if and ~for are ordinary events, declared in koru_std/control.kz and available only because you import it. The one thing that lets you write a bare ~if instead of a qualified ~std/control:if is a one-word annotation, [keyword]. That is the entire difference between a keyword and any other event — a module event without it, like regex’s match, simply has to be qualified.

So control flow isn’t grammar. It’s a library you happen to always import.

Now the part that surprised even us to write down: the obligation checker isn’t grammar either. Everything in the previous post — issue in, discharge out, KORU030 was not discharged — is not built into the language. It’s a compiler pass. Koru’s checker passes all come from compiler.kz and run in a pipeline orchestrated by a single event, compiler:coordinate, which — like any abstract event — you can override. The default pipeline runs the phantom/obligation checker; a different one need not. The discipline is on by default, it has a warn-only mode, and it can be turned off entirely:

Sit with that. The thing that feels most like the soul of Koru — the obligation system — is a default-on, removable pass, not a language primitive. Compile with a coordinate pipeline that drops it and you have a different, looser language built from the very same grammar.

So when David asks whether some construct should be “in the language,” the honest Koru answer has an unusual edge: there is very little in the language in the sense he means. The grammar is events, flows, and branches. Control flow is a library. The type discipline is a pass. The line he’s asking us to draw — feature versus library — is one we mostly don’t have.

The exemplar: a bounded protocol that’s already a library

Koru has a high-performance computation kernel — a construct where you declare a data shape, then operate on it relationally, and the compiler decides layout and iteration:

~std/kernel:shape(Body) { x: f64, y: f64, mass: f64 }

~std/kernel:init(Body) { x: 42.0, y: 0, mass: 1.5 }

The interesting part isn’t the math. It’s that inside a kernel, only certain things are legal. No for loops, no branch constructors, no arbitrary events — a kernel body is a bounded sub-tree with a defined set of permitted operations. That is precisely the shape of a protocol: “within this scope, these interactions and no others.” It is, in miniature, the session idea David is reaching for — a sub-tree whose legal contents are constrained and checked.

And every bit of it is a library. There is no kernel keyword. std/kernel is an ordinary Koru module — 1,500 lines of [transform] and [norun] events — that hangs off one real language primitive: an annotation called claims_descendants, which means “this transform owns the sub-tree beneath it.” The kernel’s init is declared [comptime|transform|claims_descendants], claims the operations that follow it, walks them, and rejects what doesn’t belong.

The enforcement is real, and — honestly — partial. The structural rules are in:

The legal-event whitelist — “only kernel operations, reject arbitrary user and stdlib events” — is still aspirational, and we keep it pinned red so it stays visible instead of forgotten:

So the kernel is a working proof that Koru can express a bounded protocol as a library — and an honest record of how far the enforcement has actually gotten.

The substrate: annotations meaty enough to carry it

A library that does compiler-level work needs to attach real directives to its constructs — not just a flag, but parameters, configuration, intent. For a long time annotations were inline and pipe-delimited: [comptime|transform]. That runs out of room fast once the directives take arguments.

So the substrate grew. Annotations can now be written vertically, one directive per line, with parameters:

~[
- comptime
- runtime
- optimize(level: 3)
- gpu(target: "metal", precision: "half")
] event vertical-params {}

This parses to the same AST as the inline form — it’s purely about giving library authors room to write meatier directives legibly.

The road keeps going past what’s paved. The aspirational end of it is literate annotations — annotation blocks as micro-documents, markdown prose interleaved with - directives, the parser keeping the directives and ignoring the prose, so metadata can explain and instruct in one place. That one’s not landed:

Widening the annotation language isn’t a cosmetic change. It’s the thing that decides whether the next bounded-protocol construct — sessions, say — can be built the way the kernel was, as a library, or whether it would have to fall back into the grammar.

The bet, stated plainly

Put the three together and the stance is: a small grammar, a few sharp compiler primitives (claims_descendants and the transform machinery), and an annotation language rich enough that libraries can carry real directives. Power lives in the composition, not in keyword count.

This is a real bet, and worth naming its risks. A library-grown construct can be under-enforced — the kernel’s red whitelist is exactly that. A formal language feature gives you guarantees a library has to earn test by test. David’s grammar-first instinct buys soundness up front; our library-first instinct buys a grammar that doesn’t ossify, at the cost of having to build — and pin — the enforcement ourselves. We think that’s the right trade for a language still being discovered, but it is a trade.

What this answers

So when his thread asks whether effects-having-effects deserves to be a session in the language, or whether Koru needs a built-in notion of singletons, the answer this post lets the others give is the same one twice: we wouldn’t add a keyword for it. We’d build it the way we built the kernel — a bounded construct in a library, standing on claims_descendants and the annotation substrate — and the work we’d point to as evidence we mean it is the annotation language quietly getting meatier underneath.

The language we didn’t grow is the point.