diff --git a/02-DECISIONS/0024-model-access-is-a-provision.md b/02-DECISIONS/0024-model-access-is-a-provision.md new file mode 100644 index 0000000..eb177ea --- /dev/null +++ b/02-DECISIONS/0024-model-access-is-a-provision.md @@ -0,0 +1,95 @@ +--- +topic: what runs on it +status: proposed +date: 2026-08-30 +deciders: jochen +reconstructed: false +--- + +# 24. Model access is a provision, and a licence is a thing with a name + +## Context + +Everything in this mesh that thinks needs a model, and there is more than one way to reach one: + +| | | +|---|---| +| **hosted services** | several vendors, each with its own account, quota and key | +| **models the mesh runs itself** | open-weight models on a node with the hardware for them | + +And the choice is **per consumer, deliberately**: a workstation's own session on one account, a +laptop on the organisation's, two hired workers on the mesh's local model because their work does +not justify paid tokens. Those are three different answers to one requirement, held at once, in +one mesh. + +**The existing system has the hard half of this already** — automatic licence refresh and +switching between accounts when one is exhausted — and it works. It is not being replaced because +it was wrong; it is being rebuilt because it lives in a place that cannot express the rest. + +## Decision + +**Model access is a provision.** A module that needs to think declares `requires: model-access`; +anthropic, openai, grok and a locally-run model are four modules that provide it. That is +[ADR 0009](0009-modules-and-the-graph.md)'s mechanism unchanged, and it buys the things that +mechanism already buys: several implementations of one job, a refusal when more than one could +answer, and choosing by assigning the one you want. + +**A locally-run model needs nothing new at all.** It is a mesh-scoped provision on the node with +the hardware — the same shape as a database, including the credential. + +### A licence is a named thing, and the name is the operator's + +Not an anonymous credential hanging off a provider. *The personal account*, *the organisation's +account* — those are names a person uses, and the mesh has to use them too, because the whole +point is saying **which one** a given consumer uses. + +**Many to many.** One provider has several licences; one licence serves several consumers. So it +is **not a claim** — claims are for things only one holder may have, and two machines sharing an +account is the ordinary case rather than a collision. + +### Four things this needs that the mesh does not have + +Written as gaps rather than as design, because each is a real piece of work and pretending +otherwise is how a plan becomes a surprise. + +**1. A provider that is not on a node.** A mesh-scoped provision today is answered by *the machine +running it*, and the reachability rule refuses two ends that share no private network. A hosted +service is on nobody's machine and is reached over the public internet. That is a third scope — +answered by a record rather than by a node — and the reachability rule must not apply to it. + +**2. A secret the mesh is given rather than one it mints.** Every credential the mesh handles +today it generated itself, sealed to both ends, and discarded. An API key arrives from a person. +The missing verb is *accept*: take a value, seal it to each holder, and **discard the plaintext** +— because a mesh that keeps operator-supplied keys readably is the arrangement this project +[measured and rejected](0009-modules-and-the-graph.md). + +**3. A consumer that is not a machine.** *This worker uses that licence* is a binding to an agent, +not to a node. [ADR 0001](0001-mesh-brokers-nodes-host-agents-think.md) already says an agent +holds credentials and that delivery follows its node bindings and modality — so what is delivered +still lands on a machine, and what is **chosen** is chosen per agent. The provisions model has no +consumer identity other than a node. + +**4. Switching is a reaction, not a declaration.** Everything here is desired state, reconciled by +comparison. A licence that hits its limit and must be swapped is a response to something observed, +and it cannot be expressed as a declaration without the declaration meaning *whatever is working +right now* — which is not a thing anybody declared. **It belongs with observability, changing a +binding**, and the binding is then declared as usual. Saying this plainly is what stops the +declaration language growing a conditional. + +## Consequences + +- **The refusing rule applies here and will be felt.** A mesh holding three ways to reach a model + refuses every consumer that has not said which — which is correct and is a great deal of + saying-which the first time. The remedy is one assignment per consumer, and the message names + the candidates. +- **A licence outliving its holder is a live credential nobody is watching.** The same rule the + provisioner follows applies: what the mesh granted and no longer grants is withdrawn. +- **Nothing here makes a node authenticate to a model provider.** ADR 0001 holds: an agent does. + What changes is that the mesh can now say *which agent, which licence, which node it lands on*, + which is the fact ADR 0001 records as missing. + +## References + +- [ADR 0009](0009-modules-and-the-graph.md) — provisions, scope, choosing, and sealed credentials +- [ADR 0001](0001-mesh-brokers-nodes-host-agents-think.md) — agents hold credentials, not nodes; + `hal/ai` as the context owning provider grants and rotation