To-be 40 revised for the tools refactor; the manager starts every exchange (ADR 0183 dated note, designs 36 and 39)

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.
This commit is contained in:
jochen
2026-10-03 16:01:37 +02:00
parent df78f7bed0
commit 8a5dbaeb98
4 changed files with 182 additions and 179 deletions
@@ -152,6 +152,19 @@ 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 0192](0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.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: the agent module's code is now a
> tools bundle the node's runtime serves ([ADR 0175](0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md),
> [ADR 0188](0188-a-modules-own-code-is-bundles-in-any-language-and-a-tools-bundle-speaks-mcp-to-the-runtime.md)),
> and a bundle has no bus credential of its own and answers calls rather than making them (ADR 0192's
> consequences). So **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 manager's own process needs a
> bus credential to make those calls, which is the question design 38's WP4c leaves to a record.
## 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
@@ -2,7 +2,7 @@
layer: to-be layer: to-be
status: designed status: designed
code: [] code: []
updated: 2026-10-02 updated: 2026-10-03
decisions: decisions:
- 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
@@ -63,8 +63,9 @@ 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
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.
Nothing under the home, nothing under `/etc`. Nothing under the home, nothing under `/etc`.
**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
@@ -119,9 +120,9 @@ resolves it; a machine without the console refuses the module by name. [To-be 34
amended in the same change; issue 192 (open) found the gap. amended in the same change; issue 192 (open) found the gap.
**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 key. 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; a tool of this module
declared state. The agent's own HTTP-only constraint for managed servers applies; a person's local cannot set it, because a bundle calls nothing (ADR 0192). 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
@@ -133,29 +134,34 @@ it is the person's to remove, and until then the agent sees the mesh's tools twi
[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; 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 answers `claude_code_public_key` with the
public half when the manager asks;
- **serves `apply`**: the manager's hand-over, a token sealed to the module's key, with the licence's - **serves `apply`**: the manager's hand-over, a token sealed to the module's key, with the licence's
name and kind. A rotation of the same licence is applied only if newer within one lineage; a switch is name and kind. A rotation of the same licence is applied only if newer within one lineage; a switch is
applied regardless, because across licences the expiries are unrelated. The answer says applied or applied regardless, because across licences the expiries are unrelated. The answer says applied or
refused and why, and never echoes a token; refused and why, and never echoes a token;
- **pulls** at start and when its token nears expiry, by the seat's `current` verb, and keeps the last - **is reconciled, never pulls**: the manager asks every bound node on a schedule and after every
token when the manager does not answer, saying so; rotation, so a node that was away receives its token when it is back; between visits it keeps the last
token, and `licence_status` says how long it has left;
- **writes** for a subscription licence the credentials file as the operator, access-token-only; for the - **writes** for a subscription licence the credentials file as the operator, access-token-only; for the
API-key licence sets the key-helper in the managed settings to a small program that prints the key 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; from the module's state, so no file under the home is touched;
- **offers a login to the manager**: when the credentials file changes by a person's login, it reads the - **holds a login for the manager to collect**: when the credentials file holds a full grant it did not
account's identity from the agent's state file and offers the grant to the seat, sealed to the manager's write — a person logged in — it answers `claude_code_pending_login`, when the manager asks, with the
key, for adoption; the manager decides; grant sealed to the key the manager gives in its request and the account's identity read from the
agent's state file; the manager decides, and the next hand-over strips the refresh token;
- **serves `licence_status`**: which licence and kind this node holds, when the token expires, whether - **serves `licence_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 it is
handed. handed. *2026-10-03:* every exchange is started by the manager, because a tools bundle answers calls and
has no bus credential to make them ([ADR 0192](../../02-DECISIONS/0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.md),
ADR 0183's dated note). The tool names follow the catalogue's `<module>_<verb>` form.
## 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
@@ -2,7 +2,7 @@
layer: to-be layer: to-be
status: designed status: designed
code: [] code: []
updated: 2026-10-02 updated: 2026-10-03
decisions: decisions:
- 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
@@ -74,8 +74,9 @@ 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 **The manager starts every exchange** (ADR 0183's dated note of 2026-10-03): the agent module is a
runs. From then on: 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 - **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 licence, with the new token sealed to that node's module key. The module answers *applied*, or
@@ -83,11 +84,12 @@ runs. From then on:
- **On a switch**, the same call with the other licence's token, and the binding is the authority: the - **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 module applies a bind without comparing expiries, because across two licences the numbers are
unrelated. unrelated.
- **On a pull** — the module starting, or finding its token near expiry — the module calls the seat's - **On a schedule**, every few minutes, the manager visits each bound node: a node whose token is near
`current` verb for its binding and is answered sealed the same way. 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. - **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 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 rather than falling silent. A node whose module refuses — a wrong identity, a stale grant within one
lineage — is recorded as drift and reported. lineage — is recorded as drift and reported.
@@ -119,9 +121,9 @@ already keeps.
A licence enters the mesh one of two ways, and the token never passes through a prompt, a terminal or an A licence enters the mesh one of two ways, and the token never passes through a prompt, a terminal or an
argument: argument:
- **From a node's login.** A person logs in on a node, as they always have. The agent module there reads - **From a node's login.** A person logs in on a node, as they always have. On its next visit the manager
the account's identity from the agent's own state file, and offers the full grant to the seat sealed asks that node's module for a waiting login, giving its own public key; the module answers with the
to the manager's key. The manager adopts it into the licence the node is bound to **only if 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 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 first adoption; a mismatch is refused and notified, because the predecessor once filed one account's
grant into another's row this way. grant into another's row this way.
@@ -135,8 +137,8 @@ argument:
**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 `visit` (reconcile one node now). A node's key and
consumer's token, sealed, asked by the consumer's module). a consumer's token are not seat verbs: the manager asks the node, by the agent module's own tools.
## 8. Settings ## 8. Settings
@@ -2,12 +2,14 @@
layer: to-be layer: to-be
status: designed status: designed
code: [] code: []
updated: 2026-10-02 updated: 2026-10-03
decisions: decisions:
- 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/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/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.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/0149-the-live-mesh-is-the-test-bed.md
- 02-DECISIONS/0027-a-provision-names-what-the-consumer-is-coupled-to.md - 02-DECISIONS/0027-a-provision-names-what-the-consumer-is-coupled-to.md
--- ---
@@ -18,209 +20,189 @@ decisions:
broken into packages that each end at something a person can see run, in the order their 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 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. 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) gave the operator's machine, applied It is the shape [design 38](38-building-the-operators-machine.md) gives the operator's machine.
to the two modules that make its agent work.
*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, and a
bundle has no bus credential of its own. Three things in the first version of this plan changed with
that: the wait on design 38's WP3 is over; the agent module calls nothing, so the manager starts every
exchange (ADR 0183's dated note); and the manager's own process now waits on a different question,
named under WP4.
## How this is built, and where it is run ## 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), **On the live mesh, by the operator's decision** ([ADR 0149](../../02-DECISIONS/0149-the-live-mesh-is-the-test-bed.md)).
and design 38's words on the same day). Every package is written with unit tests, committed on one Every package is written with unit tests, committed on one branch per repository
branch per repository ([playbook 07](../../00-META/process/07-feature-branches.md)), and proven on the ([playbook 07](../../00-META/process/07-feature-branches.md)), and proven on the machines: one
machines: the control node first for the manager, one workstation first for the agent, then the rest. workstation first for the agent, the control node first for the manager, then the rest. A broken agent
The cost accepted: a broken agent module leaves a workstation's agent without the mesh's instructions module leaves a workstation's agent without the mesh's instructions or with a stale token until the next
or with a stale token until the next push; the person's own files under the home are never in reach of push; the person's own files under the home are out of the failure's reach, by
the failure, by [ADR 0182](../../02-DECISIONS/0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md). [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. Each package names what proves it. A package that cannot name its proof is divided until it can.
## What exists already, measured ## What exists already, measured
Counted 2026-10-02 in the repositories and on the machines. Nothing here is new ground; every package Measured 2026-10-03 on the four machines and in the repositories.
reshapes something standing.
| Piece | Today | Becomes | | Piece | Today | Becomes |
|---|---|---| |---|---|---|
| the vendor's refresh, the sealed box, the grant file | `anthropic-manager` in the catalogue: a token-endpoint client, a sealed-box primitive, adoption of a grant sealed to a node's key; assigned to nothing | the manager's refresh and adoption, with the lease, the floor and the cadence the predecessor's manager had | | 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 credentials write, the refresh-token strip, the identity read | `anthropic-consumer` in the catalogue: tested; assigned to nothing | the agent module's write, unchanged in shape | | the operator account | **stated on all four** — the runtime runs as it | read by the agent module from the runtime's own words |
| the predecessor's manager and consumer | two modules in the retired system: the lease per licence, the expiry floor, the lineage comparison, the identity guard, three touchpoints with fallbacks, cooldowns on alarms | ported as logic with its tests; nothing of its registry or its bus | | 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 host's `process` and `archive` shapes | fetch a bundle by digest and run it supervised; fetch and unpack an artifact | **unchanged** — the manager's daemon is one process; both modules' tools are bundles | | 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 |
| the manifest's `uses`, `claims`, `invokes seat:<seat>.<verb>`, node-scoped `provides` with `serves: {port}` | all four exist and are used by other modules | **unchanged** — the agent uses the seat and invokes its verbs; the console provides its endpoint | | 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 |
| the console | a container per node, MCP on loopback, no provision | gains one provision; becomes the node-tools runtime's serving mode under design 38's WP3 | | a bundle calling a tool | **not possible**: a bundle answers calls; it holds no bus credential | the manager starts every exchange |
| the agent's package | present on both workstations from a build the predecessor's helper made; the distribution's repositories do not carry it | declared; satisfied where present, refused where not, until a package repository seat exists | | a module's own long-running process with a bus credential | **undecided** — design 38's WP4c names it as the question its next record answers | the manager's daemon (WP4) |
| the operator account | a column on every node record, **empty on all four** | stated by the operator, before anything home-scoped lands | | 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) |
**One dependency decides the order.** Both modules serve tools and the agent module's tools write | 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 |
under `/etc` and, as the operator, under the home. Under [ADR 0175](../../02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md)
tools run in the node's tool runtime, host-side, which design 38 builds in its WP1–WP3. Writing a
per-module tool container for these two modules would be building the pattern that record retires, so
**the live proofs of WP3 to WP5 below wait for design 38's WP3.** Everything before a live proof —
manifests, code, tests — does not, and is written now.
## The order the work allows ## The order the work allows
``` ```
WP0 the operator states the facts (the live mesh) ── accounts, roles, licences to adopt WP0 the operator names the licences and each node's role (the live mesh) ── an hour
WP1 the console provides its endpoint (mesh-catalog) ── small, independent WP1 the runtime provides its endpoint (mesh-tools) ── small
WP2 the licence manager, built and tested (mesh-catalog) ──┐ independent of each other; WP2 the agent module (mesh-catalog) ──┐ WP2 needs WP1;
WP3 the agent module, built and tested (mesh-catalog) ──┘ both wait on design 38 WP3 to run WP3 the manager's code, built and tested (mesh-catalog) ──┘ WP3 is independent
│ │
WP4 the manager live on the control node (the live mesh) ── three licences adopted, a refresh seen WP2 live: one workstation, configuration only — no licence yet ── the first live proof
WP5 the agent live on one workstation (the live mesh) ── the hand-over, the switch, the instructions │
WP6 the rest of the nodes, and the predecessor's remains ── adoption from a login, the retirements WP4 the manager live on the control node ── waits on design 38 WP4c's record (a process's bus credential)
WP5 the licence end to end on one workstation
WP6 the rest of the nodes, and the predecessor's remains
``` ```
WP1, WP2 and WP3 touch different directories of one repository and meet only at the seat's name and ## WP0 — The operator names the licences and each node's role
the provision's name; they are built in parallel. WP4 is the first time anything on a machine changes.
WP5 is the proof of the whole.
## WP0 — The operator states the facts *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.
*The live mesh. An hour, and it is the operator's.* **Proof.** The module's settings list a role for every node; the licences have names.
The account on each node record, through the controller's node command — none is stated today, and ## WP1 — The runtime provides its endpoint
[ADR 0181](../../02-DECISIONS/0181-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md)
refuses a home-scoped module without one. The role of each node, as the agent module's setting on the
node layer, once the module exists. Which three licences exist and what each is called.
**Proof.** The controller's node command lists an account for every node. *mesh-tools. An hour.*
## WP1 — The console provides its endpoint **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.
*mesh-catalog. Half a day.* **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.
**What changes.** The console's manifest gains a node-scoped provision — working name ## WP2 — The agent module
`console-endpoint`, fixed when the manifest is written — serving the port the machine gave it, as the
local model server already does for its API. Co-location resolves it
([ADR 0027](../../02-DECISIONS/0027-a-provision-names-what-the-consumer-is-coupled-to.md),
[to-be 34](34-the-console.md) as amended). When design 38's WP3 moves the console into the node-tools
module, the provision moves with it; it is a line in a manifest either way.
**Proof.** The controller's plan for a workstation shows a consumer of the provision bound to the *mesh-catalog. A day and a half.*
console's port; the same consumer on a machine without the console is refused naming the provision;
the catalogue's tests pass.
## WP2 — The licence manager, built and tested
*mesh-catalog. Two to three days; the largest package.*
**What is written**, as design 39 says:
1. **The manifest.** Claims the mesh-scoped seat `anthropic-licence-manager` with its verbs; requires a
database, a `secret` for the key its grants are encrypted with, and the bus; a `bundle` of tools; a
`process` for the daemon that refreshes, collects usage and notifies, on a schedule; declared
settings for the cadence, the usage threshold and the cooldown, each with a default; `invokes` the
agent module's `apply`.
2. **The store.** Migrations for licences, bindings, usage and audit, with the lease and the
notification slot as columns, numbered and idempotent.
3. **The refresh.** The token-endpoint client and the sealed box from `anthropic-manager`; the plan
(floor, cadence, forced, cannot) and the lease from the predecessor, as pure functions with their
tests; the vendor's reason logged on failure; counted failures, one notification per cooldown.
4. **Adoption.** From a file on the manager's node for the API key; from a sealed grant a node offers;
the identity guard that refuses a mismatch and notifies.
5. **Usage.** The vendor's reading per licence on a schedule, stored raw and normalised
([ADR 0054](../../02-DECISIONS/0054-model-usage-is-recorded-at-two-grains.md)), one notification
per threshold crossing.
6. **The verbs**: `licences`, `bindings`, `bind`, `switch`, `release`, `refresh`, `usage`, `adopt`,
`register`, `current` — the last answering a consumer's token sealed to the key that consumer
registered.
7. **The hand-over**: on rotation or switch, one call to `claude-code.apply@<node>` per bound node,
the token sealed to that node's key, the answer recorded.
**Proof, before anything runs live.** Unit tests: two refresh runs started together rotate one grant
once; a grant with a mismatching identity is refused; a worker bound to a dead licence is refused and
never lent another; every event the daemon emits is free of a token; the hand-over payload opens only
with the registered key. The catalogue's checks: no installation named, no secret in a declared file.
## WP3 — The agent module, built and tested
*mesh-catalog. Two days.*
**What is written**, as design 36 says: **What is written**, as design 36 says:
1. **The manifest.** The agent's package; the state directory; the facts file carrying the node's 1. **The manifest.** The agent's package. A state directory. A facts file in it, rendered by the mesh:
name, the operator account and its home, the console's bound port, the role and the extra tool the node's name and the console's address from `mcp-endpoint`. A settings file in it, merged from
servers from settings; requires the console's endpoint and the bus; `uses` the seat and `invokes` the module's settings layers: the node's role and the extra tool servers. A tools bundle whose
its `register`, `current` and `adopt`; a `bundle` of tools. **No file resource under a home or words name the two files, the state directory and nothing else. **No file resource under a home or
under `/etc`.** under `/etc`.**
2. **The renderer.** From the facts file and the current binding, the managed settings file (the tool 2. **The renderer**, run whenever the runtime collects the module's tools: from the two files, the
servers under the entry `mesh`, the attribution trailers, and the key-helper for an API-key binding) managed settings file (the tool servers under the entry `mesh`, the attribution trailers, the
and the managed instruction file (§3 of design 36), written under the agent's managed directory key-helper for an API-key binding) and the managed instruction file, written under the agent's
with the escalation the tool performs for itself; idempotent; re-run when the facts file changes. managed directory through the account's escalation, only when their content changed.
3. **The keypair**, made once in the state directory, the public half registered with the seat at 3. **The keypair**, made once in the state directory; X25519 and an authenticated cipher from the
start and at every start. language's own library, so the bundle carries no dependency.
4. **The consumer side**: `apply` (a rotation applied only if newer within one lineage, a switch applied 4. **The tools**: `claude_code_status` (what is rendered, what licence is held, when its token expires,
regardless, the answer naming the outcome and never a token); the pull at start and near expiry; fingerprints only); `claude_code_render` (render now); `claude_code_public_key`;
the credentials write as the operator, access-token-only, from `anthropic-consumer` with its tests; `claude_code_apply` (a sealed token, applied only if newer within one lineage unless it is a switch;
the key-helper program for the API key; the offer of a login to the seat, sealed, after reading the the credentials write as the operator, access-token-only, atomic; the key-helper program for an API
account's identity. key); `claude_code_pending_login` (a full grant found in the credentials file, sealed to the key the
5. **The tools**: `apply`, `licence_status`, `mcp_configure` (validates a server, sets the module's caller gives, with the account's identity).
setting through the controller's settings verb), `render` (re-render now, for a person). 5. **The documentation**: the six predecessor files and the hand-made console entry a person removes.
6. **The documentation**: the six predecessor files and the hand-made console entry a person removes
on a workstation that carried the predecessor.
**Proof, before anything runs live.** Unit tests: the renderer writes only the mesh's keys and leaves **Proof, before anything runs live.** Unit tests: the renderer writes the mesh's keys and nothing else;
every other key of a seeded settings file; the credentials write strips a refresh token and is atomic; it writes nothing when nothing changed; the credentials write strips a refresh token and is atomic; the
the lineage comparison from the predecessor, with its cases; a login offer carries the identity it read. lineage cases from the predecessor; a sealed hand-over opens only with the module's key; no tool's answer
The catalogue's checks pass. 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 mesh's 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, a process bundle for the daemon, 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 visit — key, hand-over, waiting login — per
bound node; the seat's verbs.
**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 ## WP4 — The manager live on the control node
*The live mesh. Half a day, after design 38's WP3.* *The live mesh. Half a day.* **Waits on design 38 WP4c's record** — how a module's own long-running
process is given a bus credential and its subscriptions — because the daemon must call the agent module
on every node. Until that record exists, nothing in this plan works around it: no tool container, no
credential copied by hand.
**Order.** Assign the manager on the control node; push. Adopt the API key from a file there. Adopt **Order.** Assign the manager on the control node; push. Adopt the API key from a file there. Adopt the
the two subscription grants: a login in a throwaway home on the control node, offered to the seat the two subscription grants: a login on a workstation carrying the agent module, collected by the manager's
way a node's agent module will. Bind each node's agent to a licence. visit. Bind each node's agent to a licence.
**Proof.** Through the console: `anthropic-licence-manager.licences` lists three licences with **Proof.** Through the console: `anthropic-licence-manager.licences` lists three licences with identity
identity and expiry; within the cadence, the audit shows a rotation and `licences` shows a later and expiry; within the cadence the audit shows a rotation and a later expiry; a forced `refresh` is
expiry; a forced `refresh` on one licence is logged with the vendor's answer; the two retired catalogue logged with the vendor's answer.
modules are still assigned to nothing.
## WP5 — The agent live on one workstation ## WP5 — The licence end to end on one workstation
*The live mesh. Half a day. The proof of the whole.* *The live mesh. Half a day. The proof of the whole.*
**Order.** Set the workstation's role in the module's settings. Record the checksums of everything **Order.** Record the checksums under the person's agent directory. Bind the workstation to a
under the person's agent directory. Assign the module; push. Remove the six predecessor files and the subscription licence. Remove the six predecessor files and the hand-made console entry. Start a session.
hand-made console entry. Start a new session.
**Proof.** The managed directory holds the settings and instruction files, owned by root. Everything **Proof.** Everything under the person's agent directory is byte-identical but the credentials file,
under the person's agent directory is byte-identical to before except the credentials file, which is which is owned by the operator, readable by nobody else, and names no refresh token. A session makes a
owned by the operator, readable by nobody else, and names no refresh token. The new session lists the model request. `switch` to the second subscription licence changes the token on the workstation within a
mesh's tools under `mesh` once, answers *which node am I* from the instruction file, and makes a model minute, and neither the verb's answer nor either module's log holds a token. Switched to the API key, the
request. `anthropic-licence-manager.switch` to the second subscription licence changes the token on credentials file is left as it was and the agent authenticates through the key-helper. Switched back.
the workstation within a minute, and neither the verb's answer nor either module's log holds a token.
Switched to the API-key licence, 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 ## WP6 — The rest of the nodes, and the predecessor's remains
*The live mesh and mesh-catalog. One day.* *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
**Order.** Assign the module on the second workstation and on the servers whose account is stated; account refused and notified; `anthropic-manager` and `anthropic-consumer` retired from the catalogue;
remove the predecessor's files on the second workstation. Log in on a workstation under a licence's designs 36 and 39 set to `implemented` with the as-is written
account and watch the offer be adopted — and under the wrong account, and watch it refused and
notified. Retire `anthropic-manager` and `anthropic-consumer` from the catalogue. Set designs 36 and
39 to `implemented` for what runs, with the as-is written
([playbook 02](../../00-META/process/02-graduation.md)). ([playbook 02](../../00-META/process/02-graduation.md)).
**Proof.** Every node with an account runs the module and `licence_status` answers on each; the **Proof.** `claude_code_status` answers on every node; the refusal's notification arrived; the catalogue
refused login's notification arrived; the catalogue has no module built on the old placement. has no module built on the old placement.
## What is deliberately not here ## What is deliberately not here
- **The package repository seat** for a distribution that does not carry the agent's package - **The package repository seat** for a distribution that does not carry the agent's package (design 36
(design 36 §7). A fresh node refuses the module in the package manager's words until it exists. §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. - **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's bindings and fallbacks know them - **Workers and the mesh's own sessions as consumers.** The manager knows them from WP3; the consumers do
from WP2; the consumers themselves do not exist yet ([to-be 15](15-the-agent-session.md), not exist yet ([to-be 15](15-the-agent-session.md)).
[ADR 0003](../../02-DECISIONS/0003-agents-are-persistent-employees.md)). - **Whether a refresh token is single-use.** WP4 may measure it; the design holds either way.
- **Whether a refresh token is single-use.** WP4 may measure it on a licence deliberately refreshed
twice; the design holds either way.
## How this list is kept true ## 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 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 commit, as design 38 does. A package whose proof fails is not reworded; the failure is recorded under it
it and the package stays open. When WP5 is proven, designs 36 and 39 move to `in-progress` with their and the package stays open. When WP2's live proof runs, design 36 moves to `in-progress` with its owning
owning repository, and when WP6 is proven to `implemented`, with the as-is written. repository; when WP5's does, design 39 does too; and when WP6's does, both move to `implemented`, with
the as-is written.