On this page
Operations
Host health publication
stado host beacon collect builds the document a host publishes about itself:
every unit identity the registry declares for that host, the state its own
init system reports for each one, the free space on the volume the fleet
writes to, and the host's memory readings. --publish hands the document
to the same route stado host beacon publish FILE takes for a document
collected elsewhere; without it the collection is printed and nothing is sent.
A unit's state is active, failed, inactive when every domain answered
that nothing is there, or unreadable with the cause in detail when a
domain refused the read or the read failed. Those are three different facts
and they stay apart: the collector this replaced read each label with
launchctl print … 2>/dev/null || true and published inactive whenever the
read produced nothing, so every system-domain daemon on a Mac without
passwordless sudo — the always-on gateway among them — was published as not
loaded, and stado service status, registry doctor and Stado Desktop each
repeated it. Reading a launchd domain or a systemd unit needs no privilege,
and neither reader asks for one now.
Every read a host makes of its own registry entry - the unit states, the
disk inventory, the cache census, the link trace - runs on the host itself,
never over SSH to itself. The entry is recognised as this machine by the one
identity rule a target has: its name, each declared hostnames alias and
the host part of each declared SSH path, compared to the kernel's own
hostname with the mDNS .local suffix dropped and case ignored. It is the
same rule stado host beacon collect finds the host's own entry by, so the
entry a host collects a beacon for is the entry whose commands run locally.
stado host link <target> --json says which route a command took:
selected_connection is local for this machine's own entry, declared by
name alone, by alias or by SSH host; the real test
tests/host_health/beacon/own_channel.rs drives all three shapes. When the two
readings used different rules, an entry declared by name only was found for
its beacon and then reached over SSH to the same box for every script of the
pass, through sshd and a login shell, on every health interval.
A GitHub runner unit is judged by its listener, not by its wrapper. What the
init system holds for a runner is runsvc.sh, start-runner.sh or a
reconcile script; the process that takes jobs is its child, the runner release
archive's bin/Runner.Listener. The install is taken from the unit's own launch chain,
in this order: its program's directory (or the parent of its bin/) holds
GitHub's .runner registration file; or the launcher names the install it
selected in .stado-runner-install beside itself; or the unit's running
process works in a registered directory directly inside that directory.
wisent-backend's release runner uses the record: its launcher selects
vendor-<version>-layout-<n>/, writes that path to .stado-runner-install,
changes into it and execs run.sh. The record matters because run.sh
exits 0 when the listener stops for good, and launchd then shows a loaded
unit with no process to follow. A registration that merely sits next to a
program does not make its unit a runner. When a runner unit reads as active
and its install's listener is not in the process table, it is published
failed, and detail names the listener it looked for and ends with the
last lines of the newest _diag/Runner_*.log — the line that says why the
listener exited or never started. A directory, registration file, install
record, working directory or process table the collector cannot read, or a
record naming a path outside the launcher's directory, is appended to
detail, and the state stays what the init system reported.
On a fleet host the publisher is the host's one Stado process: the catalog's
com.wisent.stado declares --health-interval-seconds 60, and the process
collects the document on that cadence and writes it as
host_health/<host>.json into the fleet store its queue roles already use —
the store stado host beacon list and stado service list read. It needs
no beacon URL and no beacon grant, so a host cannot go unknown because its
unit environment named no route. A local API the same process may serve
(--api-local-store) is not that store and receives no beacon.
stado host beacon collect --publish and stado host beacon publish FILE
require STADO_HOST_HEALTH_API_URL plus the dedicated
stado-host-health-beacon Skarbiec URL/consumer/grant metadata. They resolve
only the token of the item playing role host-health-api (the item tagged
stado:role:host-health-api) and send the document to authenticated
PUT /api/host-health. The control plane stores host_health/<host>.json
through its configured Stado backend, so Azure and local outage profiles write
to Azure Blob and local storage respectively.
Missing routing, an unreadable or over-broad grant, an insecure non-loopback HTTP URL, failed authorization, and backend errors all leave the prior beacon untouched and return failure. There is no cloud CLI, provider SDK, direct bucket URL, ambient credential, or cross-backend fallback in the writer.
The two commands above read the non-secret API/Skarbiec origins from their
environment (/etc/stado/host-health.env on systemd hosts); the opaque
Skarbiec grant stays owner-only at
~/.stado/host-health-beacon-skarbiec-token.
Reading admission and diagnostic failures
stado host gates HOST --json reads the registry, current disk usage, janitor
state and snapshots, effective agent storage configuration, storage client,
published capacity and queue. Each read retains its result or concrete error
and reports its operation, source, state, start and finish time, elapsed
milliseconds and error in observations. Elapsed time describes the read;
the host-gate observer does not use it to cancel the operation. Independent
reads run together. A failed host read does not remove a completed capacity
or queue reading.
complete: false means the diagnostic could not finish; claiming is then
null, and the command exits non-zero. Observation states distinguish
complete, partial, absent, cached, error and skipped. A cached
registry is identified as historical, not presented as a successful current read.
An unread source is not the same thing as an absent object.
agent_declared_not_loaded is a blocker when the host's newest beacon does not
report its declared agent unit active. A beacon that carries no units map
says nothing about the agent, so it is that blocker too, naming the beacon's
object path, and a unit the beacon lists without a state is reported with the
entry the beacon carried rather than as an empty state. A host with no beacon
at all is the separate no_capacity_publication silence.
The memory: line reports what memory this host has left — available X of Y GiB, swap N% used — and memory evidence: names where that reading came from:
capacity_publication while the host's agent is publishing, and
host_memory_measurement when it is not and the command read vm_stat and
sysctl (or /proc/meminfo) off the host itself. Memory is a reading only and
never a blocker. The second source is what makes the line useful on the host an
operator looks at when it has stopped answering: the one whose agent has no
memory left to publish with would otherwise print memory: not observed.
A figure the host's capacity publication does not carry — free or total
cores, RAM or VRAM, the running job count, its age, a reservation's fields —
prints as not reported on the capacity: and reserved: lines, and a reading
this command could not take (disk use, memory, its evidence, the local snapshot
count) prints as not observed; neither is ever printed as zero, - or ?.
Figures print as the host published them, and waiting jobs are named by their
whole job id.
The queue read walks the priority index — queue_priority/, which names
queued jobs and nothing else — and returns the pinned jobs it finds, oldest
first, up to 200. It is partial when that window filled, with the detail
200 pinned jobs filled the window; more may be queued for this host; a
partial read still decides the verdict. Reading every object under queue/
instead would include one transition sentinel per job that ever left the
queue, and on a busy queue every host's read would time out and every verdict
would be claiming: unknown. The coordinator's reaper retires a sentinel once its
job has settled in a terminal prefix and the object is a day old, at most 100
per prefix per tick, and logs reaper: queue/ settled sentinels retired=N kept=M inspected=K when it removed any.
When placed workloads hold the host, the text carries a reserved: line —
N placed workload(s) hold C core(s), R GiB RAM, V GiB VRAM; the capacity line is net of them — and one line per hold naming the kind, product, holder
and acquisition time; capacity in the JSON carries running_workloads,
reserved and reservations. The accelerator: line says who holds the
card, from the same publication (space).
disk.free_bytes is measured by the host's filesystem reader; free_gb is its
GiB display value. used_percent, headroom_gb (what may still be written
before the disk-full rule deletes; negative past it), full_percent (80) and
full judge that measurement against the rule.
These fields are null when the measurement is unavailable, never zero.
pressure_source distinguishes the agent's current capacity_publication
from host_disk_measurement; capacity.diagnostics retains the agent's actual
admission reason and reported state. Low free space and the agent's decision
are different readings. Neither a slow queue read nor an unavailable disk
reader proves that the disk is full.
Stado Desktop shows the same results in Hosts → selected host → Gates:
completed measurements, unknown admission when reads are incomplete, each
source and error, and the agent's published diagnostics. Reading this screen or
running host gates does not clean a disk, restart a service or change policy.
Missing service reconciliation
Every autonomy pass reconciles each host's declared services against its newest beacon and a reachability sweep, retires units a product's one process replaced, and reports what it did: Missing service reconciliation.
Every command below enters through Stado. Provider diagnostics belong inside the corresponding adapter and are unavailable unless that provider is explicitly enabled in the selected profile.
Storage root authority handoff
Inspect, run, resume, roll back and finalize a storage root handoff, from the CLI or Stado Desktop, and what each answer means: Storage root handoff and space reclaim.
Space reclaim
The one built-in rule every host's space reclaim follows, when a pass runs and how to operate it: Storage root handoff and space reclaim.
Isolated GitHub pre-check runners
Install, inspect and remove the isolated pre-check runner, and what it may reach: Isolated GitHub pre-check runners.
Common queries
The commands that read the fleet, the queue, quota and billing, a local agent's state and one job end to end: Common queries and failure modes.
Failure mode quick-grep
The failure modes, what each looks like and where to look: Common queries and failure modes.
Recover the Stado binary on a host
When a host's Stado binary or resolver is missing or broken, so declaration-driven delivery cannot run, follow the one break-glass procedure: Recover the Stado binary on a host.
Release / publishing
Release operations enter through Stado:
stado release catalog sync --root /path/to/registered-checkouts
stado release catalog audit
stado release submit --source /path/to/product --version <exact-version> \
--channel candidate
Submit requires a clean committed tree but does not contact its remote. It
archives the exact committed tree, publishes the create-only source object,
records source and manifest identity in the Stado catalog, and creates one
provider-neutral queue job pinned to a registry builder whose
release_platform matches the recipe. Queue state and
status/<job>/output/ remain the authoritative work and transport records.
Inspect or resume a run by repeating the same submit command. Its identity is
derived from product, version, channel, source digest, and manifest digest; the
durable stado://release-runs/<id>/run.json shows job IDs, output coordinates,
delivery state, and failure. A terminal successful platform output is read
from JobStorage and published, never rebuilt.
The release authority is configured by item name and trusted key ID:
release.signing_key_item = stado-release-signing
release.signing_key_id = stado-release-2026-08
The Skarbiec key-pair item contains the base64 PKCS#8 value in private_key. Key bytes stay in
Skarbiec. Build and delivery secrets are only checked-in item#field
references and must also be permitted by agent.skarbiec.secret_fields.
Canonical publication is immutable and ordered:
release.tar.gz -> qualification.json -> release.sig -> release.json
The signed manifest is the commit marker. Required delivery jobs run only
after it exists and consume its exact URI and digest. Optional mirrors do not
gate canonical success. Runtime products then use the existing
registry.release_control generation CAS and release-agent reconciliation.
deployment.json is written only after every declared target reports the
promoted version, artifact digest, and manifest digest exactly.
No Git forge credential, hosted workflow, provider repository checkout, or
direct provider API is required by this chain. Existing external adapters may
mirror completed releases, but their availability cannot change source,
qualification, signing, desired state, or observed rollout truth. See
release.md for the strict manifest and catalog contract.
Bringing up a new local box
# Install a verified Rust release and resolved profile first.
./install-stado.sh
export STADO_CONFIG="$HOME/.stado/config.json"
stado config validate
stado doctor --fix-hints
# Writes the one com.wisent.stado unit running `stado serve` with every role
# the registry declares for this host.
stado bootstrap --local --target <registry-target>
# Health publication additionally requires the dedicated
# stado-host-health-beacon Skarbiec grant and non-secret Stado/Skarbiec origins
# described above. It never requires a cloud login.
stado host health <registry-target>
Source: this website