Livepeer Agent · Admin & ops

Register a capability — the admin runbook

Step by step, in verbs: take a capability from nothing to agent-ready, whether it comes from a provider like fal or from a community orchestrator. Only the first step differs by source; everything after it is identical.

This is the task guide. For why each stage exists and what "done" means, read Onboard a capability — the six stages.

Read this before anything else. Registering a community orchestrator does not make its capabilities reachable by name. orch_register writes the provider registry, which is what routes Live Runner traffic. The agent's own resolver is a separate set of tables.

An audit on 2026-08-13 found 8 of 9 live capabilities on one onboarded orchestrator resolved to a different capability — rembg-bg-remove ran flux-dev, so a background-removal request was served by text-to-image. Nothing looked wrong, because every one of them returned media. Step 3 below is what prevents this, and it is not optional.

The shape of it

  step 1   register            ← the ONLY step that differs by source
  step 2   price               cap_price
  step 3   make discoverable   cap_route          ← the step people skip
  step 4   document            cap_docs
  step 5   measure             cap_probe
  step 6   promote             cap_promote        ← optional; explicit-name is a valid ship

  at any point: cap_status  →  which stage is open, and the exact next action
Run this first, and after every step:
cap_status({ capability: "<name>" })
It audits all six stages and names the file or verb for each gap. Read-only and free. Every step below ends by telling you to re-run it, because it is the only thing that tells you whether the step landed.

The one-call version

If you would rather not walk the steps, cap_plan returns the whole work order in a single call — the six-stage audit, the collision check, the price at the house margin, the drafted docs, and the probe step, each tagged with who does it:

cap_plan({ capability: "minimax-music-3", model_id: "minimax/music-3",
           upstream_usd: 0.002, unit: "second" })
minimax-music-3 — onboarding plan (provider)
current: NOT_STARTED — not registered anywhere

•  1. 1 · register       [off-platform] add model id "minimax/music-3" to the provider node's
                         capability config and restart the adapter…
•  3. 2 · price          [repo] upstream_usd 0.002 per second → ships at $0.0021/second (+5%)
✗  4. 3 · discoverable   [repo] NAME COLLISION — resolves to "minimax-h3-i2v". Registering the
                         name alone will NOT fix this…
•  7. 6 · measure        [verb] cap_probe({ …, n: 5, confirm: true })
✗  8. gate · promote     [verb] refuses until every stage above is clear

2 blocked, 6 ready to do. Clear the blocked ones first.

verbyou can run it from here   repoa file you edit and review in a diff   off-platforma change on the provider node

It composes cap_status, cap_route, cap_price and cap_docs rather than re-deriving them, so it cannot tell you something those verbs would contradict. It does not fetch the provider's page — pass the facts you read — and it does not call orch_onboard; it names it, because that verb is admin-gated.

Why it plans rather than does. Four of the six stages write source files that are reviewed in a diff, and this server is serverless with no repo. That is a property of where the code runs, not a missing feature. In an agent session that DOES have the repo — Claude Code in the project — the whole thing really is one request: say “onboard MiniMax Music 3 end to end” and the onboard-capability skill runs all six stages, opens the PR and reports back. It still pauses to confirm billable probes and node changes, deliberately.

What to say

You do not type verb names. Say the outcome; the agent picks the verb. This is the whole vocabulary:

Say thisIt runs
“Plan the onboarding for <name>”cap_plan — the whole work order in one call
“Get this fal cap online: <model url>”reads the provider page → proposes name + price → registers
“Onboard this orchestrator: <discovery url>, contact <email>”orch_onboard
“Is it actually reachable by name?”cap_routeask this every time
“What does a <N-second / 4K / rigged> job cost?”cap_price
“Draft its skill and playbook”cap_docs
“Probe it 5 times at the cheapest settings”cap_probe — shows the bill, renders only on your confirm
“Is it done?” / “What's still open?”cap_status
“Can it go into automatic routing?”cap_promote
“Ask them what they charge”orch_email (drafts first, sends on your say-so)
“Mark them live” / “Are they still healthy?”orch_update / orch_healthcheck
“Audit everything <orchestrator> is serving”orch_getcap_status + cap_route per cap

Two worked sessions

You do not call these verbs by name. You say what you want in an admin MCP session and the agent picks them — the value of knowing the verbs is being able to check that it picked the right ones, and to read what comes back. Both sessions below are real: the routing findings are what these names actually resolve to today.

Session A — a new provider model

Someone posts that MiniMax Music 3 is on fal: complete songs up to five minutes, holding theme and vocal identity across the whole length.

