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.
173 lines
11 KiB
Markdown
173 lines
11 KiB
Markdown
---
|
|
layer: to-be
|
|
status: designed
|
|
code: []
|
|
updated: 2026-10-03
|
|
decisions:
|
|
- 02-DECISIONS/0183-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
|
|
---
|
|
|
|
# 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](../../02-DECISIONS/0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md)
|
|
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](36-the-operators-agent-on-a-machine.md),
|
|
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](../../02-DECISIONS/0054-model-usage-is-recorded-at-two-grains.md)) |
|
|
| **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
|
|
|
|
**The manager starts every exchange** (ADR 0183's dated note of 2026-10-03): the agent module is a
|
|
tools bundle, which answers and calls nothing. The manager asks each bound node's module for its public
|
|
key the first time and keeps it. 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 schedule**, every few minutes, the manager visits each bound node: a node whose token is near
|
|
expiry, or that did not answer last time, is handed its current token. A node that was away is
|
|
served when it is back, with nothing for it to ask.
|
|
- **Never as an event.** What the manager emits names the licence and the outcome and carries no token.
|
|
|
|
A node whose module does not answer for its 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](../../02-DECISIONS/0024-model-access-is-a-provision.md)), 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. On its next visit the manager
|
|
asks that node's module for a waiting login, giving its own public key; the module answers with the
|
|
full grant sealed to it and the account's identity read from the agent's own state file. 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`, and `visit` (reconcile one node now). A node's key and
|
|
a consumer's token are not seat verbs: the manager asks the node, by the agent module's own tools.
|
|
|
|
## 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](15-the-agent-session.md), [ADR 0003](../../02-DECISIONS/0003-agents-are-persistent-employees.md)):
|
|
the agent module on that node is their local source, and the reading is theirs to design.
|
|
|
|
## References
|
|
|
|
- [ADR 0183](../../02-DECISIONS/0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md) — the decision
|
|
- [36 — The operator's agent on a machine](36-the-operators-agent-on-a-machine.md) — the consumer on every node
|
|
- [14 — Model access](14-model-access.md) — the vendor-blind provision this sits beside
|
|
- the predecessor's `claude-licences` module: the lease, the floor, the cadence, the cooldown, the identity guard — read 2026-10-02
|