Compare commits

...
Author SHA1 Message Date
jochen e3755d5b60 There is no home-scoped module: ADR 0181 and 0182 say so as progressive insights; design 36 and to-be 40: the module declares the two directories it owns
ADR 0173 §2: a module is what it declares, and there are no kinds of module. The two records called
a resource under a home and a module placing one home-scoped; the wording is corrected in place,
marked and dated, the decisions unchanged. Design 36 and to-be 40 now say the module declares
/etc/claude-code and ~/.claude as directories, so the ownership check sees both, and declares no file
under either (mesh-catalog #244).
2026-10-03 23:50:06 +02:00
jochen baf82b1cdb Plan and designs after ADR 0193, 0195 and 0198: bundles are launched and the runtime is their bus; the manager's daemon is a long-running bundle; the console's five tools
The dated note on ADR 0183 now rests on ADR 0193 and 0198 rather than on a bundle having no way to
call: the manager starts every exchange by the operator's direction, through mesh/ask. To-be 40's
WP4 no longer waits on a record — ADR 0198 is it — and the live proofs count the console's five
tools (ADR 0195).
2026-10-03 23:41:38 +02:00
jochen 88fd7d9739 Design 36 §4: the console is registered in the exclusive managed tool-server file, because the managed-settings key refuses a non-https URL 2026-10-03 23:41:10 +02:00
jochen ce0dc7b55b 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.
2026-10-03 23:41:10 +02:00
jochen 7d18b0c1d0 To-be 40: building the operator's agent and its licence manager as work packages
Designs 36 and 39 say what is built; this says in which order and what proves each step, in the
shape to-be 38 gave the operator's machine. Seven packages: the operator states the facts (accounts,
roles, licences); the console provides its endpoint; the licence manager and the agent module are
built and unit-tested in parallel; the manager goes live on the control node; the agent on one
workstation, with the switch and the predecessor's files removed as the proof of the whole; then the
rest of the nodes and the retirement of the two catalogue modules built on the old placement. The
live proofs wait for to-be 38's WP3, because both modules' tools run in the node's tool runtime
(ADR 0175) and a per-module tool container would rebuild what that record retires.
2026-10-03 23:41:10 +02:00
mesh-admin 895c2afad1 Merge pull request 'Issue 218: a mesh seat answered by a non-holder (located, fixed in mesh-controller#248)' (#339) from issues/218-a-mesh-seat-answered-by-a-non-holder into main 2026-10-03 21:32:24 +00:00
mesh-admin d362155401 Merge pull request 'Issue 218: a mesh seat is answered by a module on a machine that does not hold it' (#338) from issues/218-a-mesh-seat-answered-by-a-non-holder into main 2026-10-03 21:25:27 +00:00
6 changed files with 296 additions and 43 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
> **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
[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
@@ -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
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
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
@@ -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
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
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
@@ -80,7 +86,7 @@ anything.
## 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.
- 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.
@@ -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
> **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
[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
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
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.
## Considered Options
@@ -52,7 +58,7 @@ section.
## 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
([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
@@ -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 |
| 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 |
| 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
@@ -152,6 +152,20 @@ 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 |
| 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.
## 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
@@ -2,7 +2,7 @@
layer: to-be
status: designed
code: []
updated: 2026-10-02
updated: 2026-10-03
decisions:
- 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
@@ -54,8 +54,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 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
module's own code writes for a subscription licence (§5). The person's memory, history, projects, local
the module owns the directory `~/.claude` — that it exists, that the operator owns it, its mode,
`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
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.
@@ -63,9 +65,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
**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
settings; the bus, the console's provision, and that it uses the `anthropic-licence-manager` seat.
Nothing under the home, nothing under `/etc`.
in that directory carrying the node's name and the console's endpoint, and a settings file carrying the
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.
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
changes:
@@ -111,17 +116,20 @@ the playbooks in the record.
## 4. The console
The module tells the agent where the console is, and the port is the console's to say. **The console
provides a node-scoped provision** — its MCP endpoint on loopback — serving the port the machine gave
it, and the module requires it. A requirement names what the consumer is coupled to
([ADR 0027](../../02-DECISIONS/0027-a-provision-names-what-the-consumer-is-coupled-to.md)); co-location
resolves it; a machine without the console refuses the module by name. [To-be 34](34-the-console.md) is
amended in the same change; issue 192 (open) found the gap.
> **Revised 2026-10-03, building it.** The vendor's managed-settings key for tool servers refuses any
> URL that is not `https://`, including one on loopback, so it cannot carry the console. The module
> owns the vendor's **exclusive** managed tool-server file instead (operator's choice): the console as
> `mesh`, over HTTP on loopback, and every server in the module's `mcp_servers` setting — and no other.
> A server added by hand, a project's own file and a plugin's servers stop loading; claude.ai's
> 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
— mesh layer or node layer — rendered into the same managed key. A module tool, `mcp_configure`,
validates a server and sets the setting through the controller's settings verb, so the list stays
declared state. The agent's own HTTP-only constraint for managed servers applies; a person's local
— mesh layer or node layer — rendered into the same managed file. The person sets them with the
controller's `settings` verb on this module, so the list stays declared state; the list is the operator's
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.
**The entry's name is `mesh`.** The hand-made entry both workstations carry today is named after this
@@ -133,34 +141,39 @@ 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)
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
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
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
token when the manager does not answer, saying so;
- **is reconciled, never pulls**: the manager asks every bound node on a schedule and after every
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
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;
- **offers a login to the manager**: when the credentials file changes by a person's login, it reads the
account's identity from the agent's state file and offers the grant to the seat, sealed to the manager's
key, for adoption; the manager decides;
- **holds a login for the manager to collect**: when the credentials file holds a full grant it did not
write — a person logged in — it answers `claude_code_pending_login`, when the manager asks, with the
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
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
handed.
handed. *2026-10-03:* every exchange is started by the manager, by the operator's direction (ADR 0183's dated
note); this module is a bundle the node's runtime launches over stdio and answers what it is asked
([ADR 0193](../../02-DECISIONS/0193-every-bundle-the-runtime-serves-is-launched-and-the-runtime-knows-no-language.md)). The tool names follow the catalogue's `<module>_<verb>` form.
## 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)).
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.
**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
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.
## 7. The package
@@ -178,13 +191,13 @@ installer is rejected: it puts a self-updating binary under the person's home, i
| 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 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 |
| 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 |
| 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
@@ -2,7 +2,7 @@
layer: to-be
status: designed
code: []
updated: 2026-10-02
updated: 2026-10-03
decisions:
- 02-DECISIONS/0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md
- 02-DECISIONS/0024-model-access-is-a-provision.md
@@ -74,8 +74,9 @@ Carried from the predecessor, where each rule was earned by an incident:
## 4. Handing a token to a node
Every node that runs the agent module registers that module's public key with the seat when it first
runs. From then on:
**The manager starts every exchange** (ADR 0183's dated note of 2026-10-03), by `mesh/ask` through the
runtime that launched it ([ADR 0198](../../02-DECISIONS/0198-a-modules-long-running-code-is-launched-by-the-node-runtime-and-reaches-the-bus-through-it.md)). The manager asks each bound node's module for its public
key the first time and keeps it. From then on:
- **On rotation**, the manager calls `claude-code.apply@<node>` on every node bound to the rotated
licence, with the new token sealed to that node's module key. The module answers *applied*, or
@@ -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
module applies a bind without comparing expiries, because across two licences the numbers are
unrelated.
- **On a pull** — the module starting, or finding its token near expiry — the module calls the seat's
`current` verb for its binding and is answered sealed the same way.
- **On a schedule**, every few minutes, the manager visits each bound node: a node whose token is near
expiry, or that did not answer last time, is handed its current token. A node that was away is
served when it is back, with nothing for it to ask.
- **Never as an event.** What the manager emits names the licence and the outcome and carries no token.
A node whose module 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
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
argument:
- **From a node's login.** A person logs in on a node, as they always have. The agent module there reads
the account's identity from the agent's own state file, and offers the full grant to the seat sealed
to the manager's key. The manager adopts it into the licence the node is bound to **only if the
- **From a node's login.** A person logs in on a node, as they always have. On its next visit the manager
asks that node's module for a waiting login, giving its own public key; the module answers with the
full grant sealed to it and the account's identity read from the agent's own state file. The manager adopts it into the licence the node is bound to **only if the
identity matches** that licence's recorded account; a licence not yet identified is identified by its
first adoption; a mismatch is refused and notified, because the predecessor once filed one account's
grant into another's row this way.
@@ -135,8 +137,8 @@ argument:
**The seat's verbs**, the contract every future holder must serve: `licences` (each with kind,
identity, expiry, failures, who is bound), `bindings`, `bind`, `switch`, `release`, `refresh` (now, one
or all), `usage` (current and history), `adopt`, `register` (a node's module key), `current` (a
consumer's token, sealed, asked by the consumer's module).
or all), `usage` (current and history), `adopt`, and `visit` (reconcile one node now). A node's key and
a consumer's token are not seat verbs: the manager asks the node, by the agent module's own tools.
## 8. Settings
@@ -0,0 +1,212 @@
---
layer: to-be
status: designed
code: []
updated: 2026-10-03
decisions:
- 02-DECISIONS/0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md
- 02-DECISIONS/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**: `claude_code_status` (what is rendered, what licence is held, when its token expires,
fingerprints only); `claude_code_render` (render now); `claude_code_public_key`;
`claude_code_apply` (a sealed token, applied 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); `claude_code_pending_login` (a full grant found in the credentials file, sealed to the key the
caller gives, with the account's identity).
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 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
*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. Adopt the API key from a file there. Adopt the
two subscription grants: a login on a workstation carrying the agent module, collected by the manager's
visit. Bind each node's agent to a licence.
**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.