On this page

Renting our idle GPU

The fleet has one way of earning money back: when the queue has nothing for the GPU box, Stado offers that box on a compute marketplace, and it takes the offer down the moment our own work appears. The commands are the stado market group; the marketplace is an adapter named by --provider, and Vast.ai (--provider vast) is the one Stado carries. Flag-by-flag listings live in cli.

Nothing else in Stado earns. stado cost and stado billing read what the fleet spends (costs), and Monetizer reads commercial records without pricing or charging anything (monetizer).

What the bridge does

stado market auto-list --provider vast keeps the listing in step with the queue. Every --poll-interval-s seconds it reads the queue: idle means nothing is queued and no running job names this host. After --idle-window-s consecutive idle seconds it lists the machine at --price-gpu US dollars per GPU-hour, with --max-duration-s capping how long a single renter can book it (0 leaves the offer open-ended). None of these has a default: a price and a window are the operator's decision, so each is an argument.

When work appears, the offer is withdrawn immediately. A rental already running is never cut short: Vast keeps the box until the renter releases it or the duration cap expires, and the loop says so — waiting for Vast rental to finish — so an operator can tell that state apart from a dead agent. An exclusive inference reservation on the host withdraws the offer for as long as it holds.

On startup the loop adopts a listing that is already there, from a manual placement or an earlier run, and normalizes it to the configured price, so the next unlist reaches it.

stado serve --worker --vast-auto-list runs the same loop inside the worker role, which is how a host carries both the claim loop and the marketplace toggle without a second unit. A local worker turns it on by itself once the credential resolves; WC_VAST_AUTO_LIST=0 keeps it off.

stado market list --provider vast --price-gpu <usd> --price-disk <usd>, stado market unlist --provider vast and stado market status --provider vast are the manual half, and stado market monitor --provider vast [--bucket <namespace>] prints one snapshot: what Vast says about our machine, how many jobs the queue holds and runs (every one counted, in the configured bucket unless --bucket names another), and which Skarbiec channel the credential came through. The auto-list loop reads one queued name, which is all its busy decision needs, so its queued= log says whether the queue is empty, not how long it is; a running job record or capacity record it cannot read or parse stops that decision with the record's path instead of reading the host as idle.

Trying it without a credential

stado market auto-list --provider vast --dry-run calls no Vast endpoint. It reads the queue and prints the decision it would take:

startup: no Vast.ai credential; printing decisions only
DRY-RUN would list (idle 0s)

Without --dry-run the loop refuses without a key, because a loop that prints decisions it never applies is worse than one that stops.

--once evaluates a single poll and exits, so a preview can be asked for by something that has to answer — Stado Desktop's Earning screen runs stado market auto-list --provider vast --dry-run --once and shows what came back:

startup: no Vast.ai credential; printing decisions only
not listed, queued=2, free_vram_gb=1.0: the GPU is occupied and this host
holds no Vast credential, so no rental of ours is on it

The screen

Stado Desktop carries the same capability under Fleet → Earning: the readiness verdict with the commands that close it, the queue and marketplace snapshot, the one-shot preview, and the listing controls — GPU-hour and GB-month price — behind a confirmation that shows the exact command. It runs stado market --provider vast and prints the CLI's own sentences; it never words a refusal itself.

What it needs, and how to see what is missing

The Vast adapter authenticates with the item that plays the role vast (tagged stado:role:vast), field api_key; no item id is written anywhere. A host reads it through the control-plane consumer when it holds that bearer, and through its own agent grant otherwise — the worker that carries the loop has no control-plane bearer and must not have one. The machine it lists is WC_VAST_MACHINE_ID, or the one Vast reports for our account whose name matches this host.

stado market readiness --provider vast asks all three authorities in order — the channel this host holds, the vault that would hold the item playing vast, and Vast.ai itself with the resolved key — and exits non-zero unless Vast accepts the key. --json prints a stado.vast-readiness.v1 document with the same fields; its item field names the role.

Verdict What it means
ready Vast.ai accepted the key and answered for our machine.
vast_refused The key resolved and Vast.ai did not accept it.
item_absent No item in the vault plays role vast.
not_authorized The item playing vast exists and this consumer may not read api_key.
no_channel This host holds no Skarbiec bearer, so it cannot ask at all.
unknown The key did not resolve and the vault could not be read; two items playing vast also land here, with the vault error naming both.

The vault it asks is the one on the host the service directory names as serving skarbiec; --vault-host names another, and --no-vault-check skips that read on a machine with no fleet channel. Skarbiec answers a missing item and a missing grant with the same 403 consumer not authorized to read item field, which is why the report asks the vault directly instead of repeating that status.

Each verdict prints the commands that close it. For a missing item those are stado credentials item put --host <vault> --type api-key vast with an api-key document on stdin (it stores the item that plays the role), then stado credentials grant add --host <vault> --role vast --field api_key --token-file <file> <consumer>, then stado market readiness --provider vast again.

Refusals

A missing --provider, price, window or duration, or a provider Stado does not carry, is refused by the parser with exit 2 before anything is read. The adapter's own refusals — no key, Vast.ai's answer, an unreadable queue — exit 1 with the sentence that names them, and every one states its failure class:

Failure Class
No key, no machine id, a malformed variable config
Vast.ai cannot be reached infra_down
Vast.ai answered with a body that cannot be read or is not JSON infra_down
Vast.ai refused the request by its HTTP status, through the fleet's upstream status table: auth for a rejected key, not_found, rate_limit, infra_down for a server error, and refused for any client error the table leaves unclassified
The queue state cannot be read the store's own class

An empty or unreadable answer is never read as an empty listing: a body that could not be read is the failure above, not {}.

Source: this website