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.
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.
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
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.
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.
onboard-capability skill runs all six stages, opens the PR and reports back. It still pauses to confirm billable probes and node changes, deliberately.You do not type verb names. Say the outcome; the agent picks the verb. This is the whole vocabulary:
| Say this | It 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_route — ask 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_get → cap_status + cap_route per cap |
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.
Someone posts that MiniMax Music 3 is on fal: complete songs up to five minutes, holding theme and vocal identity across the whole length.
lyrics requirement matters: music and sonilo-t2m take a prompt alone, so this one cannot be a drop-in sibling for them.
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.
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.
cap_docs returns both files with the derivable facts filled in and TODOs where judgement is needed. Then cap_probe — which 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.
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.
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.
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.
cap_route on each:
"realesrgan-x4plus-upscale" → topaz-upscale unregistered_name_absorbedcap_status per capability for the rest.
open: priced (no cost basis), measured (never invoked)
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.
cap_probe for the measurements (asking to confirm the spend first), and orch_update to flip registered → live. Then cap_status on each to confirm.
cap_route under the hood — is what separates a capability users can reach from one that quietly runs something else.The one step with two tracks. Both end with the capability existing; neither makes it reachable by name yet.
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.
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 ✓.
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 says | What to do |
|---|---|
no_cost_basis | Add 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_mismatch | Someone hand-edited a price. Regenerate. |
| basis EXPIRED | Re-read the provider page, or ask the operator, and update verified_on. |
| resolution unpinned | The quote is a lower bound — real cost floats with the provider's default. Pin the tier or accept the risk knowingly. |
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.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:
| Finding | Meaning |
|---|---|
unregistered_name_absorbed | The agent does not know this name and matched it onto another capability. This is the 8-of-9 failure. |
name_collision | It is registered but still resolves elsewhere — usually a prefix tie-break with a similarly-named model. |
chain_input_mismatch | A fallback needs a different input than the primary. That retry cannot succeed and is billed anyway. |
no_output_kind | No 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:
FALLBACK_CAPABILITIES and CAPABILITY_KIND;INPUT_REQUIREMENTS;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 ✓.
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.
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.
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.
| Verb | When |
|---|---|
orch_healthcheck | Reconciles 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_update | Promote an orchestrator registered → live after verification, or deactivate it. |
orch_list / orch_get | Who is onboarded, which capabilities, per-capability status. |
orch_email | Ask an operator for what only they have — a declared price, a fix. Dry by default; pass send to deliver. |
cap_deprecate / cap_retire | Steer to a successor; tombstone a name forever. |
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.
cap_status returns COMPLETE, CI is green, and it is deployed. Anything less is NAME_ONLY — shippable, honestly labelled, and not yet chosen automatically.