youGet this fal cap online: https://fal.ai/models/minimax/music-3
agent Reads the model page and its API schema, then proposes a name and shows you what it found before touching anything: model id minimax/music-3 · $0.002 per second of audio · up to 300s
requires BOTH prompt and lyrics — it is not a plain text-to-music
returns audio.url, plus seed and actual duration
The lyrics requirement matters: music and sonilo-t2m take a prompt alone, so this one cannot be a drop-in sibling for them.
youBefore we register it — will the name work?
agent Runs cap_route on the proposed name, and this is why the question is worth asking: "minimax-music-3" → minimax-h3-i2v unregistered_name_absorbed A music request would be served by an image-to-video model, because minimax- prefix-matches that first. It also crosses the price by a wide margin — $0.16 per second against $0.0021. Registering the name alone would not fix it; it needs an explicit resolver guard ahead of prefix matching, which is step 3.
youRegister it, then show me what a five-minute song costs
agent Adds the capability, then cap_price: $0.0021/second from a basis verified today · 300s song ≈ $0.63 Worth checking against its neighbours rather than against memory: sonilo-t2m ships at $0.00263/s, so this is about 20% cheaper per second of finished music — not the order of magnitude an older note in CLAUDE.md ($0.105/s) would suggest. Read the shipped price from static-pricing.json; a stale number in a doc is exactly how a comparison like this goes wrong.
youDraft its skill and playbook, then probe it five times at the cheapest settings
agent cap_docs returns both files with the derivable facts filled in and TODOs where judgement is needed. Then cap_probewhich renders nothing until you confirm: probes: 5 · per probe: $0.06 (30s) · total: $0.32 — re-run with confirm to dispatch Say “yes, go ahead” and it runs them, reserving against your 24h spend cap before each one.
youIs it done? And can it go into automatic routing?
agent cap_status for the six stages, then cap_promote. Promotion needs all six clear and ≥4 successful runs at ≥80%; if it refuses it names exactly what is missing. Until then the capability is live and usable by name — which is a real shipped state, not a half-finished one.

Session B — a community orchestrator

An operator says their runners are healthy on an RTX 5080 and visible through /discovery, offering realesrgan-x4plus-upscale (wire app realesrgan-upscale) and whisper-large-v3-asr.

youOnboard this orchestrator — discovery https://their-node:8935/discovery, contact them@example.org, mainnet
agent orch_onboard probes discovery, classifies which advertised capabilities are onboardable, and upserts the registry. It reports what it took and what it skipped, with per-cap reasons — send those straight back to the operator. onboarded: realesrgan-x4plus-upscale, whisper-large-v3-asr → status registered Where the agent-facing name and the wire app differ, the wire app is recorded alongside it, so realesrgan-x4plus-upscale dispatches to realesrgan-upscale.
youAre they actually reachable by name?
agent The question that stops this going wrong. cap_route on each: "realesrgan-x4plus-upscale" → topaz-upscale unregistered_name_absorbed
"whisper-large-v3-asr" → whisper-word unregistered_name_absorbed
Both registered, both live on the operator's box, and neither reachable — each silently answered by a different model. This is the 8-of-9 finding reproducing itself in real time.
youFix the routing for both, and tell me what's still open
agent Makes the four edits per capability — capability set, output kind, an explicit resolver guard, the input requirement — updates the resolution golden so it is reviewable, then cap_status per capability for the rest. open: priced (no cost basis), measured (never invoked)
youAsk them what they charge for each
agent orch_email, dry by default so you see the draft before it sends. A community price is declared, not published — it comes from the operator and carries an expiry. The agent will not invent one, and neither should you.
youThey replied with prices. Add them, probe both, and mark the orchestrator live
agent Records each basis with its expiry, regenerates the prices, cap_probe for the measurements (asking to confirm the spend first), and orch_update to flip registeredlive. Then cap_status on each to confirm.
The one sentence to take from both: after anything that adds a capability, ask “is it actually reachable by name?”. That single question — cap_route under the hood — is what separates a capability users can reach from one that quietly runs something else.

Step 1 — register

The one step with two tracks. Both end with the capability existing; neither makes it reachable by name yet.

A · Community orchestrator

One verb. It probes the operator's /discovery (SSRF-guarded), classifies which advertised capabilities are onboardable — priced, single-shot, non-scaffold — and upserts the registry.

orch_onboard({
  discovery_url: "https://their-node:8935/discovery",
  contact_email: "them@example.org",
  network: "arbitrum-one-mainnet"
})

It returns what it onboarded and what it skipped, with per-cap reasons — send those back to the operator. Capabilities land at registered.

Use orch_register instead when you are hand-supplying the capability list rather than probing discovery.

B · Provider model (fal & co)

Verify the model id on the provider's own page first — providers rename models, and a wrong id fails at dispatch, not at registration.

Then a config change on the provider node (add the entry to the capability config and restart the adapter so it re-reads it), plus the code entry in lib/sdk/capabilities.ts.

Copy the wire price_per_unit from a same-modality sibling. Never invent it — a bumped wire price fails every inference with "Insufficient balance".

Verify: the capability appears on /capabilities with a display price. Then cap_status — stage 1 should be ✓.

Step 2 — price it

cap_price({ capability: "<name>" })
cap_price({ capability: "<name>", params: { duration: 30, resolution: "720p" } })

The first call audits: is there a verified cost basis, and does the shipped price equal that basis at the house margin? The second quotes a specific request through the same estimator create_media uses, so you see what a real job costs — tiers, per-call add-ons, minimum billed quantity and all.

