The operator's directions, taken during review: the host is module-agnostic and never writes a vendor's file or anything under a home; the controller has no part; a real licence manager doles out the correct licence in every situation; it talks to the agent module on every node over the bus; the seat is named for the vendor, since the agent is coupled to an Anthropic grant, not to "a model". - ADR 0178 rewritten: `claude-licence-manager` holds the mesh seat `anthropic-licence-manager`, owns the licences, grants (encrypted with a key the vault made for it), bindings per touchpoint, usage and audit; one rotation source under a lease; tokens travel module to module sealed to each node's module key on request/reply, never as an event; the agent module alone writes what the agent reads; the exception to ADR 0113 stated and bounded. Dated mechanism notes on ADR 0050 and 0113. - To-be 37 (new): the manager — its store, the two licence kinds, keeping a grant alive, the hand-over, who gets which licence with the predecessor's fallbacks, adoption with the identity guard, verbs. - To-be 36 rewritten: the mesh's part of the agent's configuration lives in the agent's machine-wide managed directory (settings, tool servers, instruction file), owned whole by the module and written by its code; the home is found except the credentials file; the API-key licence through the key-helper writes nothing under the home; the console as a node-scoped provision; MCP servers as settings with an `mcp_configure` tool; the six predecessor files removed by the operator. - Records 0169–0171 renumbered to 0176–0178 after main gained 0169–0175 today.
10 KiB
layer, status, code, updated, decisions
| layer | status | code | updated | decisions | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| to-be | designed | 2026-10-02 |
|
37 — The Anthropic licence manager
One module knows every Anthropic licence the mesh has, keeps each alive, decides which consumer gets which, and hands every node's agent its token over the bus. ADR 0178 decides it; this is the shape. It is the successor of the predecessor's manager module, built from what that module learned the hard way, and the counterpart of 36 — The operator's agent on a machine, which is the consumer on every node.
1. What it is
A module, claude-licence-manager, holding the mesh-scoped seat anthropic-licence-manager. One
holder, on the node the operator assigns it to — the control node is the natural one, and nothing in the
definition says so. It requires a database for its own store and the bus; it claims the seat; it serves
the seat's verbs. It has no port, no route, no file under anyone's home.
Its store holds four things:
| table | holds |
|---|---|
| licences | name, kind (subscription or api-key), the account's identity (id, address, organisation) once adopted, the grant encrypted at rest, when the access token expires, when the refresh token expires, consecutive failures, the refresh lease, when a person was last notified |
| bindings | one row per consumer: kind (node-agent, node-session, worker), its key (the node, or the node and the worker), the licence, or inherit |
| usage | the vendor's readings per licence per period, raw beside normalised (ADR 0054) |
| audit | every switch, adoption, refusal and drift, with who asked |
The grants are encrypted with a key the vault made for the manager — its one secret requirement.
The vault keeps that key; the manager keeps the grants. That is ADR 0050's carve-out, one module, one
node, the long-lived grants only.
2. The licences it manages today
Two subscription accounts and one API key. They differ in kind and the manager treats them so:
| kind | what the grant is | refresh | what a node is handed | how the agent uses it |
|---|---|---|---|---|
subscription |
an OAuth grant: an access token that lives hours and a refresh token that lives weeks | the manager rotates it, alone | the access token only | written into the agent's credentials file by the agent module, as the operator |
api-key |
a key the operator obtained from the vendor | none; a new key is a new adoption | the key | served to the agent through its key-helper setting; nothing is written under the home |
3. Keeping a grant alive
Carried from the predecessor, where each rule was earned by an incident:
- One rotation source. Only this module calls the vendor's token endpoint. An OAuth refresh is presumed to rotate the refresh token, so a second refresher presenting the old one would kill the grant; whether that presumption holds is to be measured in the lab, and the design is safe either way.
- A lease per licence, taken in the store before the row is read. A duplicate run sees the token its predecessor just wrote, finds hours of life on it, and does nothing.
- An expiry floor and a cadence. Within an hour of expiry a refresh must happen; otherwise a grant is rotated once it is older than a declared setting, so a node that misses one rotation still holds hours of life and a broken refresh surfaces in minutes rather than the next morning.
- Failure is counted and escalated once. Consecutive failures are recorded; past a threshold a notification is emitted, and at most once a day while it stays broken — the predecessor sent one alarm 411 times in 35 hours and the incident went unnoticed inside its own alarm.
- A refresh token's own expiry is warned about three days ahead, because the only remedy is a person logging in again.
- The vendor's reason is logged, never only the status code: a malformed request and a revoked grant both answer 400, and the predecessor built three concurrency fixes for a bug that was a wrong client id.
4. Handing a token to a node
Every node that runs the agent module registers that module's public key with the seat when it first runs. From then on:
- On rotation, the manager calls
claude-code.apply@<node>on every node bound to the rotated licence, with the new token sealed to that node's module key. The module answers applied, or refused and why, and the manager records it. - On a switch, the same call with the other licence's token, and the binding is the authority: the module applies a bind without comparing expiries, because across two licences the numbers are unrelated.
- On a pull — the module starting, or finding its token near expiry — the module calls the seat's
currentverb for its binding and is answered sealed the same way. - Never as an event. What the manager emits names the licence and the outcome and carries no token.
A node whose module has not registered a key cannot be handed a token, and the manager says so by name rather than falling silent. A node whose module refuses — a wrong identity, a stale grant within one lineage — is recorded as drift and reported.
5. Who gets which licence
Three consumer kinds, the predecessor's touchpoints with their fallbacks:
| consumer | bound by | falls back to | if the bound licence cannot be served |
|---|---|---|---|
| the node's interactive agent | the node | nothing: an unbound node has no licence and the agent says so | keeps the last token, which expires within hours; a notification is emitted |
| the mesh's session on a node | the node, for that session | the node's agent licence | refused |
| a worker | the worker | the node's session licence, then the node's | refused: a worker never borrows a person's account |
Binding is a person's act through the seat's verbs, listed by the console: bind, switch,
release. Exhaustion is observed, not acted on: usage is read every few minutes, a crossing of a
declared threshold in the five-hour window is notified once per crossing, and moving a consumer is the
operator's call. Switching remains a reaction, not a declaration
(ADR 0024), and an automated policy — move to the
least-used licence, stay off a dying one — is designed later if wanted, on the readings this module
already keeps.
6. Adopting a grant
A licence enters the mesh one of two ways, and the token never passes through a prompt, a terminal or an argument:
- From a node's login. A person logs in on a node, as they always have. The agent module there reads the account's identity from the agent's own state file, and offers the full grant to the seat sealed to the manager's key. The manager adopts it into the licence the node is bound to only if the identity matches that licence's recorded account; a licence not yet identified is identified by its first adoption; a mismatch is refused and notified, because the predecessor once filed one account's grant into another's row this way.
- An API key is delivered to the manager by the operator through the seat's
adoptverb from a file on the manager's node, never as an argument.
7. What it emits and serves
Events, no secret in any: licence.rotated, licence.switched, licence.adopted,
licence.failing, licence.refused, usage.read — the audit logger records them all.
The seat's verbs, the contract every future holder must serve: licences (each with kind,
identity, expiry, failures, who is bound), bindings, bind, switch, release, refresh (now, one
or all), usage (current and history), adopt, register (a node's module key), current (a
consumer's token, sealed, asked by the consumer's module).
8. Settings
The refresh cadence; the usage threshold; the notification cooldown. Each declared with a default, so one definition serves and one mesh may differ.
How it is checked
| Check | Defends |
|---|---|
| two refresh runs started together rotate one grant once; the second does nothing and says so | ADR 0178, one rotation source |
| every event the manager emits is free of any token; the hand-over opens only with the receiving module's key | ADR 0178, to-be 32 §10 |
| a worker bound to a dead licence is refused, never answered with another licence's token | ADR 0178, the fallbacks |
| a grant offered with a mismatching identity is refused and one notification emitted | ADR 0178, attribution |
| a failing licence notifies once, and once a day after, not once per tick | §3 |
the console lists the seat's verbs and switch changes a workstation's token end to end |
ADR 0132, the exit of the build |
What this does not settle
- An automated switch on exhaustion (§5).
- Whether an OAuth refresh token is single-use; the lab measures it, and §3 holds either way.
- How the mesh's own session and a worker read their token on a node once those exist (to-be 15, ADR 0003): the agent module on that node is their local source, and the reading is theirs to design.
References
- ADR 0178 — the decision
- 36 — The operator's agent on a machine — the consumer on every node
- 14 — Model access — the vendor-blind provision this sits beside
- the predecessor's
claude-licencesmodule: the lease, the floor, the cadence, the cooldown, the identity guard — read 2026-10-02