Files
hq/03-DESIGN/01-to-be/37-the-anthropic-licence-manager.md
T
jochen 479b8fe72d Revised: the Anthropic licence manager is a module holding a seat, and the agent's configuration lives in its managed directory
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.
2026-10-02 16:54:48 +02:00

10 KiB

layer, status, code, updated, decisions
layer status code updated decisions
to-be designed
2026-10-02
02-DECISIONS/0178-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md
02-DECISIONS/0024-model-access-is-a-provision.md
02-DECISIONS/0050-model-access-is-vendor-agnostic.md
02-DECISIONS/0054-model-usage-is-recorded-at-two-grains.md
02-DECISIONS/0113-the-vault-makes-every-secret.md
02-DECISIONS/0126-a-module-declares-its-own-seats.md
02-DECISIONS/0132-a-seat-carries-the-tools-its-holder-must-serve.md
02-DECISIONS/0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md

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 current verb 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 adopt verb 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