The rhai runtime
The rhai runtime (runtime::rhai, kind rhai) lets an agent’s brain be a
rhai script instead of a hand-written wasm component. It evaluates the script
on the bundled rhai interpreter (a wasm component compiled into the host at
build time whose omw.* host imports route to the very same global provider /
tooling / bus as every other runtime).
The interpreter ships in the omw-rhai package/binary (nix run .#omw-rhai or
the omw-rhai-<arch>.tar.gz release tarball). The default omw binary doesn’t
include it. With the --features runtime-rhai build flag it is compiled into
the host at build time instead.
Configuration
The rhai runtime takes no required parameters beyond the shared WASI sandbox:
[runtime.rhai]
kind = "rhai"
[[agents]]
name = "alice"
runtime = "rhai"
script = "brain.rhai"
A custom interpreter component can be substituted via the runtime’s
interpreter parameter; otherwise the interpreter compiled into the binary is
used. The same WASI sandbox keys as the wasm runtime apply, flattened
alongside interpreter:
[runtime.rhai]
kind = "rhai"
inherit_env = true
env = { FOO = "bar" }
[[runtime.rhai.preopens]]
host_path = "./data"
guest_path = "/data"
perms = "read_only"
The interpreter and the WIT bindings
The bundled guest (omw-wasm-rhai-interpreter) exports the runtime interface
(kind returns rhai, run(script) evaluates the script) and imports the
omw world. On startup it registers an omw static module with three
sub-modules that expose the WIT interfaces to the script:
omw::provider::get(name)— returns a provider handle map whose blockingchat, streamingchat_stream,is-open,cancel,list_models, andkindentries are methods.chat/chat_streamtake an optional trailingparamsargument: either a map of generation settings (#{ temperature: 0.2, reasoning_effort: "high" }) or an already-JSON string (e.g. anendpoint-message’sparams), merged over the provider’s configured defaults.omw::tooling::get(name)— returns a tooling handle map whoselist-tools,call-tool,is-open,cancel,call-tool-blocking,list-resources,read-resource,subscribe-resource-list,subscribe-resource,unsubscribe-resource-list,unsubscribe-resource, andkindentries are methods.omw::host::*— the host helpers:log,time_now,time_format,wait_until,wait_for,wait_cron,cancel_timer,subscribe_agent,unsubscribe_agent,subscribe_lifecycle,unsubscribe_lifecycle,subscribe_endpoint,unsubscribe_endpoint,stream_endpoint,send_agent,recv,try_recv,new_uuid,base64_encode,base64_decode,memory_get,memory_set,memory_remove,sleep_for,sleep_until, andsleep_cron.
Handles are Rhai maps. Methods are FnPtrs stored on them, so scripts call them
method-style (provider.chat_stream(...), tooling.call-tool(...)). The time
functions take plain integer literals: the interpreter converts rhai’s i64
integers to the WIT u64 tick type at the boundary (rejecting negatives).
Values in rhai
Events come back as maps shaped #{ id, kind, payload }:
id— the envelope’s UUID;kind— one ofmessage,error,timer,chat-delta,chat-end,tool-result,resource-list-updated,resource-updated,endpoint-message,endpoint-session-end;payload— the text formessage/error, a map forchat-delta(withcontent,reasoning,tool_call{ id, name, arguments },finish_reason, andusage{ prompt_tokens?, completion_tokens?, total_tokens? }), a map fortool-result({ name, arguments, value }), a list of resource maps ({ uri, name, description?, mime_type? }) forresource-list-updated, a resource-content map ({ uri, mime_type?, content }) forresource-updated, a map forendpoint-message({ session, messages, tools, params? }, withmessagesa list of{ role, content?, reasoning?, tool_call? }maps,toolsa list of{ name, description?, input_schema }, andparamsthe opaque JSON string the client submitted), a map forendpoint-session-end({ session, error? }), and unit otherwise. Thecontentfield holds actual text for textual formats and base64 for anything else — match onmime_typeto tell which. Decode binary payloads withomw::host::base64_decode(which returns a blob) and encode back withomw::host::base64_encode.
Example brain
let p = omw::provider::get("openai");
p.chat_stream("gpt-4o", [
#{ role: "user", content: "say hi" },
], []);
let out = "";
loop {
let ev = omw::host::recv();
if ev.kind == "chat-delta" { out += ev.payload.content }
if ev.kind == "chat-end" { break }
if ev.kind == "error" { throw ev.payload }
}
out
The script’s final value becomes its terminal message when it is not unit.
Memory
memory_get returns the value or unit when absent; memory_set stores;
memory_remove returns true when a value was present:
omw::host::memory_set("timer", omw::host::wait_for(1000));
// ... after a reload, the same context still has it:
let timer = omw::host::memory_get("timer");