Languages
Seven runtimes. The language is chosen by file extension, and --runtime overrides that when the
extension is missing or wrong.
| Extension | --runtime | Shape it expects |
|---|---|---|
.js | javascript | A function, called by name |
.lua | lua | A function, called by name |
.star | starlark | A function, called by name |
.go | go | An exported function — Main, not main |
.expr | expr | A single expression |
.cel | cel | A single expression |
.wasm | wasm | A compiled module |
--runtime takes a language, not an engine
Section titled “--runtime takes a language, not an engine”The flag’s help text mentions values like v8 and goja. Those are rejected:
$ kis script compile hello.js -r v8compilation failed: unsupported language: v8So is the abbreviation js. Use javascript.
The function languages
Section titled “The function languages”JavaScript, Lua and Starlark work the same way: define a function, the engine calls it, the return value is the result.
function main(name, count) { return { greeting: `hello ${name}`, count: count };}function main(name, count) return { greeting = "hello " .. name, count = count }enddef main(name, count): return {"greeting": "hello " + name, "count": count}All three receive positional arguments and reach the host namespaces.
Go is the exception
Section titled “Go is the exception”The Go runtime calls an exported function, so the entry point is Main with a capital M:
package main
func Main(x int) map[string]any { return map[string]any{"doubled": x * 2}}A lowercase main produces:
execution failed: function 'main.Main' not found: undefined selector: MainThe same applies to any entry point named with --func — export it.
The expression languages
Section titled “The expression languages”expr and CEL evaluate one expression. There is no function, no statement, no entry point:
$ echo '1 + 1' > calc.expr$ kis script run calc.expr --debugResult: 2They are built for embedding, and that is where they earn their place: a computed field, an access rule, a routing condition. In those positions the host supplies the environment — the row, the request, the record — and the expression names it directly.
At the CLI there is no host, so there is nothing to name. --vars and positional arguments do not
declare variables for these two, because compilation happens before values bind:
$ kis script run doubler.expr --vars x=21compilation failed: expr compilation error: unknown name x (1:1)So use the CLI for these the way it is useful: checking that an expression parses and that a constant folds the way you expect, before pasting it into the schema or rule that will supply its environment. For anything needing input at the CLI, write JavaScript.
WebAssembly
Section titled “WebAssembly”A .wasm file is a compiled module, built elsewhere and executed here. Reach for it when the work
is CPU-heavy enough that interpretation costs real time, or when the logic already exists as a
compiled artifact.
There is no source for the engine to check, so validate confirms only that the binary is a
well-formed module.
Reading a rejection
Section titled “Reading a rejection”Compiling one file under several languages is a quick way to see what each one is:
$ kis script compile hello.js -r luaerror: hello.js line:1(column:17) near '{': syntax error
$ kis script compile hello.js -r exprerror: unexpected token Identifier("main") (1:10)
$ kis script compile hello.js -r wasmerror: invalid magic numberEach message names the real reason: Lua does not use braces that way, expr wants an expression
rather than a declaration, and wasm expects a binary rather than text.
Starlark accepting a JavaScript-shaped function is not permission to write JavaScript in it. The syntaxes overlap far enough to compile and diverge immediately afterwards.
Choosing
Section titled “Choosing”| The script | Language |
|---|---|
| Calls host namespaces and has real control flow | javascript |
| Is untrusted or model-generated, and should be constrained by construction | starlark |
| Is one condition or one calculation, embedded in a schema or rule | expr or cel |
| Is CPU-heavy and compiled elsewhere | wasm |
| Is maintained by a team that already writes Lua or Go | lua or go |
JavaScript is the default for a reason: it has the widest namespace coverage, the most examples, and the least friction. Choose something else when you have a reason, not a preference.
Starlark’s reason is worth spelling out, and worth being precise about. The language has no I/O of its own: no clock, no filesystem, no network, no unbounded recursion. Everything it can reach, the host handed it — which means the reach is something you decide rather than something you have to audit for.
That is not automatic. By default kis script grants every namespace, to Starlark as much as to
JavaScript:
def main(): return shell.execute({"script": "echo hi", "capture": True})runs, and shells out. The guarantee comes from pairing the language with an explicit grant:
kis script run generated.star --namespaces noneNow the language contributes no authority and the host contributes none either. For logic you did not write and cannot fully review, those two together are a stronger position than restricting a more capable language after the fact. See What a script can reach.
Verifying the choice
Section titled “Verifying the choice”kis script compile transform.js --runtime javascriptCompiling with javascript runtime...✓ Compilation successful (95.377µs)Source size: 59 bytesCompile under the language you will actually configure, not the one you assume. A file that
compiles as javascript says nothing about whether it compiles as starlark.
See also
Section titled “See also”- Calling convention — entry points and arguments
- Commands —
validate,compile,bench - What a script can reach — namespaces and restriction