Log inGet started

Generating assets & content

Updated 6 September 2026

Generation is driven through the services toolbox: discover what you can make, start a run, track it to a finished asset. You never resolve or invoke a generator by hand — you work the tool rail, and every generator behaves the same way regardless of what it produces.

What you can generate

Each generator is a service the toolbox lists for you, self-describing down to each operation's inputs and credit cost:

use_tool { toolbox = "services", tool = "list" }
-- mesh_gen   → a textured 3D mesh (GLB), from a prompt or a reference image
-- image_gen  → an image (PNG) from a prompt
-- pbr_gen    → a PBR material (albedo / normal / roughness / …) from a prompt
-- audio_gen  → music, a sound effect, or speech from a prompt
-- anim_gen   → a humanoid animation clip from a text prompt
-- world_gen  → a navigable gaussian-splat world from a prompt

Each service exposes one or more operations, and list reports every operation's inputs and credit cost — so you read what to pass without looking anywhere else.

The set is open — read list rather than memorising it, and check balance for the credits a run draws from:

use_tool { toolbox = "services", tool = "balance" }

Three surfaces, pick the one that matches where you are

Like every toolbox, the services tools answer on three surfaces, one per context (the tool system is covered in full in the tools guide):

zero, from the shell. zero services lists the verbs and zero services <verb> --help prints one verb's arguments and types, so you can read the call before you make it:

zero services list
zero services generate mesh_gen '{"prompt":"a wooden treasure chest"}'

use_tool — the MCP call, when you're driving the engine from outside. search_tools finds a tool, use_tool runs it. Pass the toolbox, the tool, and positional args:

use_tool { toolbox = "services", tool = "generate",
           args = ["mesh_gen", { prompt = "a wooden treasure chest" }] }
-- → { id = "gen_0", service = "mesh_gen", operation = "generate" }

tools.use(toolbox, tool, ...) — from Luau, when you're authoring in code. Inside an execute() call it runs the tool and hands back the value directly (raising on failure):

local g = tools.use("services", "generate", "mesh_gen", { prompt = "a wooden treasure chest" })
-- g is the handle { id, service, operation }

All three do the same thing; the examples below use the Luau form for brevity.

Kick off a generation, then track it

generate returns immediately with a handle; the run takes minutes and outlives the call. Poll status across turns — each call is quick — until status == "completed", then the row's asset is the spawnable asset:

local g = tools.use("services", "generate", "mesh_gen", { prompt = "a wooden treasure chest" })

-- in a LATER turn rather than busy-polling this one:
local s = tools.use("services", "status", g.id)   -- { status, progress, asset, error, … }
if s.status == "completed" then
    -- s.asset is the imported, spawnable asset
end

The run imports its raw output into a spawnable asset — a mesh becomes a .bundle (mesh + material + texture), an image a .texture, a sound a .audio. s.asset points at that imported asset, ready to spawn / apply / play. A run that can't proceed fails with a legible reason on s.error ("out of credits", "not signed in", "upstream 401") rather than finishing empty. jobs lists every run this session; outputs lists what you've generated across sessions.

Dropping it into a scene you've already laid out

You don't have to stare at an empty slot while a generation runs, and you never have to leave a rough stand-in in the finished scene. Lay the scene out, then replace each entity with its generated asset in place — same position, rotation, scale, name, and parent — with appearance.replaceWithContent:

local g = tools.use("services", "generate", "mesh_gen", { prompt = "a carved oak chest" })
-- …poll services status to completion…
local asset = tools.use("services", "status", g.id).asset
tools.use("appearance", "replaceWithContent", "chest_slot", asset)

The entity named chest_slot (and its subtree) is removed and the generated model takes its exact place. This is how a scene blocked out with stand-ins becomes the finished thing — you swap each placeholder for the real, generated (or library) asset rather than shipping the stand-in.

Inputs beyond text

Most operations take a prompt, but inputs are whatever the operation declares — numbers (width, height), booleans (seamless), a public asset URL, or a local asset (e.g. feed an image to mesh_gen's from_image operation). The list tool shows each operation's exact inputs, so read it rather than guessing.

The two inputs every call takes

Alongside an operation's own inputs, the input table carries two fields the call itself takes. list publishes both on every operation, marked framework = true:

  • operation — which of the service's operations to run, by name. Each entry's default names the operation a call runs when you leave this out; where it is absent the service refuses the call and names its operations, because picking one for you would spend credits on a guess, and list marks operation required there. Read the entry rather than assuming: a service can offer several operations and still run one of them unasked.
  • asset_path — where the run writes what it generates: a full path carrying the operation's extension. Leave it out and the asset lands at /zero/source/generated/<kind>/<the prompt, slugified>.<ext>, which is fine for a probe and unhelpful for content you are filing into a project. An operation that produces a set of files (pbr_gen's map set) authors its own folder, so it does not take asset_path and refuses one.
-- a non-default operation, and a name of your choosing for what comes back
tools.use("services", "generate", "audio_gen", {
    operation = "speech",
    text = "Welcome aboard.",
    asset_path = "/zero/source/game/audio/lines/open1.mp3",
})

generate also takes the operation as a third argument, which is the same thing said in the other place — generate("audio_gen", { text = "…" }, "speech"). Name it once; naming it twice differently is refused.

Credits

Generating consumes credits from your ZeroMind account — when a bot generates, from the human who owns the bot. Metering is server-side: a service names a provider offering and a logical endpoint; ZeroMind resolves the real upstream, injects the provider's secret, debits the price, and relays the result. The offering identity and the secret never appear in the service or your world. Read a run's cost up front in the list output, and check balance before a batch.

The throughline

Reach for the ZeroMind library and asset.list to see what already exists before building anything by hand; when the asset you need isn't there, generate it from a description through the services toolbox, then swap it into place. Authoring your own generator — becoming a provider others spend credits on — is the .service asset type; its README (types/service) is the worked reference.

  • documentation
  • guide