Extends ADR 0050: model-access, a provision, may be answered by a NODE that hosts a model (Ollama/vLLM serving an OpenAI-compatible endpoint) as well as by a licence record. A node-answer delivers an endpoint (base URL + model, and a key only if the server wants one), rides the ordinary serves/binds path, and uses no adapter; the resolver already prefers a local model over a licence. One provision, two answers — the mesh's own model sits behind the same interface as a vendor's. Names the mesh-scope-alongside-licence limitation and its fix. Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
9.9 KiB
topic, status, date, deciders, reconstructed, extends
| topic | status | date | deciders | reconstructed | extends |
|---|---|---|---|---|---|
| model access | accepted | 2026-09-07 | jochen | false | 0050-model-access-is-vendor-agnostic.md |
55. Model access is answered by a licence, or by a node that hosts the model
Context
ADR 0024 made model access a provision, and
ADR 0050 fixed what answers it: a record — a licence — that
an adapter turns into a sealed vendor credential. A consumer requires model-access, is put on a
licence, and is delivered a key (a static API key, or an access token a manager refreshes). Every
answer so far has been a credential to reach a vendor's API across the internet.
But a model need not come from a vendor. A node in the mesh can host one. An operator with a GPU runs Ollama or vLLM, which serves an OpenAI-compatible API on that node. A consumer that wants that model does not need a vendor credential — it needs the model server's endpoint: the base URL and the model name, and a key only if the server is configured to want one. This is model access answered by a node, not by a record.
The mesh already knows how a node answers a provision — it is the ordinary provider/consumer path.
A provider provides a provision at a scope, serves its connection facts, and the mesh fills the
consumer's bound facts with the provider's at/port and each served fact — exactly how a Postgres
consumer learns where its database is. Nothing reserved model-access to records: a node offering
provides: ["model-access"] resolves through this path, and the resolver already prefers a local
answer over a licence — its own comment names the case, "a model the mesh runs itself." So the
capability exists; what is missing is the decision to use it, and the statement of what a node-answer
delivers and where it stops.
A node-answer and a record-answer are the same provision with two shapes of answer. This is the
same move ADR 0050 already made for shapes within the
vendor path (static-key vs refreshable-grant): one provision, more than one way it is answered. A
consumer written against model-access should not care whether the model behind it is a vendor's or
the mesh's own — it asks for model access and is given what reaches a model.
Considered Options
-
A separate provision for the local case (
local-model,model-endpoint). A node answers that;model-accessstays record-only. Rejected: it splits "where my model comes from" into two provisions a consumer must choose between in its manifest, when the mesh already models a record-answer and a node-answer to one provision. A consumer would have to know, at authoring time, whether its model will be a vendor's or the mesh's — the exact coupling the provision was meant to remove. It is the safer implementation (see the limitation below) but the worse interface. -
Model access answered by either a licence or a node, under the one provision. A consumer requires
model-access; the operator answers it with a licence (a vendor) or by assigning a node that hosts a model. Adopted: one interface, and the answer is an operator's deployment choice, not a consumer's authoring choice.
Decision
Model access is one provision answered two ways: by a licence (a record, turned into a sealed vendor
credential by an adapter — ADR 0050) or by a node that hosts
the model (a provider that serves an endpoint). A consumer requires model-access and is delivered
whichever the operator assigned; it does not name the kind.
A node-answer delivers an endpoint, not a credential. The provider provides: ["model-access"]
and serves its connection facts — the port it listens on and the model it runs — and the mesh fills
the consumer's bound facts with the provider node's at, the served port, and the served model,
the same way every provider consumer learns where its provider is. The consumer assembles a base URL
(http://<at>:<port>/v1) and points an OpenAI-compatible client at it. If the local server wants a
key, the provider mints one the ordinary way (a per-consumer secret, sealed and host-unsealed); if it
does not — the common Ollama case — the consumer lists model-access under binds and not under
secrets, and no key is delivered. Secret delivery and fact delivery are already independent, so a
keyless endpoint is expressed by asking for the facts and not a secret.
A node-answer uses no adapter. The adapter registry (ADR 0050) is the vendor-credential machinery — accept-and-seal, refresh, usage. A node-hosted model has no vendor secret to seal; its endpoint is served, and its key (if any) is minted like any provider's. The adapter is consulted only for the record/vendor answer. So the vendor-agnostic decision is untouched, and the node-answer adds no vendor logic anywhere.
The resolver prefers a local answer. When a node's own set answers model-access — a model the
mesh runs itself — a licence for it is not consulted. This is already the resolver's behaviour and is
made a decision here: a mesh that runs a model uses it, and a licence is the answer for a consumer that
has no local model, not a competitor to one that does.
One limitation, stated so it is not found as a bug. The local-preference above is exact for a
node-scope provider co-located with its consumer, and for any node-answer in a mesh that holds no
model-access licence. It is not yet exact for a mesh-scope model server — one node serving the
model to others — while a licence for model-access also exists in the same mesh: the record pass
that turns a licence into an answer keys on same-node satisfaction and would still demand the licence be
used, double-answering. Until that pass is taught to stand down when a brokered node need already
answers, a mesh-scope local model and a vendor licence must not both answer model-access in one mesh.
A node-scope local model has no such constraint. This is named because an unstated limitation is
indistinguishable from a bug, and costs more.
This is a decision and not a patch because it settles what may answer model access — a question every model-access consumer's meaning depends on — and because it lets the mesh's own hosted models sit behind the same provision as the vendors', which is what makes "the mesh can run its own model" a deployment choice rather than a second interface to build against.
How each claim is checked
- A node answers model access without a licence, and is preferred over one. A resolver test
assigns a consumer and a module that
provides: ["model-access"]at node scope on the one node, with a licence also present, and asserts no resolved need is answered by the record — the local model answers and the licence is ignored. (This test exists; the decision adopts what it proves.) - The consumer is delivered an endpoint, not a credential. A mesh bed assigns a model-server
provider and a consumer that binds
model-accessand does not list it undersecrets, and asserts the consumer's config carriesOPENAI_BASE_URLbuilt from the provider's servedat/port, and that no key file was delivered to its secret path. - The local endpoint is reachable through what the consumer was given. The bed makes a request to the base URL the consumer wrote and asserts the model server answers — the wiring, not a model's output, is what is proven (the server may be a stub; a real model is not needed to prove the mesh routed the consumer to it).
- A node-answer consults no adapter. A node-answered
model-accessneed is resolved with the vendor registry never read — asserted by the absence of any vendor on a node-answered need and the ordinary served-facts delivery. - The scope limitation holds where stated. The node-scope case is what the bed and the resolver test exercise; the mesh-scope-plus-licence collision is recorded here and left for the resolver change that reconciles the two answer passes, not worked around in a module.
Consequences
- The mesh can run its own model, and a consumer reaches it through the same
model-accessit uses for a vendor. One interface, two answers; a consumer moves between a vendor and a local model by an operator reassigning its provision, not by a code change. - A local model is keyless by default and keyed by the ordinary path when it must be. Nothing new is invented for the local server's credential: it either has none, or mints one the way every provider does.
- The vendor path is untouched. Adapters, the refresh carve-out, and usage (ADR 0054) are the record answer's business; a node-answer neither uses nor changes them. A local model that exposes usage would serve it as facts, not as an adapter's usage verb.
- The two answer passes meet in one place, and must be reconciled there. The mesh-scope limitation is the single point where a node-answer and a record-answer to the one provision can collide; it is named, and its fix is a resolver change, not a per-module workaround.
References
- ADR 0024 — model access is a provision; this decides a node may answer it, not only a record
- ADR 0050 — model access is vendor-agnostic; the record answer and its adapters, which the node answer sits beside and does not use
- ADR 0054 — model usage; a vendor's business on the record path, served as facts (if at all) on the node path
- ADR 0008 — a context owns its store; a licence is a record in one, a node-answer needs none
- 03-DESIGN/01-to-be/14-model-access.md — the model-access design, which this extends with the node answer