On this page

Planning and applying a deployment

Planning and applying

The lifecycle is deliberately two-step and generation-fenced:

stado inference plan chat-primary \
  --host gpu-host \
  --image 'vllm/vllm-openai@sha256:<image-digest>' \
  --cache-dir /srv/stado/inference/chat-primary \
  --model 'example/model' \
  --revision '<model-revision>'
stado inference apply <plan-id>

plan inventories the host, requires Docker, NVIDIA tooling and ip (iproute2), reads every global IPv4 address of the host's interfaces that are up, and binds the endpoint on the first one the chosen --visibility holds; no Tailscale client is needed unless --visibility tailscale is chosen. It then saves an immutable plan bound to the current registry digest, locally under ~/.stado/inference-plans/<id>.json. A host with no address on the chosen network is refused naming it, the addresses it holds, and the visibility that would bind on one of them: <host> has no address the <visibility> visibility binds an endpoint on; its interfaces hold <addresses>: choose --visibility <other>, or connect the host to the <visibility> network (or only connect the host to the <visibility> network when no other visibility fits). The registry refuses a deployment whose endpoint.visibility is no visibility (… is no visibility; it is one of lan, tailscale), whose host is not an address that visibility binds on, whose protocol is not openai-chat, or whose host:port is another deployment's endpoint.

plan also reads, from the Hugging Face hub, the license the model repository declares at the planned revision and the license of each base model its card names, using the token of the Skarbiec role huggingface when one is stored. It records them in the deployment as model.license and prints license=<repo>: <license>, <base>: <license>; a repository or base model that declares none is printed as none declared with a license_warning= line, because serving it to outside callers is not permitted until it declares one. The license decides whether the model may be sold, not whether it may run, so a hub that cannot answer (a repository that does not exist, one gated beyond the stored token, or no network) does not stop the plan: it prints license_warning=the license of <repo> was not read (<cause>); …, records no license, and --json carries the cause as license_unread. stado inference list prints the recorded license in its LICENSE column, and not read; plan the deployment again for a deployment whose license was not read.

apply refuses, before touching the host, a deployment that is sold outside the fleet while its license was not read or its chain declares none: sold means a caller of it has a price (stado inference callers), it carries a pay-per-call offer, or a listed network node serves it (stado market node --deployment). The refusal names each sale and what the license lacks (<name> is sold outside the fleet (caller <c> pays for it; …) and its model chain declares no license for <repo> (…)); declare the license on the repository and plan again, or withdraw the sale. A deployment only the fleet uses is applied with the plan's warning.

apply executes one persisted plan only if that registry precondition still matches: it installs the container, waits for an authenticated readiness probe, and only then commits the deployment to the registry. A failed runtime, readiness check, or registry compare-and-swap restores the prior runtime.

Install refuses a host whose GPU has an unmanaged active compute process, an endpoint port already in use by anything other than this deployment's own container, and a host that already carries another inference reservation.

Who owns the board

--gpu-mode decides who owns the board. exclusive (the default) keeps the board reserved for inference, and no decision takes it. yieldable makes the local Stado agent the lifecycle owner, and the board goes to the use worth most per board-hour: when an eligible GPU job is queued, the agent values the job at the income its submitter stated (stado submit --income-usd-per-hour … --income-source …), else at the autonomy policy's budgets.internal_usd_per_gpu_hour, and the deployment at what outside callers paid for it (priced caller usage and paid calls) over the policy's freshness.pricing_max_age_seconds, per hour. The job takes the board only when it is worth more; on a tie the deployment keeps it. A side whose value cannot be read — a job with no income and no declared internal value, a caller with no price, a paid call whose answer stated no usage, no policy — is a refused candidate named with its cause and never chosen, so the board stays as it is. When the job wins, the agent pauses the inference container, advertises the released capacity, and resumes inference only after queued and active GPU work has cleared; an admitted job already running keeps the board until it ends. There is no timeout-based eviction, and the route's ordered provider fallback remains available while the local container is yielded.

Each decision is kept under market/decisions/<worker>/ with every candidate, its value and source or why it was refused, the use the board held, the one chosen and why, and the agent's diagnostics carry the latest as board_decision. stado market decisions --target WORKER [--since-hours 24] [--json] (stado.market-decisions.v1) prints them; Desktop's Registry screen runs it.

A deployment holds one board, named by the driver's UUID in resources.gpu: --gpu GPU-<uuid> (as stado inference blockers prints it) names it, and without it plan takes the board with the most free memory from the inventory's BOARDS line. The container is started with --gpus "device=<uuid>", the reservation carries the same UUID, and on a host with several boards an exclusive reservation withdraws only that board from admission: GPU jobs and rentals stay eligible on the others, and the agent's capacity line says exclusive inference reservation '<name>' holds board <uuid>; GPU claims stay eligible on the host's other <n> board(s). A deployment written before boards were named has no gpu and still holds every board of its host; so does a reservation whose board the driver does not report (inference_gpu_error in the agent's diagnostics names it). plan refuses --gpu naming a board the host does not have, listing its boards, a host that reports no board, and the registry refuses a gpu that is not GPU-<hex> or a board another running deployment of the same host already holds.

Source: this website