diff --git a/11-Features.md b/11-Features.md index c42f1fa..65c5868 100644 --- a/11-Features.md +++ b/11-Features.md @@ -4852,3 +4852,53 @@ read them before designing one. worker exhaustion starts quarantining the lead's seat too. Arm them together, or not at all. fleetd #395. + +## A central allow-list of the models the fleet may use + +**What.** A top-level `models:` block names every model id the fleet is allowed to run. Once it is +non-empty, a `profiles:` entry naming a model that is not on the list **refuses to start**, and +refuses a reload too. + +```yaml +models: + allow: + - model: claude-sonnet-5 + - model: openai/gpt-5.6-terra + - model: gx/deepseek-v4-flash +``` + +`model:` is one flat, opaque string namespace. A bare Claude id and a provider-prefixed opencode id +both fit unchanged, because the check is exact string equality — it never parses a provider prefix +and never branches on `kind:`. + +**The knob.** `models.allow`. Absent or empty keeps the old behaviour, where no model was ever +checked, so this ships inert until an operator writes the block. + +**Why it exists.** Before this there was no single place that said which models the fleet may use. +Each profile named one, and a typo or a withdrawn model id reached the backend adapter as a +free-form string. On an opencode profile that has a specific bad outcome, already recorded here: +a withdrawn model name makes opencode fall back to a **paid** model silently. An allow-list turns +that class of mistake into a refusal at load, which is the cheapest place to find it. + +**Why it is operator-owned and not checked against a vendor catalogue.** For an opencode profile +fleetd *synthesizes* the provider from the `provider/model` selector plus `baseUrl` +(`OpenCodeLauncher:562-583`). So a perfectly valid fleetd model id can appear in no published +catalogue — `gx/deepseek-v4-flash` on this fleet is exactly that, absent from models.dev and +correct. Any attempt to validate the list against a vendor catalogue would reject working +configurations. The list is the source of truth; nothing else can be. + +**Gotchas.** + +- **Adding a profile means adding its model in the same edit.** Otherwise the daemon will not + start. That is the intended trade: the failure is loud and immediate rather than silent and later. +- **`models:` is deferred, not hot.** A reload validates the new block — so a bad edit is refused + and the running config is kept — but a good edit is reported as needing a restart. Edit-then-reload + is a half-change. Restart with `scripts/redeploy-fleetd.sh`. +- **The list is a name gate, not a capability check.** It says the operator permits this id. It does + not say the backend serves it, that the credential may use it, or that the id is spelled the way + the provider spells it. A model on the list can still fail at spawn. +- **Cross-check the two lists rather than trusting either.** `grep -E '^\s+model:' fleetd.yaml | + awk '{print $2}' | sort -u` against the `allow:` entries. If they differ, the daemon refuses to + boot — which is the point, but it is better to know before a restart than during one. + +fleetd #398.