The Keel Language
A small, statically-typed language where AI agents are first-class citizens.
Agents are primitives
The actor model is the one concurrency primitive. Per-agent serial mailboxes, isolated mutable state via self — no shared-memory races to reason about.
Small core, deep stdlib
AI calls, scheduling, HTTP, email, memory — all live in a standard library you import one line at a time with use std/<name>. The core language stays tiny.
Capabilities, deny-by-default
An agent can only touch what its @tools list declares. Undeclared effects are compile-time errors — every program is auditable at a glance.
Statically typed
Full inference, exhaustive pattern matching, nullable safety, no implicit any. Misconfiguration fails loud at startup — never with plausible nonsense at runtime.
A first agent
Everything you need to triage and reply to email — model calls, human-in-the-loop, scheduling — in one file:
use std/ai
use std/email
use std/io
use std/schedule
type Urgency = low | medium | high | critical
agent EmailAssistant {
@role "Professional email assistant"
@tools [ai, io, email]
on message(msg: Message) {
urgency = ai.classify(msg.body, as: Urgency) ?? Urgency.medium
when urgency {
low, medium => {
reply = ai.draft("response to {msg.body}", tone: "friendly")
if io.confirm(reply) { email.send(reply, to: msg.from) }
}
high, critical => {
io.notify("{urgency}: {msg.subject}")
guidance = io.ask("How should I respond?")
reply = ai.draft("response to {msg.body}", guidance: guidance)
if io.confirm(reply) { email.send(reply, to: msg.from) }
}
}
}
@on_start {
schedule.every(5.minutes, () => {
for email in email.fetch(unread: true) {
send(self, email.as_message())
}
})
}
}
run(EmailAssistant)
Four imports, one capability list, and the whole program is auditable at a glance: this agent can call models, talk to a human, and send email — and nothing else.
Design Principles
- Small core, deep stdlib. If a feature can be a library, it is one. The core language has a small reserved keyword set; everything else is a
std/module imported with the sameusesyntax as your own files. - Agents are primitives.
agentis the only concurrency model. Per-agent serial mailboxes with isolated mutable state viaself. - Capabilities are deny-by-default. Effectful modules must be declared in
@toolsbefore an agent can use them. Undeclared calls are compile-time errors, not surprises in production. - Interfaces everywhere. LLM providers, memory stores, HTTP clients, loggers — all behind interfaces. Users swap implementations without leaving the language.
- Statically typed. Full inference. Exhaustive pattern matching. Nullable safety. No implicit
any. - No silent fallbacks. Misconfiguration fails loud at startup, not with plausible-looking nonsense at runtime.
Try It
Install and run your first agent:
curl https://keel-lang.dev/install.sh | sh
keel run examples/showcase.keel
Versioning and Breaking Changes
Alpha. Keel is pre-1.0. Semver is not respected between 0.x minor versions, and breaking changes can land in patch releases.
- 0.2.x — current alpha. One module system (
use std/<name>and local file imports), deny-by-default@tools, built-intestblocks. - 0.x — further pre-1.0 releases will keep breaking things where the design demands it. The changelog flags every break.
- 1.0.x — first API-stable release. Semver begins.
See SPEC.md for the authoritative design and ROADMAP.md for the plan.