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. This is its successor's design, revised during review on the operator's directions: the host is module-agnostic, the controller has no part, and a real licence manager hands out the correct licence in every situation. - ADR 0181 (reconstructed): 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; a node with no account refuses one. What the controller shipped on 2026-09-27 without a record. - ADR 0182: inside 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 are the operator's to remove once. - ADR 0183: claude-licence-manager holds the mesh seat anthropic-licence-manager and owns the Anthropic licences, grants (encrypted with a key the vault made for it), bindings per touchpoint, usage and audit; one rotation source under a lease; a token travels 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 host delivers package and state and knows nothing else. A bounded exception to ADR 0113; dated mechanism notes on ADR 0050 and 0113. - To-be 36 (claude-code): the mesh's part of the agent's configuration lives in the agent's machine-wide managed directory, owned whole by the module and written by its code; nothing under the home but the credentials file of a subscription licence; the API-key licence through the key-helper; the console as a node-scoped provision (to-be 34 amended); MCP servers as settings with an mcp_configure tool; one agent directory per machine shared by every session. - To-be 39 (claude-licence-manager): 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, the seat's verbs. Numbers taken across main and every open branch at the time of the merge; to-be 14, 29 and 34 carry dated notes; the glossary gains "operator account".
11 KiB
layer, status, code, updated, decisions
| layer | status | code | updated | decisions | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| to-be | designed | 2026-10-02 |
|
39 — 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 0183 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 |
One agent directory per machine, shared by every interactive session, so a node's binding is the licence of all its sessions at once. A worker is a consumer of its own because it runs from a home of its own, with its own agent directory and credentials file, which the agent module on that node writes for it as it writes the operator's — the predecessor ran its agents exactly so.
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 0183, 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 0183, to-be 32 §10 |
| a worker bound to a dead licence is refused, never answered with another licence's token | ADR 0183, the fallbacks |
| a grant offered with a mismatching identity is refused and one notification emitted | ADR 0183, 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 0183 — 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