The predecessor's agent module was retired and its six files stayed on both workstations telling every session to use tools that no longer exist. Before a successor module is written, the design needs the decisions it rests on and nothing in the record stated them: - ADR 0169 (reconstructed) records what the controller shipped on 2026-09-27 without a record: the operator account is a node fact stated by the operator, the home is derived unless stated, a resource may be placed under it owned by the account, and a node with no account refuses one. - ADR 0170 generalises to-be 29 §3's found-vs-owned boundary to every directory under a home: the module owns the directory and the files it places, writes into the tool's own files for its few keys, never declares a credential's content, and holds everything else as found — a predecessor's leftovers included, which the operator removes once. - ADR 0171 draws the licence line the operator asked to have drawn rather than assumed: the mesh binds and delivers (to-be 14 and 15 stand), the module alone writes the credential file, refresh stays central (ADR 0050), a switch is the binding changed through a controller seat verb asked for via the console, and the token-carrying shell helper is retired. The controller learns nothing about the agent; that is what "no part" means. To-be 36 is the module's design: the ownership map per path, the fate of the six predecessor files, what the three instruction documents say, the licence tools and skill, the console as a node-scoped provision, the package gap stated honestly, and the order of the build. To-be 14, 29 and 34 carry dated notes; the glossary gains "operator account".
10 KiB
topic, status, date, deciders, reconstructed, extends
| topic | status | date | deciders | reconstructed | extends |
|---|---|---|---|---|---|
| what runs on it | accepted | 2026-10-02 | jochen | false | 02-DECISIONS/0024-model-access-is-a-provision.md |
171. The mesh binds and delivers a licence; the module alone writes the tool's credential; a switch is the binding changed, asked for through the console
Context
The operator's stance, set on 2026-10-02: the controller has no part in the agent module. The module owns the agent's directory under the operator's home and every related file, handles them itself, and carries a licence-switching function as the predecessor's did.
What the predecessor's switching actually was. A registry of accounts held server-side; a tool, callable from a session, that decrypted the chosen account's token on the server, refreshed it if near expiry, and wrote the agent's credentials file on the target node — never returning the token. Beside it, a shell helper that ran the agent with a token read from a plaintext file in the operator's own configuration directory, one token per account, on every workstation; and an enrolment helper that logged in once in a throwaway home and registered what came out. So the central half did the refreshing and the writing; the node held nothing it could refresh with; and the convenience path kept every account's token readable on disk wherever it was wanted.
What the mesh has. To-be 14 is built as far as it goes:
a licence is a named record with a vendor; a consumer is a module on a node and is put on one licence;
the access token is sealed per holder and delivered to the holder's machine; for a refreshable grant
the manager node alone holds the refresh token, encrypted, and refreshes centrally — the one stated
carve-out of ADR 0050. The controller has commands to add a
licence, put a consumer on it, release it, accept a key, set a manager, set and refresh a grant. None
of them is a verb on the mesh-controller seat, so none can be asked for through the console
(ADR 0154 exposes eighteen commands and
not these). The catalogue has the manager module and a consumer module that already writes an
access-token-only credentials file at a path it is told; both are assigned to nothing.
Why refresh is central and must stay so. A refreshable grant rotates its refresh token on use. Two machines each refreshing one account's grant race: the second refresh presents a token the first retired. The predecessor refreshed centrally for this reason, and ADR 0024 kept that half on purpose (the hard half of this already — and it works). ADR 0050 narrowed the consequence to one node.
The two designs are not in conflict, and the line has to be drawn in a record. The controller resolving whose home a file lands in (ADR 0169) and which licence a consumer holds (to-be 14) is what the controller does for every module. "No part" cannot mean that, or the module could not be assigned. It can mean — and this record says it means — that the controller learns nothing about the agent: no file shape, no path, no key, no word beyond the vendor adapter it already has.
Considered Options
- The module keeps its own registry of accounts and tokens, the stance read literally. Rejected: a second secret store outside the vault (ADR 0113); a refresh token on every workstation, widening ADR 0050's one-node carve-out to every machine a person sits at; and two records of one licence, which drift.
- The agent refreshes itself: the mesh delivers a full grant once at a switch and the agent's own refresh keeps it alive. Rejected: the refresh race above, between the agent and the manager and between two machines on one account; and every node then holds a refresh token, which ADR 0050 decided no node does.
- The mesh binds and delivers; the module writes; a switch is the binding changed, asked for through the console. Chosen.
Decision
The consumer is the module on the machine: the operator's interactive sessions on that node, under
that account, hold one licence at a time. That is to-be 15's (node, module) identity, with the
agent module as the module. Two machines may hold different licences, the ordinary case. The mesh's own
sessions on a machine are other modules and hold theirs in their own right.
The mesh delivers; the module writes. The module requires model-access. The mesh resolves the
licence the consumer is on, delivers the access token sealed to the machine as a secret in the module's
own state, and delivers the non-secret facts — the licence's name, what it serves — beside it. The
module's own process writes the agent's credentials file from the delivered secret: under the
account's home, owned by the account, readable by nobody else, written atomically, and access-token-only
— a refresh token found there is removed, because a node never holds one (ADR 0050). The file's content
is never a declared file's content (ADR 0170).
The step runs when the delivered secret changes and on a schedule as a backstop, so a refreshed token
reaches the file without anyone asking.
The controller learns nothing about the agent. Where the file is, what shape it has, what the agent calls its keys, how it is told about the console — all of that is the module's definition and code. The controller contributes the facts it contributes to every module: the account, the home, the licence, the delivery.
A switch is the binding changed. Putting the consumer on another licence is the mesh's existing
act — use this licence, for this consumer — and it becomes a verb on the mesh-controller seat the
way the other verbs did (ADR 0154): the command it already has, served on the bus, listed by the
console. The module serves two tools of its own: one that reports which licence the machine holds and
when its token expires, and one that invokes the seat's verb for a named licence and then waits until
the credentials file carries the new licence's token, answering with the licence's name — never the
token, in any answer, log or event. A skill in the agent's directory wraps the second so a person
asks in a sentence. Switching remains a reaction, not a declaration (ADR 0024): a person asks for it,
and nothing in the declaration language grows a conditional.
Enrolling an account is the mesh's act on the manager node. A new licence is added by name, its grant obtained by a login in a throwaway home on the manager node and adopted sealed to that node's key, as the manager module already does. No token is pasted into a prompt, printed, or passed as an argument (to-be 14's rule for keys).
The shell helper that read tokens from a file is retired, not replaced. A second concurrent session on the same machine under a different licence would need a second consumer identity — the unbuilt half of to-be 14's gap — and is not provided here. Stated so it is not rediscovered as a bug.
A licence the mesh no longer grants is withdrawn at the binding (ADR 0024). The credentials file the module wrote is the module's own output: unassigning the module leaves it, like the agent's other files, and the access token in it expires within hours. Releasing the consumer from the licence is the act that ends its access.
Consequences
- The agent on a workstation authenticates with a token the mesh delivered and refreshes centrally, and no workstation holds a refresh token or any other account's token.
- The manager must run. The refresh path exists in the catalogue and is assigned to nothing; it is a prerequisite of this record, on the control node, and the first thing the build proves.
- The controller gains a verb, not knowledge. The licence commands become seat verbs, each running the command it names, as ADR 0154 did for the others; nothing in them is about the agent.
- A switch is a round trip — binding, composition, push, apply — rather than the predecessor's direct write: seconds to a minute, and reported when done rather than assumed.
- What got harder: running two sessions on one machine under two accounts at once, which the retired helper allowed by keeping tokens readable. The price of not keeping them so.
How it is checked
| Rule | Checked by |
|---|---|
The module's definition declares no secret in a file's content, and requires model-access |
a catalogue test on the module's definition |
| The credentials file is access-token-only, owned by the account, atomic | the consumer module's existing unit tests on the strip and the write, carried into this module; a live check that the file names no refresh token |
| A switch through the console changes the licence and the token, and no answer carries a token | a live check: the bound facts name the new licence, the file's fingerprint changes, the tool's answer and the module's log contain neither token |
| The controller's licence verbs run the commands they name and carry no agent vocabulary | the seat verb's test, as for the eighteen before it |
| The refresh path is live before the module is | the manager assigned on the control node and a refresh observed in the licence's record, before the module's first assignment |
| No workstation holds a refresh token | the live check above, on every machine the module is assigned to |
References
- ADR 0024, ADR 0050, ADR 0055 — what a licence is, who refreshes, what answers
- to-be 14, to-be 15 — the consumer identity and the gap this leaves where it is
- ADR 0154, ADR 0152 — how a verb reaches a person
- ADR 0113 — why there is no second registry
- ADR 0170 — the class the credentials file is in
- the predecessor's
claude-codemodule: its switch tool, its shell helpers and the rules of its skill - mesh-catalog
modules/anthropic-manager,modules/anthropic-consumer— the refresh and the write, as built