Files
hq/02-DECISIONS/0055-model-access-is-answered-by-a-licence-or-a-node.md
T
jschoubben cde00e1d5f 0054 and 0055 join the topic their subject already had
Both carried 'model access', which is not one of the six the reading order
knows, so neither had a place in it. 0050 — the record they extend, on the same
subject — is 'what runs on it'.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-11 01:06:59 +02:00

9.9 KiB

topic, status, date, deciders, reconstructed, extends
topic status date deciders reconstructed extends
what runs on it 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

  1. A separate provision for the local case (local-model, model-endpoint). A node answers that; model-access stays 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.

  2. 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-access and does not list it under secrets, and asserts the consumer's config carries OPENAI_BASE_URL built from the provider's served at/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-access need 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-access it 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