On this page

Local inference

How do you serve a model from a fleet GPU and route traffic to it? stado inference plans, deploys, routes and operates local OpenAI-compatible inference: a digest-pinned vLLM container on a registered GPU host, addressed through logical routes on the Brama gateway.

This plane is being replaced by the service declaration contract: a model server is a service like any other, declared once with stado service declare and deployed with stado service deploy. It keeps working while its declarations migrate; add nothing new to it. See service.

Flag-by-flag listings live in cli; the registry section and worked lifecycle live in configuration.

What a deployment is

The optional top-level inference section of the canonical registry is the single desired-state and routing catalog for Stado-managed vLLM. gateway_target is the registered host running Brama; deployments run on registered local GPU targets and expose their OpenAI-compatible endpoint only on a Tailscale IPv4 address.

A deployment pins what it runs: the image is a digest-pinned vLLM image (repository@sha256:digest) and the model revision is an immutable Hugging Face commit SHA, so neither the runtime nor the weights can be replaced silently. On the host it is one Docker container named stado-inference-<name>, supervised by Docker's unless-stopped restart policy, with the Hugging Face cache mounted from a persistent host directory (--cache-dir; set it when the target's home filesystem is not the intended model volume).

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 a live Tailscale address, then saves an immutable plan bound to the current registry digest, locally under ~/.stado/inference-plans/<id>.json. 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.

--gpu-mode decides who owns the board. exclusive (the default) keeps the GPU reserved for inference. yieldable makes the local Stado agent the lifecycle owner: it pauses the inference container when an eligible GPU job is queued, advertises the released capacity, and resumes inference only after queued and active GPU work has cleared. There is no timeout-based eviction, and the route's ordered provider fallback remains available while the local container is yielded.

Routing

Workloads name a route alias, never a host. route set atomically updates one logical route:

stado inference route set example-client/chat/primary \
  --to chat-primary \
  --fallback openai/gpt-4.1-mini \
  --expected openai/gpt-4.1-mini \
  --gateway gateway-host

Every change requires --expected as a compare-and-swap precondition (absent for a new route). The command probes the destination first, stages an owner-only route snapshot on the gateway, compare-and-swaps the registry, and then atomically commits the snapshot. Brama reloads that file per request, so cutover needs no backend restart. --gateway names the registered host running Brama and is required on the first managed route.

Model purposes

A model can be declared for exactly one purpose in registry.inference.model_purposes, keyed by its repository:

"model_purposes": {
  "TheDrummer/Cydonia-24B-v4.3": "erotic-roleplay"
}

Registry validation then refuses any route or fallback whose alias does not live under that purpose namespace: a model declared erotic-roleplay may only serve aliases such as erotic-roleplay/primary, never weles/agent/primary or any other general alias — as a route, and as a fallback. Models without an entry are unrestricted. The declaration exists because in August 2026 the fleet's agent aliases were found pointing at a special-purpose finetune, and nothing in the registry said what the model was for, so nothing could refuse the binding.

Ordered --fallback destinations are attempted when the primary fails; an external provider fallback (see providers) preserves the same model contract while local inference is unavailable. A non-ready yieldable deployment is accepted only as the primary of a route with at least one ordered fallback; an exclusive primary and every local fallback must be ready, so an unavailable deployment cannot be published as a route's only destination.

Reading health

Command What it answers
stado inference list Declared deployments, without contacting hosts.
stado inference status NAME The deployment's state from the latest host beacon.
stado inference doctor NAME Runtime, GPU, endpoint and authentication, inspected on the host.
stado inference verify NAME One minimal authenticated OpenAI-compatible completion, end to end.
stado inference logs NAME The deployment's log tail (default 255 lines) over the managed host channel.
stado inference plan-logs PLAN_ID Logs for a runtime whose plan has not committed, through the same channel.

The host-side readiness probe is the same fact doctor and apply rely on: the stado-inference-<name> container must report running, and an authenticated request to the endpoint's /v1/models must succeed. verify goes one step further and posts a real /v1/chat/completions request.

GPU contention

If plan or apply reports an unmanaged GPU workload, inspect it through the same target-scoped host channel instead of an ad hoc SSH session:

stado inference blockers --host gpu-host
stado inference release --host gpu-host --identity <PID:START_TICKS>

blockers reports the executable, owner, VRAM use, cgroup, and an identity made from both PID and /proc start ticks. release refuses a stale identity, sends TERM, and waits for exit; --force only escalates that same verified process to KILL. It never accepts a bare PID.

Teardown

retire stops and forgets a deployment. It refuses while any primary or fallback route still selects the deployment, and it retains the model cache unless --purge-cache is explicit. On the host it removes the container, the deployment's reservation, and its stored credential material.

rollback NAME reinstalls the previous deployment generation.

abort PLAN_ID cleans up after a cancelled or failed pre-commit plan, which can leave a runtime or root-owned model cache without a registry deployment. It never changes the registry: it stops only the runtime described by the immutable local plan, removes its cache through the pinned container runtime with --purge-cache, and consumes the plan after successful cleanup.

Credential

The deployment and Brama share one centrally stored credential item, provider:local-openai, containing a non-empty token field:

stado inference init-credential

This generates and stores the bearer in Skarbiec without printing it, and refuses to overwrite an existing item. Deliberate rotation must be coordinated with runtime replacement; never place the token in argv or registry data. See security.

Source: this website