What it saysWhat to do
no_cost_basisAdd a row to the cost-basis table: the number, the unit, the date you read it, the source URL. Then regenerate — never hand-edit a generated price.
margin_mismatchSomeone hand-edited a price. Regenerate.
basis EXPIREDRe-read the provider page, or ask the operator, and update verified_on.
resolution unpinnedThe quote is a lower bound — real cost floats with the provider's default. Pin the tier or accept the risk knowingly.
A community price is declared, not published. There is no provider page to read, so the basis comes from the operator and must carry an expiry — otherwise "verified" stops meaning anything. Ask with orch_email. Do not invent a price for someone else's capability.

Step 3 — make it discoverable the step people skip

This is where a registered capability becomes one the agent can actually route to. Check it before you believe it:

cap_route({ query: "<name>" })

You are looking for “resolves to itself”. Anything else means a caller naming this capability is served something else:

FindingMeaning
unregistered_name_absorbedThe agent does not know this name and matched it onto another capability. This is the 8-of-9 failure.
name_collisionIt is registered but still resolves elsewhere — usually a prefix tie-break with a similarly-named model.
chain_input_mismatchA fallback needs a different input than the primary. That retry cannot succeed and is billed anyway.
no_output_kindNo declared output kind — the result will be handled as the wrong media type.

The fix is four small edits, and it is the same four every time:

  1. add the name to FALLBACK_CAPABILITIES and CAPABILITY_KIND;
  2. add an explicit resolver guard before prefix matching — without it the absorption comes straight back;
  3. declare its INPUT_REQUIREMENTS;
  4. update the resolution golden so the change is reviewable in a diff.

Also give it a latency prior and a timeout entry. A capability with a prior and no timeout gets cut off at the default and billed — see the ceiling note at the bottom.

Verify: cap_route says SAFE, and cap_status stage 3 is ✓.

Step 4 — document it

cap_docs({ capability: "<name>" })

Returns a skill and a playbook as content, not files — the derivable facts (kind, price, tiers, add-ons, input, chain, measured latency) already filled in, with TODOs everywhere judgement is needed. Fill those in and commit them.

The three TODOs are the only parts worth reading, and none can be generated:

The playbook must compose: a brief that calls one capability and stops is a model card. Show it working with a finishing or export step. The showcase needs real rendered media — no placeholders.

Step 5 — measure it

cap_probe({ capability: "<name>", inputs: { /* cheapest legal settings */ } })        // plan only
cap_probe({ capability: "<name>", inputs: { … }, n: 5, confirm: true })              // renders

Without confirm you get the plan and the estimated total and nothing renders. With it, up to 5 real renders, each reserving against the same 24h spend cap create_media uses before dispatching.

Probe at the cheapest settings that still exercise the real path — lowest resolution, shortest legal duration. The point is the contract and the latency, not the picture.

It prints the SLA, timeout and measured-p95 values to paste. It does not write them: those tables are read on every dispatch, and one bad sample must not re-time the fleet. Size the timeout from the maximum duration the capability accepts, not from the probe you happened to run.

Then write the smoke report, including what you did not measure.

Step 6 — promote it (optional)

cap_promote({ capability: "<name>" })                  // dry run
cap_promote({ capability: "<name>", confirm: true })    // records it

Promotion moves a capability from reachable by explicit name to chosen for users — intent keywords, fallback chains, tier defaults. It requires all six stages clear and ≥4 successful runs at ≥80%.

A refusal is the normal answer for something new, and is not an error: it names the missing stage or the sample count. NAME_ONLY is a legitimate shipped state — the capability is usable, documented and priced; it simply is not picked automatically yet.

It records the decision and prints the routing-table edits rather than making them. Those tables are reviewed in a diff.

Keeping it working

VerbWhen
orch_healthcheckReconciles the registry against live discovery. A capability that stops being advertised is marked failing and drops out of routing on its own; one that reappears is restored.
orch_updatePromote an orchestrator registeredlive after verification, or deactivate it.
orch_list / orch_getWho is onboarded, which capabilities, per-capability status.
orch_emailAsk an operator for what only they have — a declared price, a fix. Dry by default; pass send to deliver.
cap_deprecate / cap_retireSteer to a successor; tombstone a name forever.

Auditing what is already onboarded

The same two read-only verbs, in a loop, over an orchestrator's capability list. Two minutes per orchestrator, no renders:

orch_get({ id: "<orchestrator>" })            // → its capability names
cap_status({ capability: "<each>" })          // → which stages are open
cap_route({ query: "<each>" })                // → is it silently absorbed?

That is exactly how the 8-of-9 finding surfaced. If you onboard an orchestrator, audit it the same day.

The 300-second ceiling. A long render can be cut at 300s by three separate layers: our HTTP client, the job timeout the client sends (which defaults to 300 and the orchestrator enforces), and the worker's own limit. The middle one is invisible unless you look — the render completes, the connection has closed, and the job is recorded failed and billed. The verbs and the CLI send the right value for you; a script calling the SDK directly must send it explicitly.

Done means

cap_status returns COMPLETE, CI is green, and it is deployed. Anything less is NAME_ONLY — shippable, honestly labelled, and not yet chosen automatically.