ADR 0213: the operator sets the agent's managed settings through the agent module

Rules for what the agent may do were set by hand per machine, invisible to
the mesh, and a session cannot loosen its own permissions; design 36 now
takes them as a module setting under the mesh's own keys.
This commit is contained in:
jochen
2026-10-04 17:32:18 +02:00
parent e5042e1e7a
commit 57b818b669
3 changed files with 88 additions and 5 deletions
@@ -0,0 +1,71 @@
---
topic: what runs on it
status: accepted
date: 2026-10-04
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md
---
# 213. The operator sets the agent's managed settings through the agent module, under the mesh's own keys
## Context
The agent module writes the agent's machine-wide managed settings file
([to-be 36](../03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md) §2). It carries the mesh's
own keys only: the attribution convention of its repositories, the connectors kept beside the managed
tool servers, and the key-helper for an API-key licence. Every other key was left to the person's own
settings, so that the mesh never reverts a person's choice on a push.
That left no place for a rule the **operator** wants to hold in every session on a machine: what the
agent may do without asking, what it must never do, and what its unattended mode allows. These keys
are not preferences. They are policy about what an agent may do on the mesh. Set by hand in one
person's settings on each machine, they are unmanaged state the mesh cannot see, and the agent refuses
to change them itself, as it should.
## Considered Options
1. **Leave them to each person's settings.** Rejected: policy by hand on each machine, invisible to
the mesh, and a session cannot be asked to loosen its own permissions.
2. **A field per vendor key** (permissions, auto mode, environment, hooks) in the module's settings.
Rejected: the vendor adds keys, and every one would be a change to the module.
3. **One setting holding managed-settings keys, laid under the mesh's own.** The operator sets it
for the mesh or for one node through the controller's settings verb. The module copies its keys into
the managed settings file, then lays the mesh's keys over them.
## Decision
Option 3.
1. The agent module takes a setting, `managed_settings`: an object in the vendor's settings shape. It
is set for the whole mesh or for one node, like the module's other settings, through the
controller's settings verb.
2. The managed settings file is that object with **the mesh's keys laid last**: the attribution
convention, the connectors kept beside the managed servers, and the key-helper. A setting can
neither replace one of these nor add a key-helper that the binding did not ask for.
3. Only the operator sets it, and it is declared state like the role and the extra tool servers. A
person's preferences stay in their own settings; the mesh still sets none of them by itself.
## Consequences
- The operator's rules for the agent are declared once, for the mesh or per node, and reach every
node at the next push. A rule set in the managed settings outranks every other scope, so it holds
in every session on the node.
- A setting layer is replaced whole by the controller's verb. Setting this key without the role or
the extra tool servers clears those in that layer; the module's documentation says so.
- **What got harder:** a person cannot override a rule set here, which is the point. A rule that is
wrong is wrong on every session of the node until the operator changes the setting.
## How it is checked
| Rule | Checked by |
|---|---|
| The operator's keys reach the managed settings file | the agent module's test: an auto-mode allow list and a permissions list set in the setting appear in the rendered file |
| The mesh's keys always win | the same test: a setting naming the attribution, the connectors key or a key-helper is overridden, and a key-helper appears only for an API-key binding |
| Live | the setting given for the mesh; the managed settings file on each node carries the key after the next push |
## References
- [ADR 0183](0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md) — the agent module and the files it writes
- [to-be 36](../03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md) — the design this amends (§2, §6)
- the vendor's documentation on managed settings and their precedence
+1
View File
@@ -311,6 +311,7 @@ python3 00-META/checks/index.py fail if stale
- **0208** — [The graphical session is one module per piece, on the mesh's seats](0208-the-graphical-session-is-one-module-per-piece-on-the-meshs-seats.md)
- **0209** — [A login on a node moves that node to the account it logged in to; an API key is added from any node, sealed](0209-a-login-on-a-node-moves-that-node-to-its-account-and-an-api-key-is-added-from-any-node-sealed.md)
- **0211** — [A machine's power is a node seat, its moments take contributions, and its states are events](0211-a-machines-power-is-a-node-seat-its-moments-take-contributions-and-its-states-are-events.md)
- **0213** — [The operator sets the agent's managed settings through the agent module, under the mesh's own keys](0213-the-operator-sets-the-agents-managed-settings-through-the-agent-module.md)
### How it is built
@@ -1,9 +1,10 @@
---
layer: to-be
status: implemented
status: in-progress
code: [mesh-catalog modules/claude-code]
updated: 2026-10-04
decisions:
- 02-DECISIONS/0213-the-operator-sets-the-agents-managed-settings-through-the-agent-module.md
- 02-DECISIONS/0209-a-login-on-a-node-moves-that-node-to-its-account-and-an-api-key-is-added-from-any-node-sealed.md
- 02-DECISIONS/0206-a-node-reports-the-anthropic-grant-it-holds-and-the-licence-manager-adopts-a-licence-by-refreshing-it.md
- 02-DECISIONS/0181-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md
@@ -50,7 +51,7 @@ instruction file and the manager's tools:
|---|---|
| `~/.claude/CLAUDE.md` | the managed instruction file: how a session on this mesh works (§3) |
| `~/.claude/rules/00-hal-mesh.md`, `~/.claude/rules/conventions.md` | sections of the same file: this node's identity, the repositories' conventions |
| `~/.claude/settings.json`, merged | the managed settings file: the mesh's keys only, outranking nothing a person did not also set |
| `~/.claude/settings.json`, merged | the managed settings file: the mesh's keys, and the rules the operator set for the agent (§2) |
| `~/.claude/skills/hal-switch-license/SKILL.md` | the manager seat's `switch` verb, listed by the console, and a sentence in the instruction file saying to use it |
| `~/.claude/skills/cleanup/SKILL.md` | nothing; it named the predecessor's forge |
| the console's entry in the agent's user-scope state | the managed settings' tool-server key, from the console's provision (§4) |
@@ -68,7 +69,7 @@ lists them, and until they go the agent reads stale instructions beside the mesh
**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 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
role, the extra tool servers and the operator's managed-settings keys, 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
@@ -79,7 +80,7 @@ changes:
| path | content |
|---|---|
| the managed settings file | the mesh's keys: the tool servers (the console, plus any the operator declared as settings), the attribution trailers, and — for an API-key binding only — the key-helper that serves the key |
| the managed settings file | the keys the operator set in the module's `managed_settings` setting, with the mesh's keys laid over them: the tool servers (the console, plus any the operator declared as settings), the attribution trailers, and — for an API-key binding only — the key-helper that serves the key |
| the managed instruction file | §3 |
| the agent's credentials file under the operator's home | for a subscription binding only: the access token the manager handed over, as the operator, readable by the operator alone, atomic, no refresh token |
| the module's keypair in its state | made once, the private half never leaves (§5) |
@@ -94,6 +95,14 @@ requires. The model, the spinner, the drafts and every other preference are the
predecessor's experience with the model key is the evidence: a mesh that sets a preference reverts a
person's choice on every push.
**The operator's rules for the agent** ([ADR 0213](../../02-DECISIONS/0213-the-operator-sets-the-agents-managed-settings-through-the-agent-module.md)).
What the agent may do without asking, what it must never do and what its unattended mode allows are
not preferences: they are policy about an agent on the mesh, and a session must not loosen its own. The
operator sets them in the module's `managed_settings` setting, in the vendor's settings shape, for the
mesh or one node. The module copies those keys into the managed settings file and lays the mesh's keys
last, so a setting can never replace the attribution convention, the connectors key or the key-helper,
and a key-helper appears only for an API-key binding. The mesh still sets no preference by itself.
## 3. What the instruction file says
Prose, not a paste; the file is the module's.
@@ -177,7 +186,8 @@ a token is fetched by request when the state says it changed.
**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)).
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, and the operator's managed-settings keys (§2). The controller's verb replaces a
setting layer whole, so a layer set for one of these keeps the others it already held. **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
@@ -204,6 +214,7 @@ installer is rejected: it puts a self-updating binary under the person's home, i
| 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 module's test: keys set in `managed_settings` (an auto-mode allow list, a permissions list) appear in the rendered managed settings file, a setting naming the attribution, the connectors key or a key-helper is overridden, and a key-helper appears only for an API-key binding | ADR 0213 |
| 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 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 |