Design 38's WP1-WP4b ran: the node's tool runtime is live on all four machines as the operator account, tools are bundles given only their declared words, and a bundle has no bus credential. So the wait on design 38 WP3 is over, the agent module calls nothing and the manager starts every exchange (key, hand-over, waiting login, reconcile), and the manager's daemon now waits on WP4c's record instead. Accounts are stated on all four, sudo -n works for each, the agent is installed on all four; the plan's WP0 shrinks and WP2 gets a configuration-only live proof before any licence.
14 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 |
183. The Anthropic licence manager is a module holding a seat; it hands each node's agent its token over the bus, sealed; the controller and the host have no part
Context
The operator's stance, set on 2026-10-02 and sharpened during the day. The controller has no part in the agent module. The host is module-agnostic: it knows no vendor, no agent, no path under a home. The agent module owns its own files. And there must be a real licence manager — a module that doles out the correct licence in every situation the mesh has: two subscription accounts and one API key today, used by a person's interactive agent on each workstation, by the mesh's own sessions, and by workers.
What the predecessor built, read from its code the same day. Two modules, split after an incident. A manager on exactly one node held every account's full OAuth grant encrypted, rotated each grant under a per-licence lease on a cadence and an expiry floor, published each rotation over its bus with the tokens encrypted, collected the vendor's usage figures per licence, and alerted once a day on repeated failure or on a refresh token within three days of its own expiry. A consumer on every node was the single writer of the agent's credentials file: it applied a published rotation, stripped the refresh token so a node could never rotate, pulled when stale, refused a stale grant by comparing expiries within one lineage, and mirrored a local login back to the manager only after checking the account's identity against the licence's record — because an unchecked mirror had once written one account's grant into another's row and published it mesh-wide. Three touchpoints with fallbacks: the node's interactive agent; the mesh's own sessions on the node, falling back to the node's licence; a worker's own account, falling back to the node's, and refusing to spawn when assigned a licence that could not be served. The split exists because four nodes refreshing one grant destroyed it: an OAuth refresh rotates the refresh token, and the predecessor's own code records both that a reused token killed a licence and that a malformed client id was once misdiagnosed as the same fault. Whether a refresh token is single-use is not documented by the vendor; the predecessor treated it as so, and this record keeps one rotation source for that reason while leaving the fact to be measured.
What the mesh has. ADR 0050 put a per-vendor adapter
inside the controller's licences context, with the carve-out that the manager node holds the refresh
token readably; the catalogue has a manager and a consumer module built on it, assigned to nothing. The
controller's licence commands are not seat verbs and cannot be asked for through the console
(to-be 33). model-access is a vendor-blind
provision (ADR 0024), and the operator's judgement is that the
agent is not a vendor-blind consumer: it is coupled to an Anthropic subscription grant and nothing else,
so a name that hides the vendor misdescribes the coupling
(ADR 0027).
The bus's rule for a secret (to-be 32 §10): the bus is not trusted with one; a secret travels sealed to its recipient, on core request/reply, never through a stream that persists it.
Considered Options
- Keep the lifecycle in the controller (ADR 0050 as
built), and make the agent module a consumer of
model-accessdelivered by the host as a sealed file. Rejected by the operator: the controller and the host would both carry a part of an Anthropic-specific mechanism, and the agent's coupling is misnamed. - The manager delivers each short-lived token through the vault, as a backend-issued secret the vault provides to each consumer (ADR 0113). Rejected: every hourly rotation becomes a vault delivery, a composition and a push to every node, and the host ends up writing a vendor's credential as a file — the module-agnostic host, carrying a vendor's traffic.
- A seat-holding manager module that talks to the agent module on every node over the bus. Chosen.
Decision
The Anthropic licence manager is a module, claude-licence-manager, holding the mesh-scoped seat
anthropic-licence-manager. The seat's contract is the licence verbs: list the licences and their
health, list the bindings, bind or switch a consumer, release one, refresh now, read usage, adopt a
grant, register a node's key, answer a consumer's current token. One holder, on a node the operator
assigns, is what makes rotation happen once (ADR 0126,
ADR 0132). The seat is named for the vendor,
because what it manages is one vendor's grants and nothing else is coupled to it. The vendor-blind
model-access provision stands for the consumers that do not care which vendor answers; the agent is
not among them.
The manager owns the licences. The records, the grants, the bindings per touchpoint, the usage readings and the audit of every switch live in the manager's own store, not in the controller's licences context, which keeps only what it already serves to vendor-blind consumers. The manager is the one rotation source: it alone calls the vendor's token endpoint, under a lease per licence, on an expiry floor and a cadence it declares as a setting.
The long-lived grants are encrypted at rest with a key the vault made for the manager. The vault keeps custody of that one key as the manager's own secret (ADR 0113); the grants themselves — a refresh token per subscription account, the API key — are the manager's rows, readable only by it. This is ADR 0050's carve-out, moved with the manager: one module, one node, the long-lived grants only.
The short-lived tokens travel module to module, sealed, on request/reply. The agent module on each
node makes a keypair of its own when it first runs — a private key made where it is used, never leaving
(ADR 0113) — and registers its public half with the seat. The
manager hands a node its token by calling that node's agent module (<module>.<tool>@<node>,
ADR 0159) with the token
sealed to that key, and the module answers applied or refused and why. An agent module that starts,
or finds its token near expiry, asks the seat for its current token the same way. A token is never
published as an event: what the manager emits — rotated, switched, failing, usage read — names the
licence and nothing secret, and the audit logger records it. This is a second channel for a secret
beside the vault's, and it is bounded as 0050's carve-out is: this vendor, tokens that live hours, sealed
to one recipient, request/reply only.
The agent module alone writes what the agent reads. For a subscription licence it writes the agent's credentials file under the operator's home, as the operator, access-token-only, atomically. For the API-key licence it serves the key through the agent's own key-helper setting, so nothing is written under the home at all. For the mesh's own sessions and workers on that node, it is the local source of their token. The host delivers the module's package and its state directory and knows nothing else: no path under the home, no vendor, no file shape.
A binding is explicit, and a switch is a reaction. Every consumer — a node's interactive agent, the mesh's session on a node, a worker — is bound to a licence by the operator through the seat's verb, with the predecessor's fallbacks: a session inherits its node's licence, a worker inherits its node's, and a worker assigned a licence that cannot be served is refused rather than lent another. Exhaustion is observed and warned about once per crossing of a declared threshold; moving a consumer to another licence is a person's act through the seat's verb, as ADR 0024 says, and the declaration language grows no conditional. An automated policy is not decided here.
A login is attributed only to the account it belongs to. When a person logs in on a node, the agent module reads the account's identity from the agent's own state and offers the grant to the manager sealed to the manager's key; the manager adopts it only when the identity matches the licence the node is bound to, and refuses with a notification otherwise.
Consequences
- One module decides which licence every consumer gets, one module writes what each agent reads, and neither the controller nor the host carries a word of the vendor.
- A second sealed channel exists beside the vault's, bounded as stated. A record that widens it to another vendor or a longer-lived secret is a new decision, not an application of this one.
- The catalogue's
anthropic-managerandanthropic-consumermodules, built on ADR 0050's placement, are retired once the manager runs; the controller's licences context stops holding Anthropic licences. - The console lists the seat's verbs, so a person switches a licence in a sentence, and the controller
gains no
licenceverb. - What got harder: a manager that is down leaves every node on its last token until it expires; the agent module keeps the last token and says so. And a node whose agent module has not registered its key cannot be handed a token, which the manager reports by name.
- Every interactive session on a machine shares the node's one agent directory, and so its licence; twenty sessions share it as one does. A consumer with a licence of its own on the same machine is a worker running from a home of its own with its own agent directory — the worker touchpoint above, for when workers exist (ADR 0003); the predecessor ran its agents that way.
- Not decided here: an automated switch on exhaustion; whether a refresh token is single-use, to be measured in the lab.
How it is checked
| Rule | Checked by |
|---|---|
| Only the seat's holder calls the vendor's token endpoint | a catalogue test: no module but the manager names it; the manager's refresh runs under a lease per licence, tested with two concurrent runs |
| A token crosses the bus only sealed, only on request/reply | a bus test: every message the manager publishes as an event carries no token; the hand-over is a request whose payload opens only with the receiving module's key |
| The agent module's private key never leaves the node | the per-key test of ADR 0113, extended to this module's key |
| The host writes nothing under a home and names no vendor | a catalogue test on the agent module's definition: no file resource under a home, no vendor word in anything the host applies |
| A grant is attributed only to a matching identity | a manager test: a grant whose account identity differs from the bound licence's is refused and a notification emitted |
| An unservable binding refuses rather than lends | a manager test: a worker bound to a dead licence is answered with a refusal, never another licence's token |
| A switch through the console changes the token on the node and nothing in the answer is a token | a live check on one workstation |
The mechanism changed — 2026-10-03, by ADR 0192. What stands: one manager holding the seat, one rotation source, a token sealed to the receiving module's key on request/reply and never an event, the agent module alone writing what the agent reads, the identity guard, the host knowing nothing. What moved: the agent module's code is now a tools bundle the node's runtime serves (ADR 0175, ADR 0188), and a bundle has no bus credential of its own and answers calls rather than making them (ADR 0192's consequences). So the manager starts every exchange: it asks each bound node's agent module for its public key, hands it a token, asks it for a login waiting to be adopted, and reconciles every node on a schedule — which is what "the agent module asks the seat for its current token" and "offers the grant to the manager" in the decision above now mean in practice. The manager's own process needs a bus credential to make those calls, which is the question design 38's WP4c leaves to a record.
References
- ADR 0024, ADR 0050 — the licence as a named thing, the carve-out this moves with the manager
- ADR 0027 — why the seat is named for the vendor
- ADR 0113 — the vault's custody of the manager's key, and the exception stated here
- ADR 0126, ADR 0132, ADR 0159 — a module's seat, its verbs, a call addressed to one machine
- to-be 32 §10 — a secret on the bus
- to-be 36, to-be 39 — the two modules
- the predecessor's
claude-licencesandclaude-codemodules, read 2026-10-02: the lease, the floor, the lineage comparison, the identity guard, the touchpoints