To-be 40: building the operator's agent and its licence manager as work packages #297

Merged
mesh-admin merged 6 commits from feat/claude-code-work-packages into main 2026-10-04 10:01:16 +00:00
8 changed files with 501 additions and 69 deletions
@@ -9,6 +9,12 @@ extends: 02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md
# 181. The operator account is a node fact, and a home is a placement root # 181. The operator account is a node fact, and a home is a placement root
> **Progressive insight — 2026-10-04.** This record called a resource under a home *home-scoped*, and a
> module that places one a *home-scoped module*. There is no such kind of module
> ([ADR 0173](0173-the-operators-machine-is-the-meshs-and-a-module-is-what-it-declares.md) §2: a module is
> what it declares), so the three places now say *a resource placed under a home* and *a module placing
> files under a home*. What was decided is unchanged.
*Reconstructed. The controller shipped this on 2026-09-27 and *Reconstructed. The controller shipped this on 2026-09-27 and
[to-be 29](../03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md) recorded it as built without a [to-be 29](../03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md) recorded it as built without a
decision behind it. This record states what was decided, from the code and the design, and adds the decision behind it. This record states what was decided, from the code and the design, and adds the
@@ -35,7 +41,7 @@ its home; the account and its home are machine facts a definition may name in a
and content; a roster file may say it lives under the home, and is then rendered per node, placed under and content; a roster file may say it lives under the home, and is then rendered per node, placed under
that node's account's home, owned by the account, and left out on a node with no account. On that node's account's home, owned by the account, and left out on a node with no account. On
2026-10-02 **all four nodes of the live mesh carry an empty account**: the fact exists and nobody has 2026-10-02 **all four nodes of the live mesh carry an empty account**: the fact exists and nobody has
stated it, so no home-scoped resource can land anywhere yet. stated it, so no resource placed under a home can land anywhere yet.
## Considered Options ## Considered Options
@@ -68,7 +74,7 @@ account. A definition names the account and its home as machine facts, never as
may say it is a home file and is then placed and owned the same way. The controller resolves both at may say it is a home file and is then placed and owned the same way. The controller resolves both at
composition, and the host chowns what it creates. composition, and the host chowns what it creates.
**A node with no account cannot carry a home-scoped resource, and says so.** A roster fact that lives **A node with no account cannot carry a resource placed under a home, and says so.** A roster fact that lives
under the home is left out of that node's declaration rather than written to nowhere. A resource naming under the home is left out of that node's declaration rather than written to nowhere. A resource naming
the account fact on such a node is refused at composition, naming the fact the machine does not have. the account fact on such a node is refused at composition, naming the fact the machine does not have.
A module that writes a person's files is thereby unassignable to a machine with no person on it, which A module that writes a person's files is thereby unassignable to a machine with no person on it, which
@@ -80,7 +86,7 @@ anything.
## Consequences ## Consequences
- **The operator states the account before any home-scoped module lands.** Today none is stated, so the - **The operator states the account before any module placing files under a home lands.** Today none is stated, so the
first assignment of such a module begins with four node records. first assignment of such a module begins with four node records.
- The roster carries each node's account, so a composed ssh configuration logs in as the right person - The roster carries each node's account, so a composed ssh configuration logs in as the right person
on every machine — the gap that surfaced this, closed by the same fact. on every machine — the gap that surfaced this, closed by the same fact.
@@ -9,6 +9,12 @@ extends: 02-DECISIONS/0118-undeclaring-gives-a-unit-back-the-state-it-was-found-
# 182. Inside a home, the mesh owns the directory and the files it places, writes into the tool's own files, and holds everything else as found # 182. Inside a home, the mesh owns the directory and the files it places, writes into the tool's own files, and holds everything else as found
> **Progressive insight — 2026-10-04.** This record said *a home-scoped module* and *the family of
> home-scoped modules*. There is no such kind of module
> ([ADR 0173](0173-the-operators-machine-is-the-meshs-and-a-module-is-what-it-declares.md) §2), and the rule
> is about a directory under a home, whichever module declares it; the three places now say so. The
> decision, its options and its consequences are unchanged.
## Context ## Context
[ADR 0181](0181-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md) lets a module [ADR 0181](0181-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md) lets a module
@@ -35,7 +41,7 @@ use tools that no longer exist. Nothing owns them; nothing will ever rewrite or
`~/.ssh`: the mesh owns the directory and the files it places; it holds the person's private keys and `~/.ssh`: the mesh owns the directory and the files it places; it holds the person's private keys and
personal drop-ins as found. That was argued from the lockout `~/.ssh` can cause. The argument here is personal drop-ins as found. That was argued from the lockout `~/.ssh` can cause. The argument here is
the same shape with a different stake — the person's work rather than the person's way in — and it has the same shape with a different stake — the person's work rather than the person's way in — and it has
to hold for every directory the family of home-scoped modules will touch, so it is a rule, not a to hold for every directory under a home that any module will touch, so it is a rule, not a
section. section.
## Considered Options ## Considered Options
@@ -52,7 +58,7 @@ section.
## Decision ## Decision
**A home-scoped module owns the directory it declares: its existence, owner and mode.** The host creates **A module that declares a directory under a home owns that directory: its existence, owner and mode.** The host creates
it if absent, owned by the account, and never removes it while it holds anything it if absent, owned by the account, and never removes it while it holds anything
([ADR 0030](0030-data-outlives-the-mesh-that-declared-it.md)). Inside it, every path the module touches ([ADR 0030](0030-data-outlives-the-mesh-that-declared-it.md)). Inside it, every path the module touches
is in exactly one of four classes, and **the class is visible in the definition from the shape is in exactly one of four classes, and **the class is visible in the definition from the shape
@@ -105,7 +111,7 @@ finished its definition.
| An owned file found with no record is kept once, then written | host tests of ADR 0102's kept-original rule | | An owned file found with no record is kept once, then written | host tests of ADR 0102's kept-original rule |
| Only the declared keys of a written-into file change, and are given back | host tests of ADR 0102: declared keys set, the rest kept, restored when undeclared | | Only the declared keys of a written-into file change, and are given back | host tests of ADR 0102: declared keys set, the rest kept, restored when undeclared |
| Nothing found is touched | the family's lab check: a machine with a seeded home holding a person's file beside a predecessor's; after apply the person's file is byte-identical, the predecessor's is kept as the original, the mesh's keys are set and the person's keys in the same file remain; after unassign the mesh's files are gone, the keys are restored, the person's files are untouched and the directory stands | | Nothing found is touched | the family's lab check: a machine with a seeded home holding a person's file beside a predecessor's; after apply the person's file is byte-identical, the predecessor's is kept as the original, the mesh's keys are set and the person's keys in the same file remain; after unassign the mesh's files are gone, the keys are restored, the person's files are untouched and the directory stands |
| Every path a home-scoped module touches is classified | a catalogue review rule for this family: each path is a directory, a file, a file written into, a secret-and-step, or absent — the first module written to it is [to-be 36](../03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md) | | Every path a module touches under a home is classified | a catalogue review rule for this family: each path is a directory, a file, a file written into, a secret-and-step, or absent — the first module written to it is [to-be 36](../03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md) |
## References ## References
@@ -152,6 +152,30 @@ the node is bound to, and refuses with a notification otherwise.
| An unservable binding refuses rather than lends | a manager test: a worker bound to a dead licence is answered with a refusal, never another licence's token | | An unservable binding refuses rather than lends | a manager test: a worker bound to a dead licence is answered with a refusal, never another licence's token |
| A switch through the console changes the token on the node and nothing in the answer is a token | a live check on one workstation | | A switch through the console changes the token on the node and nothing in the answer is a token | a live check on one workstation |
> **The mechanism changed — 2026-10-03, by [ADR 0193](0193-every-bundle-the-runtime-serves-is-launched-and-the-runtime-knows-no-language.md)
> and [ADR 0198](0198-a-modules-long-running-code-is-launched-by-the-node-runtime-and-reaches-the-bus-through-it.md).**
> What stands: one manager holding the seat, one rotation source, a token sealed to the receiving
> module's key on request/reply and never an event, the agent module alone writing what the agent
> reads, the identity guard, the host knowing nothing. What moved: both modules' code is bundles the
> node's runtime launches over stdio and is the bus for — `mesh/ask` for a call made on the module's
> behalf, `mesh/publish` and `mesh/subscribe` beside it — so neither holds a bus credential of its own.
> The manager's refresh and visits are a long-running bundle the control node's runtime launches. And,
> by the operator's direction, **the manager starts every exchange**: it asks each bound node's agent
> module for its public key, hands it a token, asks it for a login waiting to be adopted, and reconciles
> every node on a schedule — which is what "the agent module asks the seat for its current token" and
> "offers the grant to the manager" in the decision above now mean in practice. The agent module could
> ask through its runtime; it does not need to.
> **The mechanism changed — 2026-10-04, by [ADR 0206](0206-a-node-reports-the-anthropic-grant-it-holds-and-the-licence-manager-adopts-a-licence-by-refreshing-it.md).**
> What stands: the manager holding the seat, one rotation source, the grants encrypted in its store, a
> token sealed to the receiving module's key on request/reply and never an event, the agent module alone
> writing what the agent reads, the identity guard, bindings as a person's act. What moved: the dated note
> above — the manager no longer starts every exchange. Each node reports what it holds as state, without
> the secret; the manager asks a node for its grant only when a report shows one it does not hold, adopts
> a licence by refreshing it rather than into a licence configured beforehand, and keeps what each
> consumer should hold as state, from which the node fetches its token by request. The rotation and switch
> events are gone.
## References ## References
- [ADR 0024](0024-model-access-is-a-provision.md), [ADR 0050](0050-model-access-is-vendor-agnostic.md) — the licence as a named thing, the carve-out this moves with the manager - [ADR 0024](0024-model-access-is-a-provision.md), [ADR 0050](0050-model-access-is-vendor-agnostic.md) — the licence as a named thing, the carve-out this moves with the manager
@@ -0,0 +1,144 @@
---
topic: what runs on it
status: accepted
date: 2026-10-04
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md
---
# 206. A node reports the Anthropic grant it holds; the licence manager adopts a licence by refreshing it, and what each node should hold is the manager's state
## Context
[ADR 0183](0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md)
made the licence manager a module holding the `anthropic-licence-manager` seat: one rotation source, the
long-lived grants in its own store, a short-lived token handed to a node sealed on request/reply, the
agent module alone writing what the agent reads. How the manager *learns* a licence, and who starts each
exchange, it left to a later shape, and three texts have since disagreed: ADR 0183 has a node register
its key and the manager adopt a login only into a licence the node is already bound to; its dated note
of 2026-10-03 has the manager start every exchange and visit every node on a schedule; the agent module
as built asks the seat for its token when an event says to, and pushes a login to the seat.
**The operator settled it on 2026-10-04, in the operator's own words:** the manager must hold the active refresh token;
whichever node a login happened on holds the latest one; every client publishes what its credentials
file holds, the manager sees a licence it does not own yet and takes it into its store, and from then on
rotates it and distributes the access token. A manager launched for the first time holds no licence and
accepts what the clients report. Several nodes report the same account — today the nodes are all logged in
to one personal account — and before the manager adopts a grant it must know the refresh token still
works.
Two facts bound how that is built:
- **A refresh token cannot be published.** Anything published on the bus is kept, and a secret never
enters a stream, sealed or not ([design 32](../03-DESIGN/01-to-be/32-what-a-module-declares.md) §10).
A module's state is a stream too, and the runtime refuses a value carrying a field named like a
credential ([ADR 0201](0201-a-module-keeps-its-current-state-in-key-value-buckets-it-declares-and-reaches-through-the-runtime.md);
refused live on 2026-10-04 for an `Authorization` header).
- **A refresh token can only be checked by using it.** No endpoint answers "is this refresh token
valid" without exchanging it, and an exchange is presumed to rotate it (ADR 0183: the predecessor
lost a licence to a reused one). Checking and adopting are therefore one act, and whoever checks
becomes the token's only live holder.
Since ADR 0201 the bus has the shape this needs: **state** every node sees, including one that joins
later or a manager that starts later, read whole on start and then watched.
## Considered Options
1. **Each node publishes its credentials file, the token included.** What the operator described,
literally. Rejected for the token only: it would sit in a stream every principal that reads the
bucket can read, for as long as the bucket keeps it, and the runtime refuses it anyway.
2. **The manager visits every node on a schedule and collects a waiting login** (ADR 0183's dated
note). Rejected: the manager must know every node in advance and poll it, a node that joins later
waits for the next visit, and "what does each node hold" lives nowhere anyone can read.
3. **Each node reports what it holds as state, without the secret; the manager asks for the secret
only when the report shows a grant it does not hold, and adopts by refreshing.** Chosen: the
operator's flow, with the one part that cannot be on the bus moved onto request/reply.
## Decision
**1. Every agent module reports what its node holds, as its own state.** One key per node in the
module's `holdings` state: the account's identity as the agent's own state file names it (account id,
address, organisation), the kind, the refresh token's **fingerprint** and whether one is present at
all, the access token's fingerprint and expiry, the licence it was last handed, and when the credentials
file last changed. Written when the module starts — a node already logged in when the module is first
assigned reports at once — and again whenever the credentials file changes. **No token, ever**: a
fingerprint names a token without being one.
**2. A licence is an account, and the manager learns it from the reports.** The manager reads every
node's `holdings` at start and watches them. A report carrying a refresh token whose fingerprint the
manager does not hold is a **candidate**: for an account it has no licence for yet, a new licence; for
one it has, a login made since. A manager launched for the first time holds no licence and treats
every report as a candidate. An API key still enters only through the seat's `adopt` verb, from a file
on the manager's node.
**3. The secret travels only when asked for.** For a candidate, the manager calls that node's agent
module on request/reply, giving its own public key, and is answered with the grant sealed to that key
(ADR 0183's channel, unchanged).
**4. Adopting is refreshing.** The manager exchanges the candidate's refresh token at the vendor's
endpoint under its lease for that account. If the exchange succeeds, the grant it got back is the
licence's, stored encrypted, and the manager is from then on its only rotation source. If it fails, the
candidate is recorded dead, nothing is adopted, and the report says so. **Several nodes, one account:**
candidates for one account are tried newest login first; the first that refreshes is adopted, and the
manager does not exchange the others.
**5. A node holds an access token only, so the latest login wins.** A node bound to an adopted licence
is handed the access token and nothing else, and the agent module writes the credentials file without a
refresh token — so the agent on the node can never refresh it, and two refreshers never hold one grant.
A refresh token appearing in a node's file afterwards can therefore only be a person's login there; its
report makes it a candidate, and if it refreshes it replaces the licence's grant. That is the operator's
"whichever node a login happened on holds the latest one", made mechanical.
**6. What each consumer should hold is the manager's state.** One key per consumer in the manager's
`bindings` state: the licence, its kind, and a **generation** that increases with every rotation and
every switch. The agent module watches its own key; when the generation is newer than the one it
applied, it asks the seat's `current` verb for the token, sending its public key, and is answered sealed
(request/reply). A node that was away reads its key when it is back and asks once. The `licence.rotated`
and `licence.switched` events go: what they announced is now the state itself, and a node needs the
latest, not the history.
**7. A first binding follows the login.** When the manager adopts a licence from a node's report, a
node with no binding yet whose report names that account is bound to it. Every later change is a
person's act through `bind`, `switch` and `release`, as ADR 0183 says.
**8. The identity guard stands, on two sources.** The account a grant is filed under is the identity
the node read from the agent's own state. Where the vendor's answer to the refresh names the account,
the manager compares the two and refuses a mismatch with a notification; whether it names it is
measured when the manager is built, and the record of which source decided is kept in the audit.
## Consequences
- The manager needs no configuration to start: launched on a mesh whose nodes are logged in, it adopts
every account they hold, one licence each, from the newest login that still refreshes.
- Every node's holding is readable by anyone allowed to read the state — the console, an agent, the
operator — without a token in sight, which is what `licence_status` on each node answered one at a
time.
- **What got harder:** adoption consumes the refresh token the node held. On a node whose grant was
adopted, the agent's own copy is dead from that moment; until the manager hands it an access token
(decision 6), the agent keeps the access token it already had, which lives hours. And a node whose
file still holds a refresh token after adoption — it was not handed one yet — is a second holder of a
dead grant, not a live one, so the rotation-source rule holds.
- A candidate whose refresh fails is not retried by the manager: a dead refresh token does not come
back. A person logs in again, and the new report is a new candidate.
- Nothing in the reports is secret, but they do say which account each node uses; readers of the state
are declared in manifests like any other.
## How it is checked
| Rule | Checked by |
|---|---|
| No report carries a token | the runtime refuses a credential-named field (ADR 0201's test); the agent module's test: a report built from a full credentials file holds fingerprints and identity only |
| A node already logged in reports at start | the agent module's test: with a credentials file present and unchanged, starting writes its `holdings` key |
| A candidate is adopted only by a successful refresh, newest login first, once per account | the manager's tests against a stub vendor: two reports for one account, the newer refreshes and is adopted, the older is never exchanged; a failing refresh adopts nothing and records the candidate dead |
| A node is handed an access token only | the agent module's test: the file it writes after a hand-over holds no refresh token |
| A newer generation is fetched once, by request | the agent module's test: a `bindings` change with a newer generation asks `current` once; an equal one asks nothing |
| No event carries a token, and none announces a rotation any more | the manager's test of everything it publishes |
| Live | the manager launched with no licence on a mesh whose four nodes are logged in to one account adopts one licence, binds the four nodes, and each node's file then holds an access token and no refresh token |
## References
- [ADR 0183](0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md) — the manager, its seat and its channel, which this extends
- [ADR 0201](0201-a-module-keeps-its-current-state-in-key-value-buckets-it-declares-and-reaches-through-the-runtime.md) — module state, and the refusal of a secret in it
- [design 32](../03-DESIGN/01-to-be/32-what-a-module-declares.md) §10 — no secret in a stream
- [to-be 36](../03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md), [to-be 39](../03-DESIGN/01-to-be/39-the-anthropic-licence-manager.md) — the two modules, amended by this record
+1
View File
@@ -304,6 +304,7 @@ python3 00-META/checks/index.py fail if stale
- **0203** — [The account's environment is one module's, and every module contributes to it](0203-the-accounts-environment-is-one-modules-and-every-module-contributes-to-it.md) - **0203** — [The account's environment is one module's, and every module contributes to it](0203-the-accounts-environment-is-one-modules-and-every-module-contributes-to-it.md)
- **0204** — [A module contributes shell code to the login shell in named slots, and the login shell is the mesh's seat](0204-a-module-contributes-shell-code-to-the-login-shell-in-named-slots.md) - **0204** — [A module contributes shell code to the login shell in named slots, and the login shell is the mesh's seat](0204-a-module-contributes-shell-code-to-the-login-shell-in-named-slots.md)
- **0205** — [Software the distribution does not package ships as a pinned archive of the module's own](0205-software-the-distribution-does-not-package-ships-as-a-pinned-archive-of-the-module.md) - **0205** — [Software the distribution does not package ships as a pinned archive of the module's own](0205-software-the-distribution-does-not-package-ships-as-a-pinned-archive-of-the-module.md)
- **0206** — [A node reports the Anthropic grant it holds; the licence manager adopts a licence by refreshing it, and what each node should hold is the manager's state](0206-a-node-reports-the-anthropic-grant-it-holds-and-the-licence-manager-adopts-a-licence-by-refreshing-it.md)
### How it is built ### How it is built
@@ -2,8 +2,9 @@
layer: to-be layer: to-be
status: designed status: designed
code: [] code: []
updated: 2026-10-02 updated: 2026-10-04
decisions: decisions:
- 02-DECISIONS/0206-a-node-reports-the-anthropic-grant-it-holds-and-the-licence-manager-adopts-a-licence-by-refreshing-it.md
- 02-DECISIONS/0181-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md - 02-DECISIONS/0181-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md
- 02-DECISIONS/0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md - 02-DECISIONS/0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md
- 02-DECISIONS/0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md - 02-DECISIONS/0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md
@@ -54,8 +55,10 @@ instruction file and the manager's tools:
| the console's entry in the agent's user-scope state | the managed settings' tool-server key, from the console's provision (§4) | | the console's entry in the agent's user-scope state | the managed settings' tool-server key, from the console's provision (§4) |
**The home.** Under [ADR 0182](../../02-DECISIONS/0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md) **The home.** Under [ADR 0182](../../02-DECISIONS/0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md)
every path under `~/.claude` is *found*, with one exception: the agent's credentials file, which the the module owns the directory `~/.claude` — that it exists, that the operator owns it, its mode,
module's own code writes for a subscription licence (§5). The person's memory, history, projects, local `0700` — and declares it, so the mesh refuses a second module owning it. Of what is inside, it owns only
the agent's credentials file, which its own code writes for a subscription licence (§5); every other path
is *found*. The person's memory, history, projects, local
settings, their own rules, skills and tool servers are never read or written by the mesh. **The six settings, their own rules, skills and tool servers are never read or written by the mesh. **The six
predecessor files are the operator's to remove, once, on each workstation**; the module's documentation predecessor files are the operator's to remove, once, on each workstation**; the module's documentation
lists them, and until they go the agent reads stale instructions beside the mesh's. lists them, and until they go the agent reads stale instructions beside the mesh's.
@@ -63,9 +66,12 @@ lists them, and until they go the agent reads stale instructions beside the mesh
## 2. What the module declares and what its code writes ## 2. What the module declares and what its code writes
**Declared, applied by the host:** the agent's package (§7); the module's state directory; a facts file **Declared, applied by the host:** the agent's package (§7); the module's state directory; a facts file
in that directory carrying the node's name, the operator account, the console's endpoint, the module's in that directory carrying the node's name and the console's endpoint, and a settings file carrying the
settings; the bus, the console's provision, and that it uses the `anthropic-licence-manager` seat. role and the extra tool servers, merged from the module's settings layers — the bundle is told the two
Nothing under the home, nothing under `/etc`. files' paths, because a bundle's words are paths and constants only (ADR 0192); the bus, the console's provision, and that it uses the `anthropic-licence-manager` seat.
Two directories, declared so the ownership check sees them: the agent's managed directory under
`/etc`, root's, and `~/.claude` under the operator's home, the operator's. No *file* resource under
either: what is in them is written by the module's code (§2 below) or is the person's.
**Written by the module's code**, from the facts file and the manager's hand-over, whenever either **Written by the module's code**, from the facts file and the manager's hand-over, whenever either
changes: changes:
@@ -111,17 +117,20 @@ the playbooks in the record.
## 4. The console ## 4. The console
The module tells the agent where the console is, and the port is the console's to say. **The console > **Revised 2026-10-03, building it.** The vendor's managed-settings key for tool servers refuses any
provides a node-scoped provision** — its MCP endpoint on loopback — serving the port the machine gave > URL that is not `https://`, including one on loopback, so it cannot carry the console. The module
it, and the module requires it. A requirement names what the consumer is coupled to > owns the vendor's **exclusive** managed tool-server file instead (operator's choice): the console as
([ADR 0027](../../02-DECISIONS/0027-a-provision-names-what-the-consumer-is-coupled-to.md)); co-location > `mesh`, over HTTP on loopback, and every server in the module's `mcp_servers` setting — and no other.
resolves it; a machine without the console refuses the module by name. [To-be 34](34-the-console.md) is > A server added by hand, a project's own file and a plugin's servers stop loading; claude.ai's
amended in the same change; issue 192 (open) found the gap. > connectors are kept by a managed setting. A person's own servers move into the setting, for the mesh
> or for one node. Stdio straight to the bus, through the runtime's own client, was weighed and left
> for later: it needs a verb the delivered runtime does not have, and a session holding its own bus
> connection breaks on a credential rotation.
**Other tool servers** a person wants on every machine, or on one, are a declared setting of this module **Other tool servers** a person wants on every machine, or on one, are a declared setting of this module
— mesh layer or node layer — rendered into the same managed key. A module tool, `mcp_configure`, — mesh layer or node layer — rendered into the same managed file. The person sets them with the
validates a server and sets the setting through the controller's settings verb, so the list stays controller's `settings` verb on this module, so the list stays declared state; the list is the operator's
declared state. The agent's own HTTP-only constraint for managed servers applies; a person's local choice, set where every setting is set. The agent's own HTTP-only constraint for managed servers applies; a person's local
command-based servers stay their own, in their own file. command-based servers stay their own, in their own file.
**The entry's name is `mesh`.** The hand-made entry both workstations carry today is named after this **The entry's name is `mesh`.** The hand-made entry both workstations carry today is named after this
@@ -131,36 +140,42 @@ it is the person's to remove, and until then the agent sees the mesh's tools twi
## 5. The licence: the consumer side ## 5. The licence: the consumer side
[ADR 0183](../../02-DECISIONS/0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md) [ADR 0183](../../02-DECISIONS/0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md)
decides it; to-be 39 is the manager's half. This module: decides it and [ADR 0206](../../02-DECISIONS/0206-a-node-reports-the-anthropic-grant-it-holds-and-the-licence-manager-adopts-a-licence-by-refreshing-it.md) says how it moves; to-be 39 is the manager's half. This module:
- **makes a keypair** in its state the first time it runs and registers the public half with the seat; - **makes a keypair** in its state the first time it runs, and sends the public half with every request
- **serves `apply`**: the manager's hand-over, a token sealed to the module's key, with the licence's that is answered sealed;
name and kind. A rotation of the same licence is applied only if newer within one lineage; a switch is - **reports what the node holds**, as its own `holdings` state, one key for this node: the account's
applied regardless, because across licences the expiries are unrelated. The answer says applied or identity read from the agent's state file, the kind, the refresh token's fingerprint and whether one is
refused and why, and never echoes a token; present, the access token's fingerprint and expiry, the licence and generation it last applied, when the
- **pulls** at start and when its token nears expiry, by the seat's `current` verb, and keeps the last credentials file last changed. Written at start — a node already logged in reports at once — and on every
token when the manager does not answer, saying so; change of the file. Never a token: the runtime refuses one anyway;
- **writes** for a subscription licence the credentials file as the operator, access-token-only; for the - **hands over a grant only when asked**: `claude_code_grant` answers the manager, which gives its public
API-key licence sets the key-helper in the managed settings to a small program that prints the key key, with the full grant in the credentials file sealed to that key — the one time a refresh token
from the module's state, so no file under the home is touched; leaves the node, for the manager to adopt by refreshing it;
- **offers a login to the manager**: when the credentials file changes by a person's login, it reads the - **watches the manager's `bindings` state** for this node, and when the generation is newer than the one
account's identity from the agent's state file and offers the grant to the seat, sealed to the manager's it applied, asks the seat's `current` verb for the token, sealed to its own key. A rotation of the same
key, for adoption; the manager decides; licence is applied only if newer within one lineage; a switch is applied regardless;
- **serves `licence_status`**: which licence and kind this node holds, when the token expires, whether - **writes** for a subscription licence the credentials file as the operator, **access-token-only** — so
the agent here never refreshes, and a refresh token appearing later is a person's login, reported like
any change; for the API-key licence sets the key-helper in the managed settings to a small program that
prints the key from the module's state, so no file under the home is touched;
- **serves `claude_code_status`**: which licence and kind this node holds, when the token expires, whether
the file matches what was handed over — by fingerprint, never by value. the file matches what was handed over — by fingerprint, never by value.
Switching is the seat's `switch` verb, asked through the console; this module only applies what it is Switching is the seat's `switch` verb, asked through the console; this module only applies what the
handed. state says it should hold. *2026-10-04:* this replaces the manager's visits of 2026-10-03 (ADR 0183's dated
note): the node reports, the manager asks for a secret only when a report shows one it does not hold, and
a token is fetched by request when the state says it changed.
## 6. Scope, settings and the order of assignment ## 6. Scope, settings and the order of assignment
**Every node with an operator account** ([ADR 0181](../../02-DECISIONS/0181-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md)). **Every node with an operator account** ([ADR 0181](../../02-DECISIONS/0181-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md)).
None has one today; the operator states them first. **Per node:** the role. **Per mesh or per node:** All four nodes carry one since 2026-10-03. **Per node:** the role. **Per mesh or per node:**
extra tool servers. **Prerequisite:** the manager holds its seat and has adopted the licences. extra tool servers. **Prerequisite:** the manager holds its seat and has adopted the licences.
**Order:** the manager assigned and a refresh observed; the console's provision in the catalogue; this **Order:** the manager assigned and a refresh observed; the console's provision in the catalogue; this
module on one workstation; the six predecessor files and the hand-made console entry removed there; a module on one workstation; the six predecessor files and the hand-made console entry removed there; a
new session read to confirm it sees the mesh's instruction file, the console's tools under `mesh`, and new session read to confirm it sees the mesh's instruction file, the console's five tools under `mesh`, and
its licence; then the rest. its licence; then the rest.
## 7. The package ## 7. The package
@@ -178,13 +193,13 @@ installer is rejected: it puts a self-updating binary under the person's home, i
| Check | Defends | | Check | Defends |
|---|---| |---|---|
| the module's definition names no node, path or login, declares nothing under a home or `/etc`, and no file resource carries a secret | ADR 0112, ADR 0155, ADR 0183 | | the module's definition names no node, path or login, declares no file under a home or `/etc` (only the two directories), and no file resource carries a secret | ADR 0112, ADR 0155, ADR 0183 |
| on a lab machine with an account and a seeded home holding a person's rule file and the predecessor's leftovers: after assign, the managed directory holds the mesh's files, the home is byte-identical except the credentials file, which is owned by the operator and names no refresh token; after unassign, the managed directory's files are gone and the home is untouched | ADR 0182, the host's agnosticism | | on a lab machine with an account and a seeded home holding a person's rule file and the predecessor's leftovers: after assign, the managed directory holds the mesh's files, the home is byte-identical except the credentials file, which is owned by the operator and names no refresh token; after unassign, the managed directory's files are gone and the home is untouched | ADR 0182, the host's agnosticism |
| on a lab machine with no account, the assignment is refused naming the fact | ADR 0181 | | on a lab machine with no account, the assignment is refused naming the fact | ADR 0181 |
| a switch asked of the seat through the console changes the licence and the token on the node; no tool answer and no log line holds a token | ADR 0183 | | a switch asked of the seat through the console changes the licence and the token on the node; no tool answer and no log line holds a token | ADR 0183 |
| the API-key binding writes nothing under the home and the agent authenticates through the helper | ADR 0183 | | the API-key binding writes nothing under the home and the agent authenticates through the helper | ADR 0183 |
| the console's provision resolves by co-location; a machine without the console refuses the module by name | ADR 0027, ADR 0152 | | the console's provision resolves by co-location; a machine without the console refuses the module by name | ADR 0027, ADR 0152 |
| a new session on the assigned workstation lists the console's tools under `mesh` and answers "which node am I" from the instruction file | the exit of the build | | a new session on the assigned workstation lists the console's five tools under `mesh` ([ADR 0195](../../02-DECISIONS/0195-the-meshs-tools-are-found-by-address-not-announced-whole.md)) and answers "which node am I" from the instruction file | the exit of the build |
## What this does not settle ## What this does not settle
@@ -2,8 +2,9 @@
layer: to-be layer: to-be
status: designed status: designed
code: [] code: []
updated: 2026-10-02 updated: 2026-10-04
decisions: decisions:
- 02-DECISIONS/0206-a-node-reports-the-anthropic-grant-it-holds-and-the-licence-manager-adopts-a-licence-by-refreshing-it.md
- 02-DECISIONS/0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md - 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/0024-model-access-is-a-provision.md
- 02-DECISIONS/0050-model-access-is-vendor-agnostic.md - 02-DECISIONS/0050-model-access-is-vendor-agnostic.md
@@ -74,22 +75,22 @@ Carried from the predecessor, where each rule was earned by an incident:
## 4. Handing a token to a node ## 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 *Amended 2026-10-04 by [ADR 0206](../../02-DECISIONS/0206-a-node-reports-the-anthropic-grant-it-holds-and-the-licence-manager-adopts-a-licence-by-refreshing-it.md)*, replacing the manager's visits: **what each consumer
runs. From then on: should hold is the manager's state, and the token is fetched when it changes.**
- **On rotation**, the manager calls `claude-code.apply@<node>` on every node bound to the rotated - **The manager keeps a `bindings` state**, one key per consumer: the licence, its kind, and a
licence, with the new token sealed to that node's module key. The module answers *applied*, or **generation** that increases with every rotation and every switch. Nothing in it is secret.
*refused* and why, and the manager records it. - **The agent module on each node watches its own key.** When the generation is newer than the one it
- **On a switch**, the same call with the other licence's token, and the binding is the authority: the applied, it asks the seat's `current` verb, sending its public key, and is answered with the token
module applies a bind without comparing expiries, because across two licences the numbers are sealed to it — request/reply, never an event. A node that was away reads its key when it is back and
unrelated. asks once; a manager that is down leaves every node on its last token, which lives hours.
- **On a pull** — the module starting, or finding its token near expiry — the module calls the seat's - **On a switch** the agent applies the new licence's token without comparing expiries, because across
`current` verb for its binding and is answered sealed the same way. two licences the numbers are unrelated; within one licence it applies only a newer grant.
- **Never as an event.** What the manager emits names the licence and the outcome and carries no token. - **No event announces a rotation or a switch.** What they announced is the state itself, and a node
needs the latest, not the history. What the manager still emits names an 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 A consumer that never asks is visible: its own report (§6) names the licence and generation it holds,
rather than falling silent. A node whose module refuses — a wrong identity, a stale grant within one and a node behind its binding is drift the manager reports.
lineage — is recorded as drift and reported.
## 5. Who gets which licence ## 5. Who gets which licence
@@ -116,27 +117,46 @@ already keeps.
## 6. Adopting a grant ## 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 *Amended 2026-10-04 by [ADR 0206](../../02-DECISIONS/0206-a-node-reports-the-anthropic-grant-it-holds-and-the-licence-manager-adopts-a-licence-by-refreshing-it.md)*: a licence is an account, learned from what the nodes
argument: report, and adopted by refreshing it.
- **From a node's login.** A person logs in on a node, as they always have. The agent module there reads - **Every node reports what it holds**, as the agent module's `holdings` state, one key per node: the
the account's identity from the agent's own state file, and offers the full grant to the seat sealed account's identity read from the agent's own state file, the kind, the refresh token's fingerprint and
to the manager's key. The manager adopts it into the licence the node is bound to **only if the whether one is present, the access token's fingerprint and expiry, the licence and generation it was
identity matches** that licence's recorded account; a licence not yet identified is identified by its last handed, when the credentials file last changed. Written when the module starts — a node already
first adoption; a mismatch is refused and notified, because the predecessor once filed one account's logged in reports at once — and on every change. Never a token.
grant into another's row this way. - **The manager reads every report at start and watches them.** A report with a refresh token whose
fingerprint the manager does not hold is a candidate: a new licence for an account it has none for, a
login made since for one it has. A manager launched for the first time holds no licence and takes every
report as a candidate.
- **The secret is asked for, never published.** For a candidate the manager calls that node's agent
module, giving its own public key, and is answered with the grant sealed to it.
- **Adopting is refreshing.** The manager exchanges the candidate's refresh token under its lease for
that account; success makes the returned grant the licence's and the manager its only rotation source;
failure records the candidate dead and adopts nothing. Candidates for one account are tried newest login
first, and the first that refreshes ends the search — the others are never exchanged.
- **The latest login wins.** A bound node is handed an access token only and its file holds no refresh
token, so a refresh token appearing there later is a person's login; its report makes it a candidate,
and if it refreshes it replaces the licence's grant.
- **A first binding follows the login**: a node with no binding whose report names the adopted account
is bound to it. Every later change is `bind`, `switch` or `release`.
- **The identity guard** files a grant under the identity the node read; where the vendor's refresh
answer names the account too, a mismatch is refused and notified. Which source decided is audited.
- **An API key** is delivered to the manager by the operator through the seat's `adopt` verb from a file - **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. on the manager's node, never as an argument.
## 7. What it emits and serves ## 7. What it emits and serves
**Events**, no secret in any: `licence.rotated`, `licence.switched`, `licence.adopted`, **Events**, no secret in any: `licence.adopted`, `licence.failing`, `licence.refused`, `usage.read` —
`licence.failing`, `licence.refused`, `usage.read` — the audit logger records them all. the audit logger records them all. *2026-10-04 (ADR 0206):* `licence.rotated` and `licence.switched` are
gone; a rotation or a switch is a new generation in the `bindings` state.
**State**: `bindings`, which it keeps; the agent module's `holdings`, which it reads.
**The seat's verbs**, the contract every future holder must serve: `licences` (each with kind, **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 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 or all), `usage` (current and history), `adopt`, and `current` (a consumer's token, sealed to the key the
consumer's token, sealed, asked by the consumer's module). consumer sends — ADR 0206). The manager asks a node for a candidate grant by the agent module's own tool.
## 8. Settings ## 8. Settings
@@ -0,0 +1,216 @@
---
layer: to-be
status: designed
code: []
updated: 2026-10-04
decisions:
- 02-DECISIONS/0206-a-node-reports-the-anthropic-grant-it-holds-and-the-licence-manager-adopts-a-licence-by-refreshing-it.md
- 02-DECISIONS/0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md
- 02-DECISIONS/0181-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md
- 02-DECISIONS/0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md
- 02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md
- 02-DECISIONS/0188-a-modules-own-code-is-bundles-in-any-language-and-a-tools-bundle-speaks-mcp-to-the-runtime.md
- 02-DECISIONS/0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.md
- 02-DECISIONS/0149-the-live-mesh-is-the-test-bed.md
- 02-DECISIONS/0027-a-provision-names-what-the-consumer-is-coupled-to.md
---
# 40. Building the operator's agent and its licence manager
**The work of [design 36](36-the-operators-agent-on-a-machine.md) and [design 39](39-the-anthropic-licence-manager.md),
broken into packages that each end at something a person can see run, in the order their
dependencies allow.** The two designs are the authority on *what* is built; this document holds the
packages, their order, their sizes and their proofs, and is wrong the moment it disagrees with them.
It is the shape [design 38](38-building-the-operators-machine.md) gives the operator's machine.
*Revised 2026-10-03, after design 38's WP1–WP4b ran:* the node's tool runtime is live on all four
machines, tools are bundles it serves and each is given only the words its artifact declares, every
bundle is a child the runtime launches over stdio and is the bus for
([ADR 0193](../../02-DECISIONS/0193-every-bundle-the-runtime-serves-is-launched-and-the-runtime-knows-no-language.md),
[ADR 0198](../../02-DECISIONS/0198-a-modules-long-running-code-is-launched-by-the-node-runtime-and-reaches-the-bus-through-it.md)),
and the console offers five tools over addresses
([ADR 0195](../../02-DECISIONS/0195-the-meshs-tools-are-found-by-address-not-announced-whole.md)). What changed
in this plan: the wait on design 38's WP3 is over; the manager starts every exchange, by the operator's
direction (ADR 0183's dated note); and the manager's daemon is a long-running bundle the runtime launches,
which ADR 0198 decided the same day — nothing in this plan waits on another record.
## How this is built, and where it is run
**On the live mesh, by the operator's decision** ([ADR 0149](../../02-DECISIONS/0149-the-live-mesh-is-the-test-bed.md)).
Every package is written with unit tests, committed on one branch per repository
([playbook 07](../../00-META/process/07-feature-branches.md)), and proven on the machines: one
workstation first for the agent, the control node first for the manager, then the rest. A broken agent
module leaves a workstation's agent without the mesh's instructions or with a stale token until the next
push; the person's own files under the home are out of the failure's reach, by
[ADR 0182](../../02-DECISIONS/0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md).
Each package names what proves it. A package that cannot name its proof is divided until it can.
## What exists already, measured
Measured 2026-10-03 on the four machines and in the repositories.
| Piece | Today | Becomes |
|---|---|---|
| the node's tool runtime | live on all four, a host process; **runs as the operator account**; listens for the console on loopback at a port its own code fixes; launches or imports every assigned module's tools bundle and hands each its declared words | serves the agent module's tools; gains one provision for its endpoint (WP1) |
| the operator account | **stated on all four** — the runtime runs as it | read by the agent module from the runtime's own words |
| escalation | passwordless `sudo` for the operator account on all four — a fact about the machines, checked by nobody | how the agent module writes its managed directory under `/etc` |
| the agent itself | installed on all four, at four different versions, all above the one the managed tool-server key needs | declared as the module's package |
| a bundle's words | paths and constants written with `${dir:…}` and `${port:…}` only; a fact the mesh knows reaches a bundle as a file whose path is a word | the agent module's facts file and settings file |
| a bundle calling a tool | `mesh/ask` through the runtime that launched it (ADR 0198); no bundle holds a bus credential | how the manager visits every node |
| a module's own long-running code | a bundle the runtime launches and restarts (ADR 0198); the runtime's subscription and grants built, the modules moving in design 38 WP4c's waves | the manager's daemon (WP3, WP4) |
| the vendor's refresh, the sealed box, the grant file | `anthropic-manager` in the catalogue, built on the controller placement ADR 0183 moved away from; assigned to nothing | its client ported into the manager; the module retired (WP6) |
| the credentials write, the strip, the identity read | `anthropic-consumer` in the catalogue; tested; assigned to nothing | ported into the agent module with its tests; the module retired (WP6) |
| the predecessor's manager and consumer | the lease per licence, the expiry floor, the lineage comparison, the identity guard, three touchpoints, cooldowns | ported as logic with its tests |
## The order the work allows
```
WP0 the operator names the licences and each node's role (the live mesh) ── an hour
WP1 the runtime provides its endpoint (mesh-tools) ── small
WP2 the agent module (mesh-catalog) ──┐ WP2 needs WP1;
WP3 the manager's code, built and tested (mesh-catalog) ──┘ WP3 is independent
│
WP2 live: one workstation, configuration only — no licence yet ── the first live proof
│
WP4 the manager live on the control node ── its daemon a long-running bundle (ADR 0198)
WP5 the licence end to end on one workstation
WP6 the rest of the nodes, and the predecessor's remains
```
## WP0 — The operator names the licences and each node's role
*The live mesh. An hour, and it is the operator's.* The accounts are stated already. What remains: the
names of the two subscription licences and the API key; each node's role, as the agent module's setting
on the node layer once the module is registered.
**Proof.** The module's settings list a role for every node; the licences have names.
## WP1 — The runtime provides its endpoint
*mesh-tools. An hour.*
**What changes.** The `node-tools` manifest provides a node-scoped provision, `mcp-endpoint`, serving
the port its code listens on, the way the local model server serves its API
([ADR 0027](../../02-DECISIONS/0027-a-provision-names-what-the-consumer-is-coupled-to.md)). Co-location
resolves it. The runtime's port stays what its code fixes; assigning it is
[issue 192](../../04-ISSUES/192-the-meshs-tools-reach-a-person-only-by-a-registration-made-by-hand/00-report.md)'s
second question and not this package's.
**Proof.** The plan for a workstation carrying a consumer of `mcp-endpoint` shows it bound to the
runtime's port; the controller's tests and the catalogue's checks pass.
## WP2 — The agent module
*mesh-catalog. A day and a half.*
**What is written**, as design 36 says:
1. **The manifest.** The agent's package. A state directory. A facts file in it, rendered by the mesh:
the node's name and the console's address from `mcp-endpoint`. A settings file in it, merged from
the module's settings layers: the node's role and the extra tool servers. A tools bundle whose
words name the two files, the state directory and nothing else. The two directories it owns declared — the agent's managed
directory and `~/.claude` — and **no file resource under either.**
2. **The renderer**, run whenever the runtime collects the module's tools: from the two files, the
managed settings file (the tool servers under the entry `mesh`, the attribution trailers, the
key-helper for an API-key binding) and the managed instruction file, written under the agent's
managed directory through the account's escalation, only when their content changed.
3. **The keypair**, made once in the state directory; X25519 and an authenticated cipher from the
language's own library, so the bundle carries no dependency.
4. **The tools and the state** (*2026-10-04, ADR 0206*): `claude_code_status` (what is rendered, what
licence is held, when its token expires, fingerprints only); `claude_code_render` (render now);
`claude_code_grant` (the full grant in the credentials file, sealed to the key the manager gives).
The `holdings` state, written at start and on every change of the credentials file; a watch of the
manager's `bindings` key for this node, which asks the seat's `current` on a newer generation and
applies the sealed answer — only if newer within one lineage unless it is a switch; the credentials
write as the operator, access-token-only, atomic; the key-helper program for an API key.
5. **The documentation**: the six predecessor files and the hand-made console entry a person removes.
**Proof, before anything runs live.** Unit tests: the renderer writes the mesh's keys and nothing else;
it writes nothing when nothing changed; the credentials write strips a refresh token and is atomic; the
lineage cases from the predecessor; a sealed hand-over opens only with the module's key; no tool's answer
contains a token. The catalogue's checks pass.
**Proof, live, on one workstation, configuration only.** Assign the module; set the node's role; push.
The agent's managed directory holds the two files; everything under the person's agent directory is
byte-identical to before; a new session lists the console's five tools under `mesh` and answers *which node am
I* from the managed instruction file. `claude_code_status` answers through the console. No licence is
touched: the module writes the credentials file only when it is handed a token.
## WP3 — The manager's code, built and tested
*mesh-catalog. Two to three days.*
**What is written**, as design 39 says: the manifest (the seat and its verbs, a database, a `secret`
for the key the grants are encrypted with, a tools bundle and a long-running bundle for the daemon, both launched by the runtime, settings
with defaults); the store's migrations; the refresh with its plan, lease, floor and cadence as pure
functions; the vendor client from `anthropic-manager`; adoption from a file and from a node's waiting
login with the identity guard; usage and its threshold; the seat's verbs. *2026-10-04 (ADR 0206):* in place
of the visit, the watch of every node's `holdings`, adoption of a candidate by refreshing it (newest login
first, once per account), the `bindings` state with a generation per consumer, and `current`.
**Proof, before anything runs live.** Unit tests: two refresh runs started together rotate one grant
once; a mismatching identity is refused; a worker bound to a dead licence is refused and never lent
another; nothing the daemon emits carries a token; a hand-over sealed for one node opens with no other
node's key.
## WP4 — The manager live on the control node
*The live mesh. Half a day.* The daemon is a long-running bundle the control node's runtime launches
(ADR 0198); it calls each node's agent module by `mesh/ask`. If the runtime's half of ADR 0198 is not
yet live on the control node when this package starts, this package waits for it: no tool container, no
credential copied by hand.
**Order.** Assign the manager on the control node; push. It reads every node's `holdings` and adopts each
account the nodes are logged in to, by refreshing the newest login's grant (ADR 0206); each node with no
binding is bound to the account it reported. Adopt the API key from a file there. A second subscription
account enters by a login on a workstation carrying the agent module.
**Proof.** Through the console: `anthropic-licence-manager.licences` lists three licences with identity
and expiry; within the cadence the audit shows a rotation and a later expiry; a forced `refresh` is
logged with the vendor's answer.
## WP5 — The licence end to end on one workstation
*The live mesh. Half a day. The proof of the whole.*
**Order.** Record the checksums under the person's agent directory. Bind the workstation to a
subscription licence. Remove the six predecessor files and the hand-made console entry. Start a session.
**Proof.** Everything under the person's agent directory is byte-identical but the credentials file,
which is owned by the operator, readable by nobody else, and names no refresh token. A session makes a
model request. `switch` to the second subscription licence changes the token on the workstation within a
minute, and neither the verb's answer nor either module's log holds a token. Switched to the API key, the
credentials file is left as it was and the agent authenticates through the key-helper. Switched back.
## WP6 — The rest of the nodes, and the predecessor's remains
*The live mesh and mesh-catalog. One day.* The module on every node, the predecessor's files removed on
the second workstation; a login under a licence's account collected and adopted, and one under the wrong
account refused and notified; `anthropic-manager` and `anthropic-consumer` retired from the catalogue;
designs 36 and 39 set to `implemented` with the as-is written
([playbook 02](../../00-META/process/02-graduation.md)).
**Proof.** `claude_code_status` answers on every node; the refusal's notification arrived; the catalogue
has no module built on the old placement.
## What is deliberately not here
- **The package repository seat** for a distribution that does not carry the agent's package (design 36
§7). The four machines have the package; a fifth would refuse the module in its package manager's
words.
- **Escalation as a checked fact.** The agent module's write under `/etc` relies on the operator
account's passwordless `sudo`, true on all four and checked by nothing. A machine without it refuses
the render in the tool's own words; making escalation a reported capability is design 38's to decide.
- **An automated switch on exhaustion.** The readings are kept from WP4; the policy is a later record.
- **Workers and the mesh's own sessions as consumers.** The manager knows them from WP3; the consumers do
not exist yet ([to-be 15](15-the-agent-session.md)).
- **Whether a refresh token is single-use.** WP4 may measure it; the design holds either way.
## How this list is kept true
Each package's proof is run when the package is finished and its line here gains the date and the
commit, as design 38 does. A package whose proof fails is not reworded; the failure is recorded under it
and the package stays open. When WP2's live proof runs, design 36 moves to `in-progress` with its owning
repository; when WP5's does, design 39 does too; and when WP6's does, both move to `implemented`, with
the as-is written.