fleetctl — the public box-fleet control surface
fleetctl (cmd/fleetctl/) is the public, transport-agnostic Go core for operating
a fleet of boxes — GPU servers, worker nodes — that the operator drives over the private
private control bridge. It is the single Go home the scattered tools/fleet_*.py helpers
port into: a typed roster, a deterministic fold, a 0–100 readiness score, and a view that
stays readable as the fleet grows toward (and past) 100 boxes.
It is Go-only and depends on nothing outside the standard library. Build it like any other binary in this module:
go build -o fleetctl ./cmd/fleetctl
The public / private boundary is a data contract, not a code import
The live control plane — the private control bridge that actually reaches the lab boxes —
is private. It speaks a lab protocol and carries lab identifiers (host, channel, token), so
it lives in fak-private, never here. See gpu-server-private-boundary.md
for what is public vs private and which gates enforce it, and
private-comms-channel.md for how to reach the channel.
The seam between that private bridge and this public tool is a per-box report JSON
(fak.fleet.report/v1). The private bridge writes one report file per box from live state;
fleetctl reads, folds, renders, and scores them. Neither side imports the other, and
nothing in this tree names a host, a channel, or a token — only a generic box id, a class,
a state word, a version, and an age.
private (fak-private) public (this repo)
┌───────────────────┐ report JSON ┌────────────────────────────┐
│ Slack bridge │ ──────────────▶ │ fleetctl │
│ (reaches the box) │ one file/box │ roster + fold + score │
└───────────────────┘ │ + render │
└────────────────────────────┘
The roster
A roster is a JSON file listing the boxes you control. Every field but id is optional;
endpoint is an opaque reference the transport resolves (the public file transport
treats it as the report-file stem; the private bridge resolves it to a channel/session).
{
"schema": "fak.fleet.roster/v1",
"boxes": [
{"id": "box-001", "class": "a100x8", "group": "lab-1", "labels": {"region": "us-west"}},
{"id": "box-002", "class": "h100x8", "group": "lab-2"}
]
}
Adding up to 100 boxes — one command
template scaffolds a roster of N boxes (ids are zero-padded so they sort in order). This
is the “how do I stand up 100 boxes?” answer — scaffold, then edit:
fleetctl template --count 100 --class a100x8 --group lab-1 > roster.json
fleetctl validate --roster roster.json # fail-loud on a duplicate/empty/bad id
The report seam
Each box’s current state is one JSON file the private bridge writes into a reports
directory, named <endpoint-or-id>.json:
{
"schema": "fak.fleet.report/v1",
"state": "live", // live | idle | draining | down | unknown
"version": "0.31.0",
"age_sec": 12.5,
"note": "throttled until 14:05",
"inference": {
"status": "ready", // ready | degraded | warming | blocked | unknown
"engine": "fak",
"model": "qwen",
"output_tps": 1.75,
"reason": "scrubbed-reason"
}
}
A box with no report file is shown as unreachable — the view never crashes on one
silent box, and an empty reports directory honestly scores the fleet at 0. The reader
floors each box’s age at its report file’s own mtime, so a frozen file from a bridge
that stopped updating ages out and trips the stale warn instead of reading green forever
— the producer must therefore re-stamp age_sec on every write.
Two seam fields are operator-facing and must stay generic in anything committed
publicly: note is rendered verbatim (keep it pre-scrubbed — never a lab hostname,
channel, or operator path), and a roster’s endpoint/labels must never carry a real
channel/session/token (the private bridge owns the id→channel map on its side).
The optional inference block answers the question liveness cannot: is this box useful
for model inference right now? ready and degraded count as useful; warming,
blocked, and unknown do not. The labels are public-safe serving facts only. Never put
a URL, host, channel id, token, private model path, or raw bridge transcript in the block.
If no producer can prove the serving state, omit inference or set status to
unknown; do not report false idle/ready.
Lab-machine dev readiness
Dispatch planning needs a smaller yes/no surface than the full operator report: can this class of lab machine safely take dev work right now? The public answer is a scrubbed readiness record. It is derived from private readbacks, but it carries only generic class and status words:
{
"schema": "fak.lab_readiness/v1",
"machine_class": "gpu-server",
"checked_at": "2026-07-04T14:00:00Z",
"status": "WAIT_PRIVATE_RECOVERY",
"next_action": "confirm-private-control-session",
"evidence": "scrubbed-private-readback"
}
status is a closed vocabulary:
| Status | Dispatch meaning |
|---|---|
READY_FOR_DEV_WORK |
The machine class may be offered to lab-backed dev workers. |
WAIT_PRIVATE_RECOVERY |
Do not dispatch work there; an operator must recover the private control path first. |
GATEWAY_UNREACHABLE |
Do not dispatch model/gateway work there; keep any existing watcher alive and recover the gateway privately. |
AUTH_OR_CHANNEL_BLOCKED |
Do not retry public dispatch; the private auth/channel state needs operator action. |
INDETERMINATE |
Fail closed. Use local workers or another ready class until a stronger readback exists. |
The record may name a generic class (gpu-server, mac-worker, linux-worker) and a
generic next-action class. It must not name a host, endpoint, channel, token, account id,
raw transcript, or private filesystem path. Super-loop and issue-dispatch planning should
treat anything other than READY_FOR_DEV_WORK as no lab-machine capacity.
The readiness record does not have to be hand-authored. Once the private bridge or a
self-reporting box has written scrubbed fak.fleet.report/v1 files with inference status,
derive and publish the gate from those reports:
fak lab readiness --from-reports --write-default --json
--from-reports (formerly --from-status, still accepted as a deprecated alias) admits
lab-backed dispatch only when at least one healthy box reports
fresh inference.ready or inference.degraded; warming, blocked, stale reports,
missing reports, and unknown inference all fail closed with a generic next action. With a
private roster, pass that roster locally (--roster <private-roster>); never commit the
roster path or any endpoint it resolves.
Lab inference targets
Readiness answers whether lab-backed work may be admitted. A guarded turn still needs a
local endpoint target. Keep that second seam local too: the private bridge/tunnel producer
writes $FAK_LAB_TARGETS, or the default fak config file fleet/lab-targets.json, with
private coordinates in an untracked local file:
{
"schema": "fak.lab_targets/v1",
"targets": [
{
"alias": "@lab/glm-5.2",
"base_url": "http://localhost:PORT",
"model": "glm-5.2",
"roster": "C:/local/private/roster.json"
}
]
}
box_id is optional. Use it only for a display-safe generic box ID; when the entry
points at a private roster, omit box_id so fak lab target --json can validate the
scrubbed report without printing the private report key.
Validate the alias without printing the resolved coordinates:
fak lab target @lab/glm-5.2 --json
The resolver requires all three witnesses before it admits: READY_FOR_DEV_WORK
readiness, a local target config entry, and a fresh healthy scrubbed report whose
inference block is ready or degraded for the requested model. Then the user-visible
guard path is:
fak guard --remote-serve @lab/glm-5.2 --probe -- codex
First green run (before the private bridge is wired)
To see a populated frame without the bridge, drop a sample report into a directory and
point status at it:
fleetctl template --count 3 > roster.json
mkdir reports
echo '{"schema":"fak.fleet.report/v1","state":"live","version":"0.31.0"}' > reports/box-001.json
echo '{"schema":"fak.fleet.report/v1","state":"idle","version":"0.31.0"}' > reports/box-002.json
fleetctl status --roster roster.json --reports reports # box-003 shows unreachable
Commands
fleetctl ls --roster roster.json [--group G] [--class C] [--json]
fleetctl status --roster roster.json --reports DIR [--group G] [--class C] [--json] [--all]
fleetctl score --roster roster.json --reports DIR [--min N] [--group G] [--class C]
status summarizes by default (counts by state and class, the version picture, a capped
attention list) so a 5-box fleet and a 500-box fleet print a frame of the same bounded
height; --all appends the per-box table. --group/--class scope any of the three to
a subset — the first thing you reach for at 100 boxes. --stale-min N tunes how many
minutes of silence flags a box (default 15). score --min N exits non-zero when readiness
is below N, so it drops straight into a watchdog or a /loop.
Exit codes are scriptable: 0 ok · 1 the score --min gate fired · 2 a
usage / roster / --reports error. A missing or mistyped --reports directory fails loud
with exit 2 rather than silently scoring 0, so a watchdog never mistakes a config typo for
a fleet-wide outage.
Example summary:
== fleet - 100 box(es) - readiness 96/100 =============================
REACHABLE 98/100
STATE live=86 idle=10 down=2 unknown=2
CLASS a100x8=64 h100x8=36
VERSION 0.31.0 (6 reachable box(es) on other/none)
INFERENCE useful=72/100 reported=80 ready=68 degraded=4 warming=3 blocked=2 unknown=3
ATTENTION
[CRIT] 4 box(es) down or unreachable
box-031, box-047, box-068, box-091
[CRIT] 2 box(es) blocked for inference
box-014(blocked/needs-operator), box-062(blocked/no-model)
[WARN] 4 box(es) off the fleet version 0.31.0
box-005@0.30.0, box-018@0.30.0, box-052@0.30.0, box-077@0.30.0
The readiness score
score is a deliberately simple, predictable 0–100 blend an operator can reason about:
score = 100 * ( 0.6*usable_frac + 0.2*reach_frac + 0.2*version_coverage_frac )
usable_frac = healthy boxes (live|idle|draining) / total
reach_frac = boxes that returned a trustworthy report (incl. down) / total
version_coverage_frac = boxes on the single most common version / total
Usability dominates (an unreachable or down box is the real problem); reach gives credit for observability (knowing a box is down beats not knowing); version coverage rewards a single consistent fleet version. An all-healthy single-version fleet scores 100, an all-down-but-visible fleet scores 20, an all-silent fleet scores 0. The score is a fence, not a benchmark — the per-state counts and the attention list carry the detail.
Honesty
fleetctl is the public core: roster + fold + render + score + the file transport that
reads reports off disk. It does not reach a live box — producing the reports is the
private bridge’s job. Pointed at a reports directory the bridge wrote (or a fixture) it is
fully exercised; pointed at no reports it honestly shows every box as unreachable. Witness:
go test ./cmd/fleetctl.