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 (browse and search), describe_tool (one tool's full schema, the MCP spelling of --help) and use_tool (run one). 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. 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
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 op registry answers to
procgen ops, 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.