Runtime Scripting
Kit supports capability-gated runtime scripting through the Script module.
A host program can load a Kit script from source or from a precompiled bytecode artifact,
inspect its exports, and call named exported functions through the bytecode VM.
The implemented API is a script engine boundary. Scripts are loaded as modules,
calls target exported functions, and data crosses the boundary through
Script.Value.
Public API
Script wraps the artifact path and its export table. ScriptAuth
is derived from FileReadAuth, so script loading is explicit in function
signatures. The public wrapper exposes loading, export discovery, calls, and JSON
conversion helpers.
import Auth.File.{file-auth, file-read-auth}
import Auth.Script.{script-auth}
import Script
auth = script-auth (file-read-auth (file-auth env.root))
Script.load-source auth "plugin.kit" source
Script.load-file auth "mods/plugin.kit"
Script.load-bytecode auth "mods/plugin.kitbc"
Script.exports script
Script.has-function? script "on-update"
Script.call auth script "on-update" args
Script.keyword :ready
Script.to-json value
Script.from-json json
Function names may be fully qualified or simple. Calling "on-update"
resolves EnemyScript.on-update when no other export has the same simple
name; pass the fully qualified name to avoid collisions.
Command Line
The kit script command builds, inspects, and calls script artifacts.
kit script build emits a .kitbc bytecode artifact; without
-o, the default output appends bc to the source path.
kit script build mods/enemy.kit -o mods/enemy.kitbc
kit script exports mods/enemy.kitbc
kit script call mods/enemy.kitbc on-update '[0.016,{"hp":100,"x":4.0,"y":9.0}]'
kit script call prints the returned value as JSON. Missing exports,
arity mismatches, invalid artifacts, and runtime failures are reported as
ScriptError.* messages. The JSON argument array is optional and defaults
to [].
Host Programs
A host usually creates a Script handle, checks the export it needs, and calls it
with Script.Value arguments. The handle stores the artifact path and export table.
module GameHost
import Auth.File.{file-auth, file-read-auth}
import Auth.Script.{script-auth}
import Script
run-update = fn(auth, script) =>
args = [
Script.Float 0.016,
Script.Record [
("hp", Script.Int 100),
("x", Script.Float 4.0),
("y", Script.Float 9.0),
],
]
match Script.call auth script "on-update" args
| Ok value -> value
| Err e ->
println "script failed: ${Error.message e}"
Script.Unit
main = fn(env: Env) =>
auth = script-auth (file-read-auth (file-auth env.root))
match Script.load-bytecode auth "mods/enemy.kitbc"
| Ok script ->
if Script.has-function? script "on-update" then
run-update auth script
else
Script.Unit
| Err e ->
println "could not load script: ${Error.message e}"
Script.Unit
The script itself is ordinary Kit. Exported functions become callable entry points.
module EnemyScript
export on-update = fn(dt: Float, state) =>
{
hp: state.hp,
x: state.x + (20.0 * dt),
y: state.y,
}
Source Loading
Script.load-source and Script.load-file compile Kit source at
runtime, cache the resulting script artifact by source hash, and return the same
Script handle shape as load-bytecode. Use source loading for
tools, development workflows, and user-editable scripts.
source = String.join "\n" [
"module TestPlugin",
"",
"export double = fn(n: Int) => n * 2",
]
match Script.load-source auth "test-plugin.kit" source
| Ok script -> Script.call auth script "double" [Script.Int 21]
| Err e -> Err e
For production-style plugin loading, prefer kit script build plus
Script.load-bytecode. It keeps the host path smaller and validates the
artifact before the VM runs it.
Value Boundary
Script calls accept and return Script.Value. The boundary intentionally
uses simple values that can round-trip through JSON.
| Script value | JSON shape | Use |
|---|---|---|
Unit |
null |
No meaningful value |
Bool, Int, Float, String |
Native JSON values | Primitive data |
Keyword |
{"$keyword":"name"} |
Keyword tags |
Handle |
{"$handle":42} |
Opaque host object ids |
List |
JSON array | Ordered values |
Record |
JSON object | Named fields |
Closures, files, sockets, channels, refs, and arbitrary host objects do not cross this
boundary. Use Handle when a script needs to refer to a host-side object,
and use Script.to-json or Script.from-json when integrating
with JSON-based host data.
Errors
Script operations return Result values with structured ScriptError
cases.
type ScriptError =
| ParseError {message: NonEmptyString, file: String, line: NonNegativeInt, column: NonNegativeInt}
| TypeError {message: NonEmptyString, file: String, line: NonNegativeInt, column: NonNegativeInt}
| LoadError {message: NonEmptyString}
| FunctionMissing {name: NonEmptyString}
| ArityMismatch {name: NonEmptyString, expected: NonNegativeInt, actual: NonNegativeInt}
| RuntimeError {message: NonEmptyString}
| PermissionDenied {capability: NonEmptyString}
Bad source becomes ParseError or TypeError with file, line,
and column. Missing exports become FunctionMissing. Wrong argument counts
become ArityMismatch. VM failures, including recursion and resource limit
failures, become RuntimeError.
Capabilities
Application code should use Auth.Script.script-auth. The current
authority is backed by FileReadAuth because source files and bytecode
artifacts are read from the file system.
import Auth.File.{file-auth, file-read-auth}
import Auth.Script.{script-auth}
import Script
main = fn(env: Env) =>
file = file-auth env.root
read = file-read-auth file
scripts = script-auth read
Script.load-bytecode scripts "mods/enemy.kitbc"
ScriptRaw.exports-json, ScriptRaw.call-json, and
ScriptRaw.load-source-json are implementation details used by
Script. They enforce file-read capability checks directly.
Runtime Limits
Script calls run in the bytecode VM with default safety limits:
max_frames: 128max_instructions: 10,000,000wall_time_ns: 5 secondsmax_allocated_bytes: 64 MiB
These limits catch recursive or runaway scripts and report them as runtime errors. The public Kit wrapper uses the defaults; lower-level Zig integration can provide custom VM setup when embedding scripts directly.
Current Shape
Runtime scripting currently includes source loading, bytecode artifact loading, export discovery, named function calls, structured errors, JSON-backed value conversion, opaque handles, CLI build/call/export helpers, artifact metadata validation, and bytecode VM limits.
It does not expose a general eval builtin, a public host callback
registry, direct host object references, or separate script-file/source/host
authority types.