Hello World
Create a file called hello.keel:
use std/io
use std/schedule
agent Hello {
@tools [io]
@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?
use std/io,use std/schedule— imports the stdlib modules this file uses. Each import binds a lowercase module name (io,schedule).agent Hello— declares an agent.@tools [io]— grants the agent access to the effectfuliomodule. Capabilities are deny-by-default inside agents.@role "..."— an attribute describing what the agent does. Bound to the LLM provider for anyai.*calls.@on_start { ... }— a lifecycle hook that runs when the agent starts.schedule.every(5.seconds, () => { ... })— schedules a recurring block.scheduleis a stdlib module, not a keyword.io.notify(...)— prints to the terminal.run(Hello)— starts the agent.runis a built-in free function — no import needed.
Two imports declare everything this program touches — see The Standard Library.
Using AI
use std/ai
use std/io
use std/schedule
type Mood = happy | neutral | sad
task analyze(text: str) -> Mood {
ai.classify(text, as: Mood) ?? Mood.neutral
}
agent MoodBot {
@tools [io]
@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.