After each reading of a licence with consumers whose session or weekly window is at or above switch_at_pct, the manager moves every consumer bound to it, when switch_auto is on and the settings are valid, to the other licence with known room whose windows are both below room_below_pct and the most room, the first by name on a tie. Each consumer moves as the switch verb does, by usage, and says binding.changed; the act says one licence.switched and one audit entry switched with the window, its percentage and reset and the target's room, and is told to the operator; the switch verb is not told. By hand the rule runs with switch_auto off, and dry answers the moves alone. Builds: design 39 bus-and-switch §26, §27, §28, §32, §33, B22 Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
claude-licence-manager
Holds the anthropic-licence-manager seat: every Anthropic licence the mesh has, kept alive by one
rotation source, and handed to each consumer sealed (novox/hq ADR 0183, ADR 0206, design 39).
How a licence comes to exist
Nothing is configured. Every node running claude-code reports what it holds as that module's
holdings state — the account, fingerprints and expiries, never a token. This module reads every report
when it starts and watches them:
- A report with a refresh token it does not hold is a candidate.
- It asks that node's
claude_code_grant, giving its public key, and receives the grant sealed to it. - It refreshes it. If the vendor exchanges the token, the grant is this module's — encrypted at rest with the key the vault made for it — and from then on it is the only refresher. If not, the candidate is recorded dead and nothing is adopted.
- Several nodes logged in to one account: newest login first; the rest are never exchanged.
- A node reporting that account and bound to nothing is bound to it.
Each node is then handed an access token only, so the agent there never refreshes, and a refresh token appearing on a node later can only be a person's login — which wins if it refreshes.
An API key enters through adopt, from a file on this module's node.
What each consumer holds
This module's bindings state: one key per consumer (a node's name) with the licence, its kind and a
generation that grows with every rotation and switch. claude-code watches its own key and, on a newer
generation, asks current with its public key.
The seat's verbs
licences, bindings, bind, switch, release, refresh, usage, adopt, current — through
the console as anthropic-licence-manager.<verb>. No answer carries a token.
Settings
settings.json in the state directory: cadence_minutes (240), floor_minutes (60),
failures_to_notify (3), cooldown_hours (24), refresh_warn_days (3).
Events
licence.adopted, licence.refused, licence.failing, usage.read — none carries a secret.
Code and tests
Go, one binary (cmd/claude-licence-manager): the seat's verbs and the daemon in one launched bundle,
prepare as the run-once preparation step. The sealed box is claude-code's own format, byte for byte —
the two modules carry the same seal.go — and a test opens one sealed by the TypeScript agent module the
Go one replaced, so the format is the one already on the machines.
go test ./...
# the store against a real postgres:
docker run -d --rm --name licmgr-pg -e POSTGRES_PASSWORD=t -p 15498:5432 postgres:16-alpine
MESH_TEST_POSTGRES=postgres://postgres:t@127.0.0.1:15498/postgres go test ./...
Files it does not know
Each directory or archive this module declares says how it treats a file it did not write (ADR 0422, ADR 0416).
${dir:state}: everything in it is the program's; the mesh moves nothing out (ADR 0422 §2).