---
title: "Service (asset type)"
description: "A service generates content you don't have yet — a 3D mesh, an image, a PBR material, a sound effect, a humanoid animation, a navigable splat world — from the inputs each operation declares, landing…"
section: "Types"
slug: "types-service"
canonical: "https://origozero.ai/docs/types-service"
updated: "2026-09-06T05:08:08.622872245+00:00"
tags: ["asset-type", "reference"]
---

# Service (asset type)

A `<name>.service/` is a **declaration**, not code. The asset type ships the whole
runtime — the metered invoke, the submit → poll → download → write pipeline, the
async generation handle, error handling — in its `shared.module/`. An instance only
declares its surface: which provider **offering** it meters, and the **operations**
it offers. There is no HTTP, polling, credit, or secret code in a service.

## Using a service

Every service is used the same way, whatever it generates:

```luau
local mesh = asset.resolve("mesh_gen", "service")

mesh:operations()   -- callable operations, each with its inputs + credit cost
mesh:cost()         -- credits the next run costs (estimate)
mesh:balance()      -- credits you have

local g = mesh:invoke({ prompt = "a wooden treasure chest" })
--> { id, task, asset_path, operation }   -- async: the run is still going

local row = tools.use("services", "status", { id = g.id })   -- poll until it settles
-- once row.status == "completed", row.asset is the spawnable asset
```

`:invoke` returns immediately with a handle; the run finishes in the background. The
`services` toolbox (`status`, `jobs`) is the agent-facing way to track a run to
completion, and a completed row's `asset` is the asset to spawn — see the
generating-assets-and-content guide. The handle's `asset_path` is where the run
directs the raw generated file, which the importer turns into the asset that row
names.

A run outlives the engine that started it. The gateway keeps the job and its
result for hours after the charge, and the run keeps its own record in the world
— what it was asked for, the job doing the work, and the step it had reached — so
when the world next loads, every run that did not finish is picked up where it
left off and lands its asset the way an uninterrupted run does. Nothing is
submitted, or paid for, a second time, and nothing is asked of the caller.

A service with several operations selects via `input.operation`
(`mesh:invoke({ operation = "from_image", image = "/zero/source/.../ref.png" })`);
a single-operation service just takes its input.

## Authoring one

```luau
-- <name>.service/init.luau
local Service = asset.containing(__FILE__).modules.shared

return Service.define({
    name = "<name>",
    description = "Generate <thing> from a text prompt.",
    offering = "<provider>/<name>",      -- the offering ZeroMind meters
    output = { kind = "mesh", ext = "glb", dir = "/zero/source/generated/meshes" },
    operations = {
        generate = {
            description = "What this makes, agent-facing.",
            input = { prompt = { type = "string", required = true } },
            cost = 5,
            steps = { … },                -- the pipeline (see below)
            result = { url = "$url" },     -- the framework downloads + writes it
        },
    },
})
```

An operation takes exactly the fields it declares in `input`, plus the `operation`
and `asset_path` the framework itself reads. A call carrying any other field is
refused by name — ahead of the credit pre-flight and ahead of any endpoint — with
the fields the operation does take named in the message, so a field the steps have
no `$ref` for costs a message rather than a run. An operation that declares no
`input` states nothing about its surface and takes any field.

### Declaring the pipeline

`steps` is an ordered list of metered calls. Each step:

- `call` — the offering's logical endpoint name.
- `body` / `params` / `headers` — templated. `"$name"` injects an input or a prior
  step's bound value; `"${name}"` interpolates inside a string (a missing value
  drops the key); `"$name?"` is optional. `{ fromFile = "$path", dataUri = "image/png" }`
  sends a local asset; `{ firstOf = { a, b } }` uses the first present value;
  `{ scale = "$n", by = 1000 }` multiplies a number; `{ truncate = "$text", to = 64 }`
  clamps a string to at most that many characters, for an upstream that caps a
  field's length.
- `poll` — when the call submits a job: `{ endpoint, param }` plus optional
  `idFrom` / `statusFrom` / `done` / `fail` / `doneWhen` / `failWhen` / `progressFrom`
  overrides (sensible defaults cover the common providers).
- `bind` / `bindFrom` — name the value later steps reference; `bindFrom` extracts it
  (a dotted path, a fallback list `{ "a", "b" }`, or an array-find
  `{ find = "assets", where = { type = "x" }, get = "url" }`). For a polled step the
  default bind is the submitted job id.

`result` is either `{ url = "$bound" }` (download the URL and write it) or
`{ bytes = "$bound" }` (write bytes a step returned directly).

`output` sets where the asset lands (`<dir>/<safe(prompt)>.<ext>`); a caller's
`asset_path` overrides it and is taken verbatim. The derived name is a
truncation of the request, so several runs can derive one name — the same line
under two voices, two prompts that open on a shared phrase. The framework hands
each run a name no other run holds and no file in `<dir>` already spends,
appending `_2`, `_3`, … when it must, and the handle's `asset_path` is the name
that run will write. So a set of related generations can run at once and each
result is readable at its own path. The written file is then imported, and the
name is derived by the rule the importer names an asset with, so the name a run
reserves is the name its finished asset carries.

Read `mesh_gen` as the worked template:
`asset.resolve("mesh_gen","service"):getDefinition()`.

## Discovery

- `asset.list("service")` — every registered service.
- `asset.resolve("<name>","service"):info()` — offering, output, operations.
- `asset.resolve("<name>","service"):operations()` — each operation's inputs + cost.

## Related types

- `.module` — reusable library code you `require` and call.
- `.tool` — an agent-callable function that composes multiple engine steps.
- `.component` — per-entity behaviour.
