Discovering the engine
Start with the job — skills
agent_skill with no argument lists every skill this world has: a packaged procedure for a whole job, carrying the systems it runs through and the checks that say it worked. If one matches what you are about to do, open it before designing anything. A skill is the already-correct path.
Then the kinds of thing — asset types
assets.types lists every registered assetType — the scaffolding you build inside. This is the layer that is easiest to skip and most expensive to miss: a job that has no matching tool very often has a matching type, already shaped for it.
assets.types <query>— what kinds exist, and which can become an entity in a scene.assets.typeDoc <name>— that type's own documentation: which file is its source, what regenerates when, what it handles for you.assets.typeBehavior <name>— what it can already do: theref:methods, the lifecycle hooks it runs, whether it is scene-capable.
"Generate this from parameters and let someone retune it" is a procGraph with a Generator. Ground is a terrain. Many of one thing is a population. Writing a module and a component that do the same work by hand is a project; building inside the type is a few lines.
Then the operations — tools
A tool is an operation the engine already implements, named and typed, grouped into a toolbox. Spawning a model, aiming a camera, baking lighting, taking a screenshot, reparenting a subtree, running the test suite — each is a tool someone already got right. Browsing the toolboxes is how you learn what the engine does at all.
From the shell, zero is the whole surface:
zero # every toolbox and what it covers
zero camera # the tools in one toolbox
zero camera lookAt --help # one tool: every argument, its type, whether it's required
zero camera lookAt --camera main --target 0,5,0
zero --search lighting # keyword search across every toolbox (short: -k)
--help answers at every level, which is the whole walk from "what exists" to a correct call: zero --help is the overview, zero camera --help lists that toolbox's tools, zero camera lookAt --help details one tool. You never have to guess a name or an argument.
Arguments are named (--camera main) or positional in signature order; --help prints both forms for the tool in front of you. A value that reads as JSON is passed as JSON — 12, true, [0,5,0], {"fov":60} — and a bare run of numbers like 0,5,0 becomes an array.
The same registry answers over MCP as search_tools, describe_tool (one tool's full schema, the MCP spelling of --help) and tool_exec (a script that runs one: tool_exec { script: "return await tools.use('zm', 'status')" }). search_tools { query } searches more than this world: on an engine bound to a world it asks the search over the whole published library for tools, toolboxes and workflows. Say what you need done in plain words. The hits a judge rated relevant come back best first, and this world's own matches, found by their words, are listed after them marked from: "world". Each row says whether this world already holds it (installed) and carries use, the one call that acts on it: tool_exec { script: "return await tools.use('<toolbox>', '<tool>')" } for a tool you have, search_tools { toolbox } for a toolbox, the same call to the workflow toolbox's start for a workflow, and for one this world does not hold yet bash { command: "zm ls-tree @<guid>" }, the read of its implementation that comes before any install, with install (zeromind_install { guid }) beside it for the candidate the reading shows fits. guides { query } does the same for the documentation: guides, agent skills and asset type READMEs, each row carrying use (guides { path }, agent_skill { name }, or the zm ls-tree read with its install call). mode: "keyword" on either reads only this engine's own files, by their words, and is what an engine with no world answers with. from says where results may come from, apart from how they are matched: from: "all" (the default) is everything published, and from: "bound" holds the search to the current world and its installed libraries, with no published-library leg, in either mode. tools.search, zeromind_search and discovery.search take it too, and discovery.search defaults to bound.
discovery.search runs a search over every type of content this world can reach: guides, asset types, modules, render features, materials, tools and more. Say what you need in q, in plain words: a semantic search decides which asset types the words are about and orders what a judge reads as relevant, best first, so leave assetType out unless the task fixes the type (naming one searches only that type, and a type guessed wrong answers nothing). A keyword search reads this engine's own files, and there assetType and match narrow it. The answer names the mode that ran, the scope it searched, and a status. Three surfaces mean three things: guides { query } searches the documentation, search_tools { query } searches what you can run, discovery.search searches everything. A guides query the documentation holds nothing for says so, and names discovery.search.
Search part by part: break the request into the systems and parts it needs (a first-person shooter needs a first-person controller and camera, weapons, hit detection, damage and health, enemy AI, a HUD, a level, audio and effects) and search for each as well as for the finished whole, and before writing any piece during the build, search for one that already does it. Never install the first plausible hit. For each part, shortlist two or three from their summary, their containers and their open_issues, then read each candidate's implementation from the shell, which installs nothing: zm ls-tree @<guid> lists its closure (files from other worlds are tagged), zm grep -n <pattern> @<guid> finds where it does the thing, zm show @<guid>:<path> reads a file, and zm log @<guid> is its commit history. Compare them, then zeromind_preview { guid } shows what an install would write. Install the one that fits, verify it does the job, and if it does not, delete what it installed and take the next candidate.
zeromind_inspect { guid } reads a hit's full record and, when the asset has a thumbnail, shows it as a picture with preview_description saying what it shows: the way to judge a mesh, a material or a scene by eye. Pass several guids to compare them; one call shows at most 4 thumbnails and says when it left some out.
From Luau code it is tools.use, which returns the tool's value and raises on failure:
local cameras = tools.use("camera", "list")
The right tool often lives in a toolbox you would not guess — re-skinning a hierarchy is under appearance, screenshots under capture, scene structure under scene, opening a file or your own text in the editor for the user to read under show. That is why browsing (zero with no arguments) is worth a call before searching for a name you have imagined.
Finding nothing that covers the task is a useful answer too: the work belongs in the API layer below, and once it works it is worth packaging as a .tool so the next reader gets it in one call.
Then the API — what tools are built from
The engine exposes well over a hundred namespaces (asset, entity, layers, mesh, compute, vfs, camera, players, ui, Material, anim, …) — its core callable API. That API is authored Luau: ordinary modules under /zero/source/libs/@builtin/modules/api/engine/, which everything else in the engine is built on top of. There's no hidden layer you can't see.
Ask the language server for the shape of any of it:
lsp.namespaces() -- every namespace (name, method count)
lsp.methods("asset") -- a namespace's methods, with signatures + docs
lsp.describe("asset/create") -- one method: signature, args, return, examples
Each of those answers with the surface you call. Under most namespaces sits an
engine primitive the module calls, installed under a __-prefixed name; the
listings leave it out and { includeInternal = true } puts it back.
Read the implementation when a signature isn't enough — it is the implementation:
ls /zero/source/libs/@builtin/modules/api/engine # the engine's API modules
rg "spawn" /zero/source/libs/@builtin # search all source
cat /zero/source/libs/@builtin/modules/api/engine/asset.module/init.luau # read how asset.* works
man asset/create # the generated reference page
And ask the content registry what exists:
asset.list("material") -- registered content, by type
asset.inspect("@builtin::materials.gold") -- a full summary of one asset
Confirm anything live
type(_G.asset) -- is it there?
Run the call and read what comes back. The running engine is the final authority: when a doc and the engine disagree, the engine wins.
Check what's broken
When something isn't behaving, ask the engine what's wrong rather than guessing — the diagnostics toolbox reports the current error state across assets, components, and the engine:
zero diagnostics errors # the active errors
zero diagnostics summary # a roll-up
zero diagnostics validate # re-check and report problems
(A single component's own errors are also on its instance — reportError/errors, the components guide.)
That is the current state. What actually happened, in order, is in the log ring — every level, every subsystem, plus every print and log.* a script made — and there are two ways at it. The logs toolbox queries it:
zero logs errors # recent errors and warnings
zero logs search --help # by severity, subsystem, text, script, entity, time window
zero logs summary # the shape of what is there before you read inside it
The same ring is also mounted as plain files, for when you can already name the string you are after:
grep -i "my_material" /zero/runtime/logs/engine # every level, most recent 500 lines
cat /zero/runtime/logs/errors # the ERROR-and-above subset of the same
cat /zero/runtime/logs/script # what scripts logged
cat /zero/runtime/logs/previous # how the previous session of this world ended, then its last lines
Reach for whichever fits the question. A message you can name is quicker to grep; a question with a shape to it — one entity's lines, what surrounded a failure, which message repeats and how often — is what the tools are for. The troubleshooting guide has the whole surface.
The method, in order
agent_skill— is this whole job already a packaged procedure?assets.types— is there a KIND of thing shaped for this? Thenassets.typeDocandassets.typeBehavioron the one that fits.zero— browse the toolboxes, then the tools in the one that fits. An operation that already exists is one call.- That system's own registry — a system's vocabulary is not in the tool registry. The procedural nodes answer to
procgen searchNodesandprocgen describeNode, not tosearch_tools; other systems keep their node types and templates the same way. No tool matching your job is not evidence the capability is missing. lsp.*/man/ the API source — the namespaces a tool is built from, for the work genuinely nothing covers.- A live call — the running engine settles every disagreement.
The concept guides — assets, components, the tool system, scenes, the engine, worlds — teach the systems; the steps above are how you find the specifics of any of them.
Skipping steps 1, 2 and 4 is the expensive mistake, because their absence looks exactly like the capability not existing. An agent that checks only tools, finds nothing, and starts writing will produce a working system that duplicates one already installed.