quartermaster
The operator-local supply layer. Decides which model runs which lifecycle job from a registry outside the repo, and routes build work by derived class rather than by the caller's label.
quartermaster
ADLC phase: P4 Build · Gate: every dispatch resolves its {adapter, model, transport} from an operator-local registry outside the candidate tree, or it does not dispatch at all.
What it defends against
model-router answers how much model a ticket deserves — a tier and a ladder
mode. It deliberately does not answer which concrete harness, on which
subscription, through which transport. Left unanswered, that question gets
answered by whatever config happens to be nearest, and the nearest config is
usually inside the repo being reviewed.
That is the hole. A candidate tree that can name its own reviewer, or repoint
frontier at something cheap, can lower the bar it is about to be judged
against — and nothing downstream would look wrong. So the registry lives
entirely outside every candidate tree, and the repo carries no knob at all:
not a path, not an override, not a symbolic hint. A registry-shaped file found in
the tree is ignored with a notice; a configured path that is relative or inside
the repo disables loading rather than reading it.
"Operator-local" now means "not selected by the candidate" and "not writable
by it" — but still not "unreadable by it." The repo's own fleet.gate commands
run inside fleet's model-plane worker, so candidate-authored code executes there.
Since #395 that worker runs
inside an OS filesystem sandbox — bwrap on Linux, Seatbelt on macOS — with
network egress preserved, because it must still reach its provider. Its
writes are bounded to the ticket's worktree, the state directories its
harness declares, and the OS temp dir; the registry's default home
($XDG_CONFIG_HOME/adlc/) is none of those, and the worker is not even told
where the file is (ADLC_QUARTERMASTER_REGISTRY is withheld from the model-plane
env). What that does not cover, stated plainly: reads are unbounded and
egress is open, so read-and-exfiltrate remains; the bound holds only while the
registry sits outside those roots, so a registry parked under the temp dir — or
handed to the model plane with --model-plane-writable — is writable again; and
--i-am-in-a-disposable-container, the override for a host with no sandbox
backend, switches the OS enforcement off on both planes and rests entirely on the
container you asserted. Moving supply
out of the tree closed the declarative downgrade; the sandbox closed the
code-execution one. Running a fleet over a repo you do not trust is still
outside the design's assumptions.
The second half is that a registry label proves nothing about what ran. The
dispatcher forces the registry's model onto the harness command line explicitly
(-m <model>) instead of trusting an ambient default, so the model that ran is
the model the records claim.
transport selects the credential — but the guarantee is asymmetric. Since
#396 dispatch derives the
worker's credential context from the seat's transport, and each adapter declares
which transport classes it can serve, so a seat bound to a class its adapter
does not declare is rejected at load. Withholding is provable: a
subscription: seat is dispatched with every provider API key removed, so no
metered credential reaches it through its environment — a fact about that
environment, not a claim about what a harness does with credentials it already
stores. Supplying is not: an api: or gateway: seat is
handed the adapter's declared credential, but a harness holding its own session
may still prefer that — the label says what was offered, never what served the
call. So a record carries transportStatus: "selected"; "attested" — the
harness reporting which transport actually paid — is not built yet. Read a
subscription: label as a guarantee, of the negative kind, and any other label
as intent plus a credential grant.
The registry
An operator-local quartermaster.json — $XDG_CONFIG_HOME/adlc/quartermaster.json
by default, or wherever ADLC_QUARTERMASTER_REGISTRY points, provided that path is
absolute and outside the repo under review. Channel names are a closed set
fixed by the spec (frontier, frontier-metered, mid, cheap, plus the
reviewer groups), so a registry cannot invent a route the routing contract never
sanctioned. Adding a channel is a spec revision, not a config edit.
Seven load-time rules apply, all fail-closed and all reported at once: closed
names, an adapter allowlist drawn from the fleet's adapter modules, no
command/argv-shaped field at any depth, distinct fallback transports, a closed
transport taxonomy (subscription: / api: / gateway:), concrete model IDs for
reviewer seats, and a (adapter, model) → provider table that must carry every
concrete model and agree with the seat's declared provider. A missing,
disabled, or invalid registry aborts before dispatch. There are no defaults.
Channels must name concrete model IDs, not aliases like default. An alias is
resolved by the harness from state the candidate can influence — Claude Code reads
a project-committed .claude/settings.json model when no --model is passed — so
§4c permits an alias only on an adapter that attests the model it actually ran.
None do yet, so alias channels are rejected at load.
Alias resolutions cannot be checked that way — a harness alias tracking a floating "latest" is not enumerable at load time — so for an alias the table is only checked to be non-empty, and the schema reference says so plainly rather than implying more.
Full schema: docs/integrations/quartermaster-registry.md.
Routing
routeJob is a total function over a closed job enum that does not trust its
caller. For build.* jobs the class is derived — contract/spec/
architecture categories are spec-class, float === 0 is critical-path,
everything else is ladder-start — and a caller-supplied label that disagrees with
the derivation throws. A mislabeled dispatch is a bug, not a route.
Float is read from the assignment, never from the ticket: stored tickets have no float field, and defaulting an absent one would silently downgrade critical-path work to the cheap channel.
Usage
The registry is operator-local configuration, not a command. To see what it would dispatch — resolved per ticket, with the argv the adapter itself renders — run the fleet's dry run:
adlc fleet run --dry-run quartermaster registry: /Users/you/.config/adlc/quartermaster.json
T900 job=build.critical-path channel=frontier adapter=claude-code model=claude-opus-5 transport=subscription:anthropic-max
argv: claude ["-p","<prompt>","--permission-mode","acceptEdits","--output-format","text","--model","claude-opus-5"]
T901 job=build.ladder-start channel=mid adapter=opencode model=opencode-go/glm-5.2 transport=gateway:opencode-go
argv: opencode ["run","-m","opencode-go/glm-5.2","<prompt>"]Notices — an ignored in-repo registry, a disabled path — go to stderr. Adding
--json returns the same seats and argv under a quartermaster key, through the
identical validation path, so automation using it as a pre-dispatch check is
never told a run is fine when the live run would fail closed. With no operator
registry present the layer is simply not engaged, and the fleet keeps its
--adapter / --model behavior.
Breaking change. fleet.model in .adlc/config.json is now ignored with a
warning, like fleet.adapter before it. It was previously honoured as
"non-executable data" — safe only by accident, because the claude-code, codex,
and opencode adapters silently dropped it. Now that every adapter forces its
model onto the command line, a repo-committed value would be a candidate tree
choosing the model that builds and judges it. Use the operator-local --model
flag.