Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit3bb1da2647

README.md

toolExec

Run a script that drives the engine's toolbox catalogue.

This is the engine half of tool_exec. A harness hands over a script; this runs it on ZeroJS, the engine's own JavaScript runtime (a subset of modern JavaScript: the language and its standard library, with no Node or browser APIs), with exactly two globals — zero.<toolbox>.<tool>(...) to call a tool, and tools.toolboxes() / list() / describe() / search() / use() to find one. No engine globals, no VM.

It is not "execute, but JavaScript." A raw runtime with the engine's globals is execute with different syntax. What a shell command and a tool-calling loop both lack is the ability to hold an intermediate result: you cannot list 200 entities, filter them in-process and act on the 6 that matched without the whole list passing through the model. That is the gap — composition, not capability. Every tool reachable here is reachable from zero in bash and tools.use in Luau, under the same names.

A tool returns an engine value. Tables arrive as plain JavaScript objects and arrays; null and undefined arrive as themselves.

Every binding is async, and that is not a style choice. A tool may yield — zeromind.search makes an HTTP round trip — and a yield cannot cross the JS→Luau call boundary. The failure is silent: the call returns and its value is lost. So no tool is ever called from inside the interpreter. __start spawns a Luau task that may yield freely and hands back a ticket; __poll reads the settled result without yielding; the prelude turns the pair into a promise. The script says await zero.scene.list() and the yielding happens outside the VM, where it is allowed.

The caller's source is wrapped in an async function that opens on the caller's first line, so await works on the first line and the line numbers in a syntax error are the caller's own. ZeroJS has no top-level await, and an agent should not have to know that.

const boxes = await tools.toolboxes();
const camTools = await tools.list("camera");
const spec = await tools.describe("camera.lookAt");   // or tools.describe("lookAt") when one toolbox holds it
const hits = await tools.search("low poly tree");

const scenes = await zero.scene.list();
return scenes.filter(s => s?.name?.startsWith("level")).length;

A library's toolbox is registered as @<lib>::<toolbox>, and a script reaches it as zero.libs.<lib>.<toolbox>.<tool>(...). zero.<toolbox> reaches it too when no other toolbox answers to that name; a name several libraries hold is refused naming each holder, and a library or toolbox that does not exist is refused naming the nearest ones.

tools.describe(name) answers with the tool's signature, arguments, return type and examples. A bare tool name reaches the tool when exactly one toolbox holds it; a name several toolboxes hold, or none does, is an error that lists the candidates or the nearest names.

The result

run returns { ok, value, error, failure, trace, logs }.

FieldHolds
oktrue when the script finished and returned.
valueWhat the script returned. A script that returned a value but left work running past its time limit (an armed setInterval, say) keeps the value here with ok = false.
errorOne sentence saying what failed and where.
failureThe same facts as data: kind, message, completed (calls that succeeded), and step and tool when a call ended the script.
traceEvery call in order of completion: step (the order calls began), tool, ok, durationMs, and error for a call that failed.
logsOne entry per console call, formatted the way a JavaScript console formats it.

failure.kind is "tool" (a call's rejection ended the script, whether the script let it propagate, rethrew it in its own words, or never awaited it), "script" (the script threw by itself), "syntax", "timeout", "budget" (the call limit) or "setup".