Hello World
Alpha (v0.1). Breaking changes expected.
Create a file called hello.keel:
agent Hello {
@role "A friendly greeter"
@on_start {
Schedule.every(5.seconds, () => {
Io.notify("Hello from Keel!")
})
}
}
run(Hello)
Run it:
KEEL_OLLAMA_MODEL=gemma4 keel run hello.keel
Output:
⚡ LLM provider: Ollama (http://localhost:11434)
▸ Starting agent Hello
role: A friendly greeter
model: gemma4 (ollama @ http://localhost:11434)
⏱ Schedule.every(5.seconds)
▸ Hello from Keel!
▸ Agent running. Press Ctrl+C to stop.
▸ Hello from Keel!
▸ Hello from Keel!
Press Ctrl+C to stop.
What just happened?
agent Hello— declares an agent.@role "..."— an attribute describing what the agent does. Bound to the LLM provider for anyAi.*calls.@model "..."— which model to use.@on_start { ... }— a lifecycle hook that runs when the agent starts.Schedule.every(5.seconds, () => { ... })— schedules a recurring block.Scheduleis a stdlib namespace, always in scope.Io.notify(...)— prints to the terminal.Iois also stdlib.run(Hello)— starts the agent.
No imports. The Schedule, Io, Ai namespaces are in scope from the start — that’s the prelude.
Using AI
type Mood = happy | neutral | sad
task analyze(text: str) -> Mood {
Ai.classify(text, as: Mood) ?? Mood.neutral
}
agent MoodBot {
@role "Analyzes the mood of text"
@on_start {
Schedule.every(10.seconds, () => {
mood = analyze("I love building programming languages!")
Io.notify("Mood: {mood}")
})
}
}
run(MoodBot)
Ai.classify sends the text to the LLM and parses the response into one of the enum variants. ?? supplies a default when the LLM is unavailable or the response doesn’t match.