diff --git a/03-DESIGN/01-to-be/40-building-the-operators-agent-and-its-licence-manager.md b/03-DESIGN/01-to-be/40-building-the-operators-agent-and-its-licence-manager.md new file mode 100644 index 0000000..6a8bc6f --- /dev/null +++ b/03-DESIGN/01-to-be/40-building-the-operators-agent-and-its-licence-manager.md @@ -0,0 +1,226 @@ +--- +layer: to-be +status: designed +code: [] +updated: 2026-10-02 +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/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) gave the operator's machine, applied +to the two modules that make its agent work. + +## 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), +and design 38's words on the same day). 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: the control node first for the manager, one workstation first for the agent, then the rest. +The cost accepted: 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 never in reach of +the failure, 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 + +Counted 2026-10-02 in the repositories and on the machines. Nothing here is new ground; every package +reshapes something standing. + +| 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 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 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 | +| 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 manifest's `uses`, `claims`, `invokes seat:.`, 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 | +| 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 | +| 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 | +| the operator account | a column on every node record, **empty on all four** | stated by the operator, before anything home-scoped lands | + +**One dependency decides the order.** Both modules serve tools and the agent module's tools write +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 + +``` +WP0 the operator states the facts (the live mesh) ── accounts, roles, licences to adopt +WP1 the console provides its endpoint (mesh-catalog) ── small, independent +WP2 the licence manager, built and tested (mesh-catalog) ──┐ independent of each other; +WP3 the agent module, built and tested (mesh-catalog) ──┘ both wait on design 38 WP3 to run + │ +WP4 the manager live on the control node (the live mesh) ── three licences adopted, a refresh seen +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 +``` + +WP1, WP2 and WP3 touch different directories of one repository and meet only at the seat's name and +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 account on each node record, through the controller's node command — none is stated today, and +[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. + +## WP1 — The console provides its endpoint + +*mesh-catalog. Half a day.* + +**What changes.** The console's manifest gains a node-scoped provision — working name +`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 +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@` 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: + +1. **The manifest.** The agent's package; the state directory; the facts file carrying the node's + name, the operator account and its home, the console's bound port, the role and the extra tool + servers from settings; requires the console's endpoint and the bus; `uses` the seat and `invokes` + its `register`, `current` and `adopt`; a `bundle` of tools. **No file resource under a home or + under `/etc`.** +2. **The renderer.** From the facts file and the current binding, the managed settings file (the tool + servers under the entry `mesh`, the attribution trailers, and the key-helper for an API-key binding) + and the managed instruction file (§3 of design 36), written under the agent's managed directory + with the escalation the tool performs for itself; idempotent; re-run when the facts file changes. +3. **The keypair**, made once in the state directory, the public half registered with the seat at + start and at every start. +4. **The consumer side**: `apply` (a rotation applied only if newer within one lineage, a switch applied + regardless, the answer naming the outcome and never a token); the pull at start and near expiry; + the credentials write as the operator, access-token-only, from `anthropic-consumer` with its tests; + the key-helper program for the API key; the offer of a login to the seat, sealed, after reading the + account's identity. +5. **The tools**: `apply`, `licence_status`, `mcp_configure` (validates a server, sets the module's + setting through the controller's settings verb), `render` (re-render now, for a person). +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 +every other key of a seeded settings file; the credentials write strips a refresh token and is atomic; +the lineage comparison from the predecessor, with its cases; a login offer carries the identity it read. +The catalogue's checks pass. + +## WP4 — The manager live on the control node + +*The live mesh. Half a day, after design 38's WP3.* + +**Order.** Assign the manager on the control node; push. Adopt the API key from a file there. Adopt +the two subscription grants: a login in a throwaway home on the control node, offered to the seat the +way a node's agent module will. 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 `licences` shows a later +expiry; a forced `refresh` on one licence is logged with the vendor's answer; the two retired catalogue +modules are still assigned to nothing. + +## WP5 — The agent live on one workstation + +*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 +under the person's agent directory. Assign the module; push. Remove the six predecessor files and the +hand-made console entry. Start a new session. + +**Proof.** The managed directory holds the settings and instruction files, owned by root. Everything +under the person's agent directory is byte-identical to before except the credentials file, which is +owned by the operator, readable by nobody else, and names no refresh token. The new session lists the +mesh's tools under `mesh` once, answers *which node am I* from the instruction file, and makes a model +request. `anthropic-licence-manager.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 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 + +*The live mesh and mesh-catalog. One day.* + +**Order.** Assign the module on the second workstation and on the servers whose account is stated; +remove the predecessor's files on the second workstation. Log in on a workstation under a licence's +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)). + +**Proof.** Every node with an account runs the module and `licence_status` answers on each; the +refused login'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). A fresh node refuses the module in the package manager's words until it exists. +- **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 + from WP2; the consumers themselves do 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 on a licence deliberately refreshed + twice; 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 WP5 is proven, designs 36 and 39 move to `in-progress` with their +owning repository, and when WP6 is proven to `implemented`, with the as-is written.