ADR 0024 — model access is a provision, and a licence has a name
A new requirement, and it is mostly a shape the mesh already has. A module that needs to think requires model-access; several vendors and a locally-run model are several modules providing it; choosing is assigning the one you want. A model the mesh runs itself needs nothing new at all — it is a mesh-scoped provision on the node with the hardware, credential included. A licence is a named thing because the whole point is saying which one a given consumer uses, and the names are the operator's. Many to many, so not a claim: two machines sharing an account is ordinary, not a collision. Four gaps, written as gaps rather than design: - a provider that is on no node, reached over the public internet, which the reachability rule must not refuse - a secret the mesh is GIVEN rather than mints. Every credential it handles today it generated and discarded; an API key arrives from a person, and accepting one must still discard the plaintext - a consumer that is not a machine. Which licence a worker uses is a binding to an agent, and the provisions model has no consumer identity other than a node - switching on exhaustion is a reaction to something observed, not a declaration. It belongs with observability, changing a binding — saying so is what stops the declaration language growing a conditional The existing auto-refresh and switching is not being replaced because it was wrong. It is being rebuilt because it lives somewhere that cannot express the rest.
This commit is contained in:
@@ -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
|
||||||
Reference in New Issue
Block a user