Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit44fd9e59bd

README.md

tool (asset type)

A tool is one named operation: a single luau typed function inside a toolboxe's <name>.tool asset, callable as <toolbox>.<tool>. The function's signature is the tool's interface, so anyone can call it correctly without reading its code.

A tool is also a shell command, zero <toolbox> <tool> <args>; guides core/tools, "A tool's command line", covers its flags, paths, render.luau and exit status.

Each tool has a declared input shape and return shape; the engine wraps each call's result in an envelope.

Why use a tool?

Tools are typed operations with a known serialized output format, made to enable efficient operation in the engine and provide an easily callable interface for operations around a topic.

They are also useful in that they can be called from a multitude of surfaces, from execute, to bash to tool_exec to users Tools portion in the edui. Compared to say a module.assetType, that might contain multiple functions with varying shapes, experct references or specific formats of tables as input and could return potentially anyting including flat tables. that has multiple callable functionm in that they might return anything a tool is meant to be called with a serialized value, and return a serialized value.

A tool is meant to perform an self contained operation and return results/info/relevant data in a readable format, including eg. image data.

Whenever you identify an operation that requires specific orchestration and or is something that could be a repeated operation and makes it easier to manage the project/task or when you want to expose logic written in luau to the other call surfaces like bash under its zero command, it propably should be a <name>.tool asset.

How tools are written

In its simplicity, a tool is a single Typed luau function that returns a value. written plainly, in its basic form it could look like this.


-- probe.toolbox/onCall.tool
--
-- Fire when a named function runs, seeing its arguments and what it returned.

--- A toolbox offered shared module, that in this case,contains a type shared by the tools in their toolbox.
local shared = require(".shared")

local M = {}

export type OnCallResult = {
    ok: boolean,
    id: string?,
    result: string?,
    error: string?,
}

--!desc example tool
--!arg path target path for the operation
--!arg opts `{ notice, when?, run?, capture?, label?, limit?, cooldown?, freeze?, silent? }`.
--      `capture` is Luau, compiled against the hit's context the way `run`
--      is, that returns a picture for the hit — a capture tool you aim:
--      `return tools.use("capture", "fromEntity", { entity = entity })`. It
--      runs once the hit is recorded, and the hit's notice then carries the
--      picture under `shot`, delivered as a picture.
--!return `{ ok, id, result }`.
--!example "arg1", arg2
typed function M.gatherResults(path: string, opts: shared.ArmOpts): OnCallResult
    local modulePath, key = splitPath(path)
    --- the tool code implementation
    local results = myCodeResults
    --- the tool call returns a table with keys, that is then wrapped in a return envelope. 
    return { ok = true, id = results.id, result = results.gathered }
end

return M


Now when called, the tool runs and returns the envelope with the inspectable results.

Why return plainly, and fail by raising

Every caller gets a tool's outcome in the same form, success or failure, so return the value itself and raise an error when the tool can't do its job. A tool that returns its own { ok = false } reads as a success that happened to return that table. Make the error say what to do instead: the caller reads it and acts on it.

What the shell prints

zero <toolbox> <tool> prints the text the tool's render.luau states: a module in the tool's folder returning one function (result) -> string | { out, err?, exit? }. A returned string is the output, and { out, err?, exit? } sets the output, the standard error text and the exit status. A call that raises exits 1, and a command line the shell cannot bind to the tool's parameters exits 2.

When to answer later instead of blocking

A caller waits for the tool to return, and might be calling multiple tools at the same time. Should your call run past the waiting threshold in order to return, even if it yields it comes back as a handle to a background task that will then complete on its own, but require the caller to separately read the task results. For a call that is small, answering inline with the result is better. When a tool can tell that this particular call will be long (a big bake, a large batch, a slow fetch), it hands itself to the background with task.background() early, so the caller gets the handle at once and carries on with other work.

When to post a notice

The return value is the answer to what was asked. A notice is for what the caller needs to know besides that: a side effect it didn't ask for, a return to one that has no caller, notices should be treated as a way to make sure your tool can return quickly, when you arent sure how long it takes for the results to be ready. It allows the agent to continue working, and then react when the notice fires. notices.post delivers it to the caller, and an agent that is idle is woken by it, so the caller learns it without polling. Name in it what to do about it. A notice for something the return value already says is noise.

Returning more than text

Tools also support returning images, that the agent can then directly read from the tool response, regardles of what surface they used to call the tool. Images also work on notices, so you can eg attach visual results to the eventual outcomes of your tool results and the agent will see them the second the notice fires.

What a tool may do that other code may not

A tool, and code only tools call, may act on whatever an input names, resolving an asset by a name that only arrives with the call. That is what makes a tool the place for operations that depend on what they are given. Tools are for working on a world rather than running in the game: a component may not call one unless it declares declare { componentSafe = true }, and a tool doesn't call another tool, so logic several tools need goes in the toolbox's shared module or exist elsewhere.

For information how to group tools and organize them, and how they are called read toolbox.assetType readme.

In the Inspector

Tool: how the tool is called (its signature), what it does, each argument, what it returns, and its examples, with Open for its source.