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