Merge pull request 'ADR 0273: the agent's home is the module's, and the operator's personal rules and lessons are registered through it' (#254) from decision/0273-the-agents-home-configuration-is-the-modules into main
This commit was merged in pull request #254.
This commit is contained in:
@@ -495,6 +495,15 @@ asking, never the act. **Decided by** ADR 0152, 0175, 0234, 0259, 0272.
|
||||
of them (a path, an address); opened with `detail` only on a `private` channel or through the mesh MCP server.
|
||||
- **agent** — a coding agent: the operator's, or one the mesh runs. Not the node-engine, and not the
|
||||
build seat's holder, whose seat name `node-build-agent` is an identifier to rename.
|
||||
- **personal rule** — one short text in the operator's own words about how the operator wants their agent
|
||||
to work with them, registered for the operator (the **operator scope** of the agent module) and rendered
|
||||
into the agent's home on every node that names an operator account. Not a mesh-wide instruction section,
|
||||
which is the mesh's word about the mesh, set at the controller's terminal
|
||||
([ADR 0273](../02-DECISIONS/0273-the-agents-home-is-the-modules-and-the-operators-personal-rules-and-lessons-are-registered-through-it.md)).
|
||||
- **lesson** — an agent's proposal, recorded when the operator expresses frustration or corrects a way of
|
||||
working: the trigger (the operator's words), the cause (what the agent did) and the rule that would
|
||||
prevent it. A lesson is `proposed`, `accepted` or `declined`; an accepted lesson is a personal rule, and a
|
||||
proposed one is never read as one (ADR 0273).
|
||||
|
||||
## The record — how this repository works
|
||||
|
||||
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
---
|
||||
status: graduated
|
||||
initiated: 2026-10-09
|
||||
touches:
|
||||
- 03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md
|
||||
- 03-DESIGN/01-to-be/40-building-the-operators-agent-and-its-licence-manager.md
|
||||
- 03-DESIGN/00-as-is/15-the-agent-and-its-licences.md
|
||||
- 02-DECISIONS/0216-the-agents-configuration-is-registered-through-its-module-at-three-scopes-and-served-as-one-plugin.md
|
||||
- 02-DECISIONS/0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md
|
||||
- 02-DECISIONS/0243-the-agent-module-removes-a-home-item-it-did-not-place-only-on-the-persons-word-and-keeps-a-copy.md
|
||||
- 02-DECISIONS/0245-a-verb-says-what-it-replaces-and-the-agent-is-guarded-from-working-round-the-mesh.md
|
||||
- 02-DECISIONS/0259-an-ask-is-a-message-on-the-bus-any-channel-answers-it-and-the-asker-acts-on-a-warrant.md
|
||||
- 04-ISSUES/340-an-agent-can-plant-a-hook-in-every-agent-session-on-the-mesh/00-report.md
|
||||
became:
|
||||
- 02-DECISIONS/0273-the-agents-home-is-the-modules-and-the-operators-personal-rules-and-lessons-are-registered-through-it.md
|
||||
- 03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md
|
||||
---
|
||||
|
||||
# 040 — The agent's home is the module's, and the operator's rules and lessons are registered through it
|
||||
|
||||
## What
|
||||
|
||||
Three things about the coding agent's own directory under a person's home (`~/.claude`), investigated
|
||||
together because the operator raised them as one on 2026-10-09:
|
||||
|
||||
1. **Who owns the configuration files in that directory.** [ADR 0216](../../02-DECISIONS/0216-the-agents-configuration-is-registered-through-its-module-at-three-scopes-and-served-as-one-plugin.md)
|
||||
gave the agent module a home scope that "owns only what it placed"; everything else there is held as
|
||||
found ([ADR 0182](../../02-DECISIONS/0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md)).
|
||||
The operator's direction reverses that for the configuration: *"the home-scope does own ~/.claude, so
|
||||
it should be completely empty and manual edits should be forbidden."*
|
||||
2. **Whose the rules in that directory are.** The rule files a person writes there today are the
|
||||
person's own, on one machine. The operator's direction: *"Rules in my home directory are my 'personal
|
||||
rules' which should also end up under control of the claude-code module and all other changes to
|
||||
~/.claude should go through the claude-code module's mcp tools."*
|
||||
3. **How an agent learns from a correction.** The operator's direction: *"register the decision, but make
|
||||
sure it's clear and that you can/should be pro-active about it. For example, if I'm frustrated, try to
|
||||
see why and try to prevent it in the future by warning yourself about stuff not to do to frustrate me
|
||||
— kind of like a self-learning process."*
|
||||
|
||||
## Why
|
||||
|
||||
The home is the one place the agent's configuration still drifts. [01](01-what-the-home-holds-and-the-options.md)
|
||||
measured it on 2026-10-09 across the four nodes: a skill to-be 36 retired in its first version is still on
|
||||
all four; a person's instruction file is on three; the operator's two hand-written rules are on one; and
|
||||
nothing in the mesh places, watches or raises anything about any of it. The corrections the operator has
|
||||
given the agent live in the agent's own memory for one repository on one machine, 45 notes of which at least
|
||||
9 are a correction of a way of working, shown to nobody and reaching no other node.
|
||||
|
||||
[Issue 340](../../04-ISSUES/340-an-agent-can-plant-a-hook-in-every-agent-session-on-the-mesh/00-report.md)
|
||||
closed the tools' route into the operator's home for the right reason: a tool cannot tell the operator from
|
||||
an agent, so an agent could plant something every session obeys. Whatever owns the home must keep that
|
||||
protection. The two routes the mesh now has for the operator's own word, the controller's terminal
|
||||
([ADR 0272](../../02-DECISIONS/0272-the-operator-reaches-the-controller-with-mesh-cli-on-every-node-and-a-node-is-the-terminal-only-where-no-agent-runs-as-its-operator.md))
|
||||
and a warrant on an ask ([ADR 0259](../../02-DECISIONS/0259-an-ask-is-a-message-on-the-bus-any-channel-answers-it-and-the-asker-acts-on-a-warrant.md)),
|
||||
make it possible to write into the operator's home without trusting a tool's caller.
|
||||
|
||||
The record was searched first: `records_search` for "personal rules lessons frustration self-learning" and
|
||||
for "hand edit home .claude drift condition" found nothing on 2026-10-09.
|
||||
|
||||
## What it touches
|
||||
|
||||
- **The agent module's design** ([to-be 36](../../03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md)
|
||||
§1 and §8): what under the home is the module's, and the scopes.
|
||||
- **ADR 0216 §5 and §6** (the home scope owns only what it placed; what it did not place is reported) and
|
||||
**ADR 0243** (removal on the person's word): the mechanism changes, the decisions stand.
|
||||
- **ADR 0182**: the class table holds; what changes is which class the configuration files are in.
|
||||
- **ADR 0245**: the guard gains a refusal, and the session gains a reminder.
|
||||
- **ADR 0259**: the module becomes an asker, for the operator's yes.
|
||||
- **Issue 340**: the protection stands; the terminal and the warrant are the two ways in.
|
||||
|
||||
## Documents
|
||||
|
||||
- [01 — What the home holds, and the options](01-what-the-home-holds-and-the-options.md): the measurement
|
||||
on four nodes, the agent's own memory as it stands, and the options weighed.
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
# 040 — What the home holds, and the options
|
||||
|
||||
## 1. The agent's directory on the laptop, 2026-10-09
|
||||
|
||||
Read by name on the laptop's own disk, contents not read. The directory holds 33 entries.
|
||||
|
||||
**Configuration, written by a person or by the program on a person's choice:**
|
||||
|
||||
| path | what | how it got there |
|
||||
|---|---|---|
|
||||
| `rules/` | 2 rule files (1779 and 755 bytes): how text meant to be pasted elsewhere is handed over; how git is used in a second forge's repositories | written by hand |
|
||||
| `skills/` | 5 skills of the operator's own, for the operator's other work; and `skills/synced/`, 2 folders the vendor's own sync placed | by hand; by the program |
|
||||
| `commands/` | empty | — |
|
||||
| `settings.json` | 6 keys: the attribution, auto mode, feedback drafts, the model, the spinner tips, the model switch on a flag | by hand, and by the program on a `/model` or `/config` choice |
|
||||
| `settings.local.json` | 1 key, `permissions`: the rules the program writes when a person answers "always allow" in a session | by the program, on a person's answer |
|
||||
| `plugins/` | the program's record of its marketplaces (the vendor's and the mesh's `nox-mesh`) and installed plugins, and its cache | by the program |
|
||||
| no `CLAUDE.md`, no `agents/`, no `hooks/` | the instruction file was removed on 2026-10-07 through the module's removal tool, with a copy kept (ADR 0243) | — |
|
||||
|
||||
**Working state, written by the program for itself** (24 entries): the credentials file (the module's own
|
||||
code writes it, ADR 0183), `history.jsonl` (5 MB), `projects/` (30 directories, 1.5 GB, holding every
|
||||
project's sessions and the agent's own memory), `sessions/`, `session-env/`, `shell-snapshots/`,
|
||||
`file-history/`, `debug/`, `cache/`, `paste-cache/`, `telemetry/`, `stats-cache.json`, the daemon's lock,
|
||||
log and status files, `state/`, `ide/`, `jobs/`, `feedback/`, `backups/`, `worktrees/`, and a few markers.
|
||||
|
||||
**The managed directory under `/etc`**, by contrast, holds exactly what the module renders: the instruction
|
||||
file, the managed settings, the managed tool-server file, and the marketplace with the `nox-mesh` plugin.
|
||||
|
||||
## 2. The homes on four nodes, 2026-10-09
|
||||
|
||||
Read through each node's `claude_code_config_status` (the module's own status tool), the field listing the
|
||||
home's own items beside what the mesh placed:
|
||||
|
||||
| node | home items the mesh did not place | placed by the mesh at home |
|
||||
|---|---|---|
|
||||
| the laptop | 2 rule files, 5 skills | none |
|
||||
| the workstation | an instruction file, a `cleanup` skill, a subagent (a module reviewer) | none |
|
||||
| the home server | an instruction file, a `cleanup` skill | none |
|
||||
| the control-node | an instruction file, a `cleanup` skill | none |
|
||||
|
||||
Three things follow:
|
||||
|
||||
- **The retired skill is still everywhere.** To-be 36 §1 said on 2026-10-02 that the `cleanup` skill
|
||||
becomes "nothing; it named the predecessor's forge". It is on all four nodes on 2026-10-09. Nothing
|
||||
owns it, so nothing removed it.
|
||||
- **The operator's rules exist on one node.** A rule about how the operator wants text handed over is as
|
||||
true on the workstation as on the laptop. It is on the laptop alone, because the only way to put it
|
||||
anywhere is to write the file there.
|
||||
- **Nothing is raised about any of it.** The status tool lists these items when asked. No condition says
|
||||
the laptop's rules are missing elsewhere, or that a skill the design retired is still loaded in every
|
||||
session on four machines. The three mesh-wide instruction sections and the `nox-mesh` plugin (holding
|
||||
the guard alone) are the only things the mesh renders for a session, and both are in the managed
|
||||
directory, not the home.
|
||||
|
||||
## 3. The agent's own memory, 2026-10-09
|
||||
|
||||
The agent keeps notes for itself in the program's memory directory under `projects/` for one repository
|
||||
on the laptop: 45 notes on 2026-10-09. At least 9 of them are a correction of a way of working that the
|
||||
operator gave in a session, for example: finish a module before pushing it; use the mesh's tools, not
|
||||
ssh; read the merge gate's verdict before merging; what is done is what the operator sees working;
|
||||
no work-arounds when a tool is missing; a language choice the operator insists on. Each was learned once,
|
||||
after the operator said it, sometimes with irritation.
|
||||
|
||||
Those notes:
|
||||
|
||||
- are on one machine, for one repository: a session on the workstation, or in another repository on the
|
||||
laptop, starts without them;
|
||||
- are written by the agent for itself, in its own words, and shown to nobody: the operator cannot see
|
||||
what the agent taught itself, or correct it;
|
||||
- are not read before a tool runs: a note saying "read the merge gate first" is read at session start,
|
||||
if the agent opens it, and nothing says it again at the moment a merge is about to be called.
|
||||
|
||||
The mesh keeps one related record: the guard's record of every refusal and override
|
||||
([ADR 0245](../../02-DECISIONS/0245-a-verb-says-what-it-replaces-and-the-agent-is-guarded-from-working-round-the-mesh.md) §5),
|
||||
which is how a habit that found a way round is seen. It records what the guard refused, not what the
|
||||
operator corrected.
|
||||
|
||||
## 4. What the vendor loads from the home
|
||||
|
||||
As documented by the vendor and confirmed on the laptop on 2026-10-09 (a session's own context names the
|
||||
two rule files as "the user's private global instructions for all projects"): the home's `CLAUDE.md` and
|
||||
every file under `rules/` are read into every session the account starts, in every project; `skills/`,
|
||||
`commands/` and `agents/` are offered under their own names; `settings.json` is the account's settings,
|
||||
below the managed settings; hooks come from `settings.json` and from a plugin. None of it is read for
|
||||
another account, and none of it reaches another machine.
|
||||
|
||||
## 5. The options
|
||||
|
||||
### Who owns the configuration under the home
|
||||
|
||||
1. **As today: the module owns what it placed, the rest is found** (ADR 0216 §5, ADR 0182). Rejected by
|
||||
the operator's direction, and by §2: a rule on one node reaches no other, a retired skill stays on
|
||||
four, and nothing is raised. There is a second reason: the guard refuses an agent that reaches a
|
||||
machine by ssh, and refuses nothing that writes `~/.claude/rules/x.md`. On a node where the agent
|
||||
runs as the operator, that is a rule planted in every later session, by hand, with no record.
|
||||
2. **The module owns the whole directory and regenerates it.** Rejected in ADR 0182 (option 1), and still:
|
||||
the working state is the program's, and a render that touched `projects/` would erase a season of
|
||||
memory.
|
||||
3. **The module owns every configuration file by name, excludes the program's working state by name,
|
||||
renders the configuration from registrations, and reports a hand edit as a condition.** Chosen.
|
||||
|
||||
### Whose the rules are
|
||||
|
||||
1. **Per node** (the home scope as ADR 0216 defines it: one account's directory on one machine). Rejected
|
||||
as the only scope: the operator is one person on four nodes, and §2 shows what one-node rules give.
|
||||
2. **Mesh-wide instruction sections**, which already reach every node. Rejected as the place for them:
|
||||
those are the mesh's word to every session, written at the controller's terminal, about the mesh.
|
||||
A personal rule is the operator's word about the operator: how to hand over text, which tools their
|
||||
other employer's repositories need. It belongs to the person and follows the person's agent, into the
|
||||
home, under the person's own names.
|
||||
3. **A new kind, the personal rule, registered for the operator and rendered into the agent's home on
|
||||
every node.** Chosen. The same scope serves the operator's own skills, commands, subagents and settings.
|
||||
|
||||
### How a lesson becomes a rule
|
||||
|
||||
1. **The agent writes the rule itself.** Rejected: an agent that writes its own rules can loosen them, the
|
||||
operator never sees what it taught itself, and issue 340's reasoning holds — text that every session
|
||||
reads is written on the operator's word, never on an agent's.
|
||||
2. **Lessons stay in the agent's memory.** Rejected by §3.
|
||||
3. **A lessons register in the module. An agent proposes a lesson; the operator says yes or no; a yes
|
||||
makes it a personal rule, rendered everywhere; the agent is reminded of the rules that bear on what it
|
||||
is about to do.** Chosen.
|
||||
|
||||
### How the operator's yes reaches the operator's home
|
||||
|
||||
1. **Trust the tool's caller.** Rejected in issue 340.
|
||||
2. **The controller's terminal only.** Safe; kept as one of two ways. It makes a yes in a session a trip
|
||||
to the control-node, which is what the operator asked to avoid.
|
||||
3. **A warrant on an authorising ask** (ADR 0259): the module asks the operator, with the exact text
|
||||
bound in the ask, and acts on the warrant alone. Chosen beside 2. The approve option needs a channel
|
||||
that proves its sender; until one answers, the terminal is the way.
|
||||
+11
@@ -118,6 +118,17 @@ ADR 0213's setting, then the mesh scope, then the node scope, then the mesh's ow
|
||||
state. It writes, changes and removes only those. It refuses to register a name the person already
|
||||
uses there, rather than overwrite it.
|
||||
|
||||
> **The mechanism changed — 2026-10-09, by [ADR 0273](0273-the-agents-home-is-the-modules-and-the-operators-personal-rules-and-lessons-are-registered-through-it.md).**
|
||||
> What stands: one plugin, three scopes, registration through the module's tools and state on the bus (§1
|
||||
> to §4). What moved: §5 and §6. The module now owns every configuration file under the agent's home,
|
||||
> rendered from registrations, with the program's working state excluded by name; a file it did not render
|
||||
> is a condition, `agent-home-drift`, rather than an item listed on request, and a hand edit of a rendered
|
||||
> file is written back with the hand version kept. A fourth scope, the operator's, carries the operator's
|
||||
> personal rules, skills, commands, subagents and settings into the agent's home on every node. Why: the
|
||||
> homes measured on 2026-10-09 held a skill retired a week before on all four nodes and the operator's rules
|
||||
> on one, and nothing was raised. Who may register into the operator's home is issue 340's rule, kept: the
|
||||
> controller's terminal, or a warrant on an ask.
|
||||
|
||||
**6. What the module did not place, it reports and can import.**
|
||||
|
||||
- A status tool lists the home's items and says which the mesh placed. It also names any that
|
||||
|
||||
+7
@@ -53,6 +53,13 @@ Option 3.
|
||||
This extends rule 5 of ADR 0216: the module removes what it placed, and also, on the person's
|
||||
word, what it did not place. Rule 6 stands as written.
|
||||
|
||||
> **The mechanism changed — 2026-10-09, by [ADR 0273](0273-the-agents-home-is-the-modules-and-the-operators-personal-rules-and-lessons-are-registered-through-it.md).**
|
||||
> What stands: a found item leaves the home only on the person's word, with a reason, a copy kept, and a
|
||||
> way back. What moved: a found item is now a condition the module raises (`agent-home-drift`), and this
|
||||
> removal and the import are the operator's two answers to it; and for the operator's own home "the
|
||||
> person's word" is no longer the caller's say-so (the consequence below) but one of two proofs, the
|
||||
> controller's terminal or a warrant on an ask, as issue 340 requires.
|
||||
|
||||
## Consequences
|
||||
|
||||
- An item the mesh did not place can now leave a machine with its reason recorded and its content
|
||||
|
||||
+419
@@ -0,0 +1,419 @@
|
||||
---
|
||||
topic: what runs on it
|
||||
status: accepted
|
||||
date: 2026-10-09
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
extends: 02-DECISIONS/0216-the-agents-configuration-is-registered-through-its-module-at-three-scopes-and-served-as-one-plugin.md
|
||||
---
|
||||
|
||||
# 273. The agent's home is the module's, and the operator's personal rules and lessons are registered through it
|
||||
|
||||
## Context
|
||||
|
||||
[ADR 0216](0216-the-agents-configuration-is-registered-through-its-module-at-three-scopes-and-served-as-one-plugin.md)
|
||||
put the coding agent's configuration under its module at three scopes, and gave the home scope the lightest
|
||||
touch: the module "owns only what it placed" (§5), reports what it did not place and can import it (§6), and
|
||||
"the stale files already in the homes stay until the person removes them". That followed
|
||||
[ADR 0182](0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md): inside a home, the
|
||||
person's own rules and skills are *found*, never read, rewritten or removed.
|
||||
[ADR 0243](0243-the-agent-module-removes-a-home-item-it-did-not-place-only-on-the-persons-word-and-keeps-a-copy.md)
|
||||
added the one channel for removing a found item, on the person's word, with a copy kept.
|
||||
|
||||
[Issue 340](../04-ISSUES/340-an-agent-can-plant-a-hook-in-every-agent-session-on-the-mesh/00-report.md) then
|
||||
found that the module's tools could not tell the operator from an agent, so an agent confined on the
|
||||
control-node could register a hook, or a rule, that the operator's next session on another node would run or
|
||||
obey. The operator closed it on 2026-10-09: shared text and everything that runs is set at the controller's
|
||||
terminal alone, and the home scope is written only into the home of an agent account of its own
|
||||
([ADR 0266](0266-on-the-control-node-agents-run-under-an-account-of-their-own-that-cannot-become-root-without-a-person.md)),
|
||||
never into the operator's. The same day
|
||||
[ADR 0272](0272-the-operator-reaches-the-controller-with-mesh-cli-on-every-node-and-a-node-is-the-terminal-only-where-no-agent-runs-as-its-operator.md)
|
||||
gave the operator a way to that terminal, and
|
||||
[ADR 0259](0259-an-ask-is-a-message-on-the-bus-any-channel-answers-it-and-the-asker-acts-on-a-warrant.md) gave
|
||||
any module a way to ask the operator a question and act on the answer with a warrant.
|
||||
|
||||
**What the homes hold**, measured on 2026-10-09 through each node's own status tool
|
||||
([research 040](../01-RESEARCH/040-the-agents-home-is-the-modules-and-the-operators-rules-and-lessons/01-what-the-home-holds-and-the-options.md) §2):
|
||||
|
||||
- a skill [to-be 36](../03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md) §1 retired on 2026-10-02
|
||||
("nothing; it named the predecessor's forge") is still in the home on **all four nodes**;
|
||||
- a person's instruction file is on three; a subagent on one;
|
||||
- the operator's two hand-written rule files, about how the operator wants text handed over and how a second
|
||||
forge's repositories are worked in, are on **the laptop alone**;
|
||||
- the mesh has placed nothing in any home, and raises no condition about any of it.
|
||||
|
||||
On the laptop the directory holds 33 entries: 7 are configuration (the two rules, five skills and a synced
|
||||
folder, an empty commands folder, a settings file with 6 keys, a local settings file the program writes with
|
||||
the permission answers a person gives, the program's plugin records), and the rest is the program's own
|
||||
working state — history, sessions, the memory of 30 projects (1.5 GB), caches, the credentials file
|
||||
([research 040](../01-RESEARCH/040-the-agents-home-is-the-modules-and-the-operators-rules-and-lessons/01-what-the-home-holds-and-the-options.md) §1).
|
||||
|
||||
**What the agent learns, and where it keeps it.** The agent keeps notes for itself in the program's memory
|
||||
for one repository on the laptop: 45 on 2026-10-09, at least 9 of them a correction the operator gave of how
|
||||
the agent works — finish a module before pushing, use the mesh's tools and not ssh, read the merge gate before
|
||||
merging, no work-arounds for a missing tool. Each was said once, sometimes with irritation, written by the
|
||||
agent in its own words, shown to nobody, read on no other node and in no other repository, and not said again
|
||||
at the moment it matters ([research 040](../01-RESEARCH/040-the-agents-home-is-the-modules-and-the-operators-rules-and-lessons/01-what-the-home-holds-and-the-options.md) §3).
|
||||
|
||||
**The operator's direction, 2026-10-09**, in their words:
|
||||
|
||||
> "the home-scope does own ~/.claude, so it should be completely empty and manual edits should be forbidden.
|
||||
> Rules in my home directory are my 'personal rules' which should also end up under control of the
|
||||
> claude-code module and all other changes to ~/.claude should go through the claude-code module's mcp tools"
|
||||
|
||||
> "register the decision, but make sure it's clear and that you can/should be pro-active about it. For
|
||||
> example, if I'm frustrated, try to see why and try to prevent it in the future by warning yourself about
|
||||
> stuff not to do to frustrate me — kind of like a self-learning process."
|
||||
|
||||
The record was searched first ("personal rules lessons frustration self-learning"; "hand edit home .claude
|
||||
drift condition"): nothing.
|
||||
|
||||
### Against GENESIS
|
||||
|
||||
- **The mesh owns the truth.** "State lives in the mesh and is derived onto nodes. A file edited on a node
|
||||
is a bug with a delay on it." A rule file written by hand into the agent's home is that file.
|
||||
- **The mesh remembers.** "Nothing has to be explained twice." A correction the operator gives once reaches
|
||||
every session of theirs, and is said again where it applies.
|
||||
- **Failure must be loud.** A hand edit is a condition, not a silent difference the next render erases.
|
||||
- **Nodes are personal; the mesh is a guest.** The program's own working state — memory, history,
|
||||
sessions — stays the program's, excluded by name, exactly as ADR 0182 drew the line.
|
||||
|
||||
## Considered Options
|
||||
|
||||
The options are weighed in full in
|
||||
[research 040 §5](../01-RESEARCH/040-the-agents-home-is-the-modules-and-the-operators-rules-and-lessons/01-what-the-home-holds-and-the-options.md);
|
||||
in short:
|
||||
|
||||
### Who owns the configuration under the home
|
||||
|
||||
1. **As today: the module owns what it placed, the rest is found** (ADR 0216 §5). Rejected by the operator,
|
||||
and by the measurement: a rule on one node reaches no other, a retired skill stays on four, nothing is
|
||||
raised. And the guard refuses ssh to a machine but not a write to `~/.claude/rules/`: on a node where the
|
||||
agent runs as the operator, that is a rule planted in every later session, by hand, with no record.
|
||||
2. **The module owns the whole directory and regenerates it.** Rejected, as ADR 0182 rejected it: the
|
||||
working state is the program's, and a render touching the projects' memory erases a season of it.
|
||||
3. **The module owns every configuration file by name, excludes the working state by name, renders the
|
||||
configuration from registrations, and reports a hand edit as a condition.** Chosen.
|
||||
|
||||
### Whose the rules are
|
||||
|
||||
1. **Per node only**, the home scope as ADR 0216 defines it. Rejected as the only scope: the operator is
|
||||
one person on four nodes.
|
||||
2. **Mesh-wide instruction sections.** Rejected as their place: those are the mesh's word about the mesh,
|
||||
written at the terminal. A personal rule is the operator's word about the operator, and follows the
|
||||
person's agent into the home under the person's own names.
|
||||
3. **A new kind, the personal rule, registered for the operator, rendered into the agent's home on every
|
||||
node.** Chosen; the same scope carries the operator's own skills, commands, subagents and settings.
|
||||
|
||||
### How a lesson becomes a rule
|
||||
|
||||
1. **The agent writes the rule itself.** Rejected: an agent that writes its own rules can loosen them, the
|
||||
operator never sees what it taught itself, and text every session reads is written on the operator's
|
||||
word, never an agent's (issue 340).
|
||||
2. **Lessons stay in the agent's memory.** Rejected by the measurement above.
|
||||
3. **A lessons register in the module; a lesson is proposed by an agent, accepted or declined by the
|
||||
operator, rendered as a personal rule on a yes, and said again before the act it bears on.** Chosen.
|
||||
|
||||
### How the operator's yes reaches the operator's home
|
||||
|
||||
1. **Trust the tool's caller.** Rejected in issue 340.
|
||||
2. **The controller's terminal only.** Safe, and kept as one of two ways; alone, it makes a yes in a
|
||||
session a trip to the control-node.
|
||||
3. **A warrant on an authorising ask** (ADR 0259), the exact text bound in the ask. Chosen beside 2.
|
||||
|
||||
## Decision
|
||||
|
||||
### 1. The configuration under the agent's home is the module's, whole
|
||||
|
||||
**What "completely empty" means.** The operator's words, "it should be completely empty", are read as: the
|
||||
agent's home holds **nothing a person or an agent put there by hand**. Every configuration file in it is the
|
||||
module's rendering, and the only other entries are the program's own working state, named below.
|
||||
|
||||
**The `claude-code` module owns every configuration file under the agent's home directory** (`~/.claude` of
|
||||
the account the agent runs as, ADR 0266 §5): the home instruction file, `rules/`, `skills/`, `commands/`,
|
||||
`agents/`, `hooks/`, `output-styles/`, `settings.json` and `keybindings.json`. **Nothing there is written by
|
||||
hand.** Each is rendered by the module's code from registrations, on every node, whenever a registration
|
||||
changes, and nothing else writes them. A configuration file the vendor adds later joins the list by name, in
|
||||
the module, before the module renders anything into it.
|
||||
|
||||
**The program's working state is excluded by name**, and stays the program's, in ADR 0182's *found* class
|
||||
or *written by the module's own process*: the credentials file `.credentials.json` (ADR 0183), the history,
|
||||
the sessions and their environments, the projects directory with every project's memory, `todos/`,
|
||||
`plans/`, `statsig/`, the shell snapshots, the file history, the debug, cache, paste and telemetry
|
||||
directories, the statistics, the daemon's files (`daemon/`, its lock, log and status files,
|
||||
`daemon-auth-status.json`, `daemon-auth-cooldown`), the program's dot-markers (`.last-cleanup`), the
|
||||
vendor's synced skills (`skills/synced/`), and the backups, worktrees, jobs, feedback, state and IDE
|
||||
directories. The module never writes or removes any of these, and names the list in its manifest's
|
||||
documentation.
|
||||
|
||||
**Two entries are the program's to write, and watched**, because each decides what a session runs and
|
||||
nothing else would see a change to it: `settings.local.json`, where the program records the permission
|
||||
answers a person gives in a session, and `plugins/`, the program's record of its marketplaces and installed
|
||||
plugins, and their cache. The module never writes them; §4 says what it raises and what the guard refuses.
|
||||
|
||||
**What is neither, by name.** Measured on the laptop on 2026-10-09: `.hal-binding.json`, a leftover of the
|
||||
retired predecessor, and `.credentials.json.testbak`, a copy of the credentials file left by a test. **No
|
||||
copy of the credentials file** (`.credentials.json` followed by anything) belongs in the home: each is
|
||||
reported, and removed on the operator's word with ADR 0243's tool, never kept as a copy in the module's
|
||||
state, because it holds a token. **Every other entry the module knows under neither list is a condition**
|
||||
(§4), so a new vendor file is a question the operator answers, not a default.
|
||||
|
||||
**The keys the program itself writes into `settings.json` on a person's choice** — the model, the spinner,
|
||||
the drafts and the other preferences the vendor's own `/model` and `/config` commands set — are not hand
|
||||
edits. The module keeps a short list of them, preferences only. When the program changes one of those keys,
|
||||
the module takes the new value over into the operator's registered settings (§2), reports it in the status
|
||||
tool, and the change follows the operator to every node. That is ADR 0182's *written into* class, applied
|
||||
the other way round: the file is the module's, and the program is allowed into a few named keys of it. This
|
||||
is the mechanism that keeps a `/model` choice from being reverted, the failure ADR 0182's context records.
|
||||
**A key that decides what runs is never taken over**: `hooks`, `permissions`, `mcpServers` and the other
|
||||
tool-server keys, `env`, `apiKeyHelper`, `statusLine`, `autoMode`, and every key not on the list. Changed on
|
||||
disk, it is written back on the look that finds it (§4).
|
||||
|
||||
**`autoMode` is a permission rule.** It tells the program's unattended mode what it may do without asking,
|
||||
which ADR 0213 calls policy, not preference. It is barred from the operator scope like `permissions` and
|
||||
`hooks`, and belongs in the managed settings, set at the controller's terminal in the module's
|
||||
`managed_settings` for the mesh or a node.
|
||||
|
||||
### 2. A fourth scope, the operator's, and a new kind, the personal rule
|
||||
|
||||
ADR 0216's three scopes stand. A fourth joins them: **operator**. An item registered at the operator scope
|
||||
is rendered into the agent's home on **every node that names an operator account** (ADR 0181): into the
|
||||
operator's own home where the agent runs as the operator, and into the agent account's home where the node
|
||||
names one (ADR 0266). The mesh has one operator; the scope is the person, not a machine.
|
||||
|
||||
The kinds at the operator scope:
|
||||
|
||||
- **personal rule** (new): one short text in the operator's words about how the operator wants the agent
|
||||
to work with them, rendered as one file under `rules/` and listed in the home instruction file, which the
|
||||
module renders as the index of the personal rules, each with where it came from (the terminal, or the
|
||||
lesson it was accepted from) and when. The vendor reads both into every session of that account;
|
||||
- the operator's own **skills, commands and subagents**, under the person's own names, without the plugin's
|
||||
prefix;
|
||||
- the operator's **settings** (`settings.json`), as one registered object of the preference keys of §1,
|
||||
laid under the managed settings as the vendor lays them; and the operator's **keybindings** and **output
|
||||
styles**.
|
||||
|
||||
**Never at the operator scope: a hook, a permission rule (`autoMode` included), a tool server, an
|
||||
environment or key-helper setting, or an item that runs anything.**
|
||||
Those stay the terminal's, in the managed settings and the managed tool-server file (issue 340). A personal
|
||||
rule, skill, command or subagent that holds a shell line or the front matter issue 340 refuses is refused at
|
||||
the operator scope too.
|
||||
|
||||
**The operator's existing items are imported as the first registrations**, through the import tool ADR 0216
|
||||
§6 named, at the operator scope: the two rule files on the laptop become the first two personal rules; the
|
||||
laptop's skills and the workstation's subagent are imported or removed, each on the operator's word
|
||||
(ADR 0243); the preference keys of `settings.json` on the laptop become the operator's registered settings,
|
||||
and its `autoMode` key moves to the managed settings at the terminal, because it is a permission rule (§1);
|
||||
the two leftovers §1 names are removed on the operator's word; the retired `cleanup` skill and the three
|
||||
instruction files are removed with a copy kept. Until each item is imported or removed it
|
||||
is a condition (§4).
|
||||
|
||||
### 3. Who may register into the operator's home, and issue 340's protection stands
|
||||
|
||||
A registration at the operator scope, and a registration at the home scope into an operator's own home, is
|
||||
taken **only** by one of two routes, and refused through any other:
|
||||
|
||||
1. **At the controller's terminal** (ADR 0272), as issue 340 made shared text: the module's settings gain
|
||||
`personal_rules`, `personal_skills`, `personal_commands`, `personal_agents` and `personal_settings`, set
|
||||
with `nox settings set claude-code …` on the control-node. The module's whole layer is already the
|
||||
terminal's, because its settings file is a mergeable file issue 340 counts as trusted.
|
||||
2. **On a warrant** (ADR 0259): the module is an asker. Its register, import and remove tools, called at the
|
||||
operator scope or for an operator's home, do not act; they publish an **approve** ask whose words show the
|
||||
item in full and whose digest binds the exact text, name, kind and scope, and they act once on the warrant
|
||||
for that ask, recording it. Until a channel that proves its sender answers approve for the module, the
|
||||
ask is refused at once with the reason and names the terminal's line.
|
||||
|
||||
**The module's tools stay the operator's way in.** A tool called for the operator's home, while no channel
|
||||
answers approve, does not only refuse: it records the request as **pending** in the module's state (what,
|
||||
at which scope, asked when, from which node) and answers with the exact line to type: `nox settings set
|
||||
claude-code '<json>'`, the request merged into the module's current layer, so that typing it changes nothing
|
||||
else. The status tool lists pending requests; one is dropped when the terminal's layer holds it, or after
|
||||
7 days. An agent asking for a rule therefore hands the operator one line to check and type, never a
|
||||
description to translate.
|
||||
|
||||
The home scope into **an agent account's own home** stays as issue 340 left it: that node's own tools take
|
||||
it, because that home is no person's. A home-scope item never shadows a personal rule: the module renders
|
||||
the operator's items first and refuses a home-scope name an operator-scope item already uses.
|
||||
|
||||
So an agent still cannot plant anything in the operator's home: a tool call without a warrant writes
|
||||
nothing there, a warrant is the router's record of the operator's own answer, and the terminal is the
|
||||
operator's own account on a node where no agent runs as it.
|
||||
|
||||
### 4. A hand edit is a condition, and the guard refuses it
|
||||
|
||||
**On every look**, at least once a minute and at once on a change on disk, the module compares the
|
||||
configuration paths of §1 with what it last rendered, and reads the two watched entries. Any difference is a
|
||||
**finding**, said as the module's own health (ADR 0240) on that node, and the controller raises the
|
||||
condition **`agent-home-drift`** for it, a warning that needs the operator, naming each path and what
|
||||
differs. **The look that finds a difference acts on it**; nothing waits for the next render:
|
||||
|
||||
- **a rendered file changed by hand**, `settings.json` above all: written back on that look, the hand version
|
||||
kept as a copy in the module's state (`home-copies`) and shown by the status tool, so the words are not
|
||||
lost. The one exception is a preference key of §1 the program changed, which is taken over. A `hooks`,
|
||||
`permissions`, tool-server or other key that decides what runs, added or changed in `settings.json`, is
|
||||
**always written back**, never taken over;
|
||||
- **a rendered file missing**: rendered again on that look, and reported once;
|
||||
- **a file nobody rendered** under `rules/`, `skills/`, `commands/`, `agents/`, `hooks/` or
|
||||
`output-styles/`:
|
||||
- **before the node's first adoption** (the imports of §2, done once per node by the operator, and recorded
|
||||
in the module's state): left in place and reported, never removed by the module. The operator's answers
|
||||
are the import tool (register it at a scope) or the removal tool (ADR 0243: a reason, a copy kept);
|
||||
- **after it**: **moved** on that look into `home-copies`, kept and restorable with ADR 0243's restore tool,
|
||||
and raised. A session never loads a file nobody registered for longer than one look. Importing it from
|
||||
the copy is the operator's way to keep it;
|
||||
- **an entry the module knows under neither list of §1**: reported and raised, never touched; the operator
|
||||
removes it on their word or the module's next build names it;
|
||||
- **`settings.local.json` holding any key other than `permissions`**: raised, naming the key. The program
|
||||
writes only that key there, so another is somebody's hand;
|
||||
- **`plugins/` naming a marketplace or an installed plugin other than the vendor's own marketplace and the
|
||||
mesh's `nox-mesh`**: raised, naming it. A plugin carries hooks, so one installed by hand runs in every
|
||||
session.
|
||||
|
||||
The condition clears on the first look that finds no difference. It is never urgent, and the first-node gate
|
||||
never reads it as a fault of a build (as
|
||||
[ADR 0254](0254-a-wait-for-a-persons-new-login-is-not-a-gates-failure-and-each-module-of-a-send-keeps-its-own-pass.md)
|
||||
treats a wait for a person).
|
||||
|
||||
**The guard on the agent's shell** (ADR 0245) refuses, before it runs, naming the module's tool that does the
|
||||
job and saying that a rule the agent wants is a lesson (§5):
|
||||
|
||||
- any shell command or file edit whose target is under a configuration path of §1 — a write, an append, a
|
||||
copy or move into it, an in-place edit, a removal;
|
||||
- the same aimed at `settings.local.json` or at a `.json` file under `plugins/`;
|
||||
- `claude plugin install`, `claude plugin marketplace add` and the in-session `/plugin install` and
|
||||
`/plugin marketplace add`, whatever the plugin.
|
||||
|
||||
The operator's override of ADR 0245 §5 applies and is recorded. The guard does not watch the rest of the
|
||||
working state.
|
||||
|
||||
### 5. Lessons: an agent proposes, the operator decides, and the agent is reminded
|
||||
|
||||
**The module keeps a lessons register** in its state (ADR 0201): one record per lesson, holding when and on
|
||||
which node it was proposed, the session that proposed it where the agent gave it, the **trigger** (the
|
||||
operator's words, quoted), the **cause** (what the agent did, in one or two sentences), the **rule** that
|
||||
would prevent it (written as a personal rule, in the operator's second person: "do not…", "always…"), an
|
||||
optional **before** (a tool name, a command prefix or a path pattern the rule bears on), and its state:
|
||||
`proposed`, `accepted` or `declined`, with who decided, how and when.
|
||||
|
||||
**When an agent records a lesson.** The mesh's instruction section tells every agent: when the operator
|
||||
expresses frustration (repeats a request, says they have asked before, says "again", "stop", "why did you";
|
||||
irritation in any words), or corrects a way of working ("don't", "always", "from now on", "never"), the agent
|
||||
first works out why — what it did, and what the operator wanted instead — and then records a lesson with
|
||||
the trigger, the cause and the rule, in the same turn, before going on. It says in its answer that it did,
|
||||
in one line. An agent that is unsure whether a correction is a lesson records it; the operator declines what
|
||||
is not.
|
||||
|
||||
**A correction goes to the register, not to the agent's own memory.** The program's per-project memory
|
||||
(§1, working state) stays the agent's notebook for facts about a project; a way of working the operator
|
||||
corrected is a lesson, so that it reaches every node and the operator sees it.
|
||||
|
||||
**The first lessons are the agent's existing corrections.** Once the register exists on a node, the agent
|
||||
there proposes, unasked, every note in its own memory that records a correction the operator gave (at least 9
|
||||
on the laptop on 2026-10-09), each as a lesson with the note's trigger, cause and rule, and says so in one
|
||||
line. The operator accepts or declines them like any other.
|
||||
|
||||
**A proposed lesson is not a rule.** It is rendered nowhere and reaches no session as an instruction. The
|
||||
module shows it to the operator for a yes: an approve ask (§3 route 2) whose words carry the trigger, the
|
||||
cause and the rule, with Accept and Decline as its options; or, until a channel answers approve, the list
|
||||
tool names the proposed lessons and the operator accepts or declines each at the terminal (`nox settings
|
||||
set claude-code …` with the lesson's id in `lessons_accepted` or `lessons_declined`). A proposed lesson
|
||||
expires after 30 days, declined with that reason.
|
||||
|
||||
**An accepted lesson is a personal rule.** The module registers it at the operator scope with the lesson's
|
||||
id as its source, so it renders into the agent's home on every node like any personal rule, and the home
|
||||
instruction file names it as "accepted from lesson N on <date>". **A declined lesson stays in the register
|
||||
with its reason**, and the list tool shows it, so an agent does not propose it again; a new lesson whose rule
|
||||
matches a declined one's is refused with the reason, and the agent is told to say so to the operator rather
|
||||
than record it again.
|
||||
|
||||
**The agent is reminded, before it acts.** Two things make the lessons proactive rather than a file the agent
|
||||
may or may not open:
|
||||
|
||||
- **at session start**, the plugin's session-start hook (the module's own binary, beside the guard) prints
|
||||
the personal rules accepted from lessons in the last 30 days and the ones whose `before` matches the
|
||||
session's working directory or repository, each in one line, so the session begins knowing them;
|
||||
- **before a tool runs**, the guard (ADR 0245 §4, asked about every tool) prints the rule of every accepted
|
||||
lesson whose `before` matches the tool, the command or the path about to be used — a reminder, never a
|
||||
refusal; a refusal is a guard rule and nothing else. The reminder is printed once per session per rule.
|
||||
|
||||
**The register is readable by any agent on any node** through the list tool, and shows `proposed`,
|
||||
`accepted` and `declined` apart, each marked, so a session reading it cannot take a proposal for a rule.
|
||||
Recording a proposal needs no yes: it writes the register and nothing a session obeys.
|
||||
|
||||
### 6. What this changes in the earlier records
|
||||
|
||||
- **ADR 0216 §5 and §6**: the mechanism changes (that record gains a note). The home scope owns the
|
||||
configuration whole; a found item is reported as a condition rather than listed on request; import stays
|
||||
the way in; the plugin's hook file holds the module's own hooks, the guard and the session-start
|
||||
reminder, and nothing registered.
|
||||
- **ADR 0243** stands as the removal on the person's word, now on a warrant or at the terminal for an
|
||||
operator's home, and is the operator's one answer to a found item beside importing it.
|
||||
- **ADR 0182** stands: the class table holds, with the configuration files in the *owned* class and the
|
||||
working state *found*, each named.
|
||||
- **ADR 0245**: the guard gains a refusal (§4) and a reminder (§5).
|
||||
- **Issue 340**: the protection stands; the terminal and the warrant are the two ways into the operator's
|
||||
home.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The operator's rules follow the operator: a rule written once reaches the agent's home on every node
|
||||
within a minute, and a session on any node starts with it.
|
||||
- A hand edit under `~/.claude` is undone within a minute, with the hand version kept, and is said as a
|
||||
condition: no silent difference, and no lost words.
|
||||
- The retired skill and the three stale instruction files, found on four nodes since 2026-10-02, are
|
||||
removed on the operator's word with copies kept, and nothing like them can stay unnoticed again.
|
||||
- A correction the operator gives once becomes a rule everywhere on one yes, and is said again before the
|
||||
act it bears on. The operator sees every rule the agent proposes, and can decline it.
|
||||
- **What got harder:**
|
||||
- a new rule for the agent is no longer a file: it is a registration at the terminal or a warrant on an
|
||||
ask. Until a channel answers approve for the module, every yes is typed on the control-node;
|
||||
- the module watches the home on every node, and its state holds the lessons register and the copies of
|
||||
hand versions. Clearing old copies is not decided here, as ADR 0243 left it;
|
||||
- a vendor file the module does not know is a condition until it is classified, which is the point: an
|
||||
unknown file in the agent's home is a question, not a default;
|
||||
- an agent is expected to notice frustration and record a lesson in the same turn, which costs the turn a
|
||||
tool call. A lesson recorded late, or not at all, is found by reading the session against the register.
|
||||
- **Not decided here:** a per-project lesson (one bound to a repository rather than the operator); lessons
|
||||
for an agent the mesh runs rather than the operator's; and a channel of the operator's own that can
|
||||
tell the operator from an agent (issue 340's "once we build a UI"), which would become a third route.
|
||||
|
||||
## How it is checked
|
||||
|
||||
| Rule | Checked by |
|
||||
|---|---|
|
||||
| Every configuration path of §1 is rendered from registrations and nothing else; the working state is never written or removed; an entry under neither list is a condition | the module's render test over a seeded home: the configuration files (`keybindings.json` and `output-styles/` included) match the registrations byte for byte; every excluded entry (`todos/`, `plans/`, `statsig/`, `.last-cleanup`, `daemon-auth-cooldown` among them) is byte-identical before and after; an unknown entry, `.hal-binding.json` and a `.credentials.json.*` copy each raise `agent-home-drift` and are left in place; removing a credentials copy keeps no copy of it |
|
||||
| A program-written preference key of `settings.json` is taken over; a key that decides what runs is always written back; `autoMode` is never taken at the operator scope | the module's test: the model key changed on disk appears in the operator's registered settings on the next look and in the rendered file on every node; `hooks`, `permissions`, `mcpServers`, `env`, `apiKeyHelper`, `statusLine` and `autoMode` added or changed on disk are each written back on the look that finds it and never reach the registration; `autoMode` in a `personal_settings` value is refused |
|
||||
| A personal rule renders into the agent's home on every node naming an operator account, under `rules/` and in the home instruction file | the module's render test for one machine of three: the operator's own home on two, the agent account's home on the one naming one; live: a rule set at the terminal is in a new session's context on each node |
|
||||
| Nothing that runs is taken at the operator scope | the module's tests: a hook, a permission rule, a tool server, and a skill, command or subagent holding a shell line or the refused front matter are each refused at the operator scope with issue 340's reason |
|
||||
| A registration into an operator's home is taken only at the terminal or on a warrant | the module's tests through its real tool dispatch: each register, import and remove tool at the operator scope, or for an operator's home, publishes an ask and writes nothing; the same with a warrant for that ask acts once; a warrant for another ask, or a replayed one, is refused; while no channel answers approve the ask is refused at once naming the terminal's line. The controller's test: the module's layer is the terminal's (issue 340's) |
|
||||
| A hand edit is a condition, undone on the look that finds it | the module's tests with a clock: a rendered file changed by hand (`settings.json` among them) is written back on the same look, within a minute, its hand version kept and shown; a missing one is rendered again on that look; the condition clears on a clean look; the controller's test that `agent-home-drift` is a warning the first-node gate never reads as a fault |
|
||||
| Before a node's first adoption a file nobody rendered is left; after it, it is moved to the copies and raised | the module's tests: an unrendered file under each of `rules/`, `skills/`, `commands/`, `agents/`, `hooks/`, `output-styles/` is reported and left before the adoption is recorded; after it, the same file is gone from the home on that look, present in `home-copies`, restorable with the restore tool, and the condition names it |
|
||||
| `settings.local.json` and `plugins/` are watched | the module's tests: a key other than `permissions` in `settings.local.json` raises the condition naming it; a marketplace or installed plugin other than the vendor's and `nox-mesh` in `plugins/` raises it naming it; neither file is written by the module |
|
||||
| The guard refuses a write under a configuration path, to `settings.local.json` or `plugins/*.json`, and a plugin install, and lets the working state alone | the module's guard tests: a redirect, append, copy, move, in-place edit and removal under each path, and aimed at `settings.local.json` and a `.json` under `plugins/`, refused naming the tool; `claude plugin install`, `claude plugin marketplace add` and their in-session forms refused; the same write under `projects/` passes; the operator's override honoured and recorded |
|
||||
| A home-scope name may not shadow a personal rule | the module's test: with a personal rule, skill, command or subagent registered at the operator scope, a home-scope registration of the same name and kind is refused naming the operator-scope item, and the render holds the operator's item |
|
||||
| A tool called for the operator's home leaves a pending request and the exact terminal line | the module's tests: the call writes nothing into the home, records one pending request, and answers a `nox settings set claude-code '…'` line which, applied to the current layer, adds that item and changes no other key; the request is dropped once the layer holds it, and after 7 days |
|
||||
| A lesson is recorded with its trigger, cause and rule; a proposal renders nowhere; an accepted one is a personal rule; a declined one is not proposed again | the module's tests over the register: register, list, accept, decline, expiry; the render test that only accepted lessons reach `rules/`; a second proposal matching a declined rule refused with the reason |
|
||||
| The agent is reminded at session start and before a matching tool | the module's hook tests: the session-start hook prints the recent and matching rules; the guard prints a matching lesson's rule once per session and refuses nothing for it |
|
||||
| An agent records a lesson when corrected, and proposes its existing corrections first | the module's render test: the rendered managed instruction file's `working-on-the-mesh` section holds the paragraph on when a lesson is recorded, that a correction goes to the register and not to the agent's memory, and that existing corrections are proposed first, and the test fails when the paragraph is taken out of the section; live: a correction given in a session is in the register within that turn, and the laptop's memory corrections are in the register as proposals after WP9 |
|
||||
| Live, at acceptance (the operator's view) | on the laptop: `~/.claude` holds the rendered configuration and the program's working state and nothing else; a rule written by hand into `rules/` raises `agent-home-drift` within a minute and is listed by the status tool; `nox settings set claude-code '{"personal_rules": …}'` on the control-node puts the rule into a new session on every node; a correction given in a session appears in the lessons list, is accepted at the terminal, and the next session on another node names it at start |
|
||||
|
||||
## References
|
||||
|
||||
- [Research 040](../01-RESEARCH/040-the-agents-home-is-the-modules-and-the-operators-rules-and-lessons/00-overview.md):
|
||||
the measurement on four nodes, the agent's memory, the options.
|
||||
- [ADR 0216](0216-the-agents-configuration-is-registered-through-its-module-at-three-scopes-and-served-as-one-plugin.md),
|
||||
[ADR 0182](0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md),
|
||||
[ADR 0243](0243-the-agent-module-removes-a-home-item-it-did-not-place-only-on-the-persons-word-and-keeps-a-copy.md):
|
||||
the home before this record.
|
||||
- [ADR 0245](0245-a-verb-says-what-it-replaces-and-the-agent-is-guarded-from-working-round-the-mesh.md): the guard.
|
||||
- [ADR 0259](0259-an-ask-is-a-message-on-the-bus-any-channel-answers-it-and-the-asker-acts-on-a-warrant.md),
|
||||
[ADR 0272](0272-the-operator-reaches-the-controller-with-mesh-cli-on-every-node-and-a-node-is-the-terminal-only-where-no-agent-runs-as-its-operator.md):
|
||||
the two ways the operator's word reaches the mesh.
|
||||
- [ADR 0266](0266-on-the-control-node-agents-run-under-an-account-of-their-own-that-cannot-become-root-without-a-person.md),
|
||||
[ADR 0181](0181-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md): whose home the agent's is.
|
||||
- [ADR 0240](0240-a-module-says-how-it-is-healthy-and-the-node-engine-judges-it.md),
|
||||
[ADR 0254](0254-a-wait-for-a-persons-new-login-is-not-a-gates-failure-and-each-module-of-a-send-keeps-its-own-pass.md):
|
||||
the condition and the gate.
|
||||
- [Issue 340](../04-ISSUES/340-an-agent-can-plant-a-hook-in-every-agent-session-on-the-mesh/00-report.md): the protection kept.
|
||||
- [To-be 36](../03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md) §1, §8 and §10,
|
||||
[to-be 40](../03-DESIGN/01-to-be/40-building-the-operators-agent-and-its-licence-manager.md) WP7–WP10: the design and the build.
|
||||
@@ -4,6 +4,7 @@ status: in-progress
|
||||
code: [mesh-catalog modules/claude-code]
|
||||
updated: 2026-10-09
|
||||
decisions:
|
||||
- 02-DECISIONS/0273-the-agents-home-is-the-modules-and-the-operators-personal-rules-and-lessons-are-registered-through-it.md
|
||||
- 02-DECISIONS/0266-on-the-control-node-agents-run-under-an-account-of-their-own-that-cannot-become-root-without-a-person.md
|
||||
- 02-DECISIONS/0243-the-agent-module-removes-a-home-item-it-did-not-place-only-on-the-persons-word-and-keeps-a-copy.md
|
||||
- 02-DECISIONS/0245-a-verb-says-what-it-replaces-and-the-agent-is-guarded-from-working-round-the-mesh.md
|
||||
@@ -63,12 +64,21 @@ instruction file and the manager's tools:
|
||||
|
||||
**The home.** Under [ADR 0182](../../02-DECISIONS/0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md)
|
||||
the 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.
|
||||
`0700` — and declares it, so the mesh refuses a second module owning it.
|
||||
|
||||
> **Revised 2026-10-09 by [ADR 0273](../../02-DECISIONS/0273-the-agents-home-is-the-modules-and-the-operators-personal-rules-and-lessons-are-registered-through-it.md).**
|
||||
> This paragraph said the module owns only the credentials file inside the directory and holds every other
|
||||
> path as found, the person's own rules and skills included. Now **every configuration file under the
|
||||
> directory is the module's** — the home instruction file, `rules/`, `skills/`, `commands/`, `agents/`,
|
||||
> `hooks/` and `settings.json` — rendered from registrations and never written by hand; and **the program's
|
||||
> working state is excluded by name** and stays found: the credentials file (written by the module's own
|
||||
> code, §5), the history, the sessions, the projects and their memory, the caches, the program's own plugin
|
||||
> records, the vendor's synced skills, and the local settings file the program writes with a person's
|
||||
> permission answers. §10 says how, and what a hand edit becomes. The six predecessor files are no longer
|
||||
> the operator's to remove by hand: a file nobody rendered is a condition, and the operator imports or
|
||||
> removes it through the module's tools.
|
||||
|
||||
The person's memory, history and projects are never read or written by the mesh.
|
||||
|
||||
## 2. What the module declares and what its code writes
|
||||
|
||||
@@ -244,28 +254,37 @@ machine. A machine that joins later takes it at its first start.
|
||||
The managed settings name the marketplace and enable the plugin. Those two keys are the mesh's, laid
|
||||
last with the attribution key, and no setting replaces them.
|
||||
|
||||
**Three scopes.** Every registration names one:
|
||||
**Three scopes, and a fourth since 2026-10-09.** Every registration names one:
|
||||
|
||||
- **mesh:** every machine running the agent;
|
||||
- **node:** one machine or a list of them, rendered into the same plugin and files there only;
|
||||
- **home:** the operator account's own agent directory on one machine, where the item sits as if
|
||||
written there by hand.
|
||||
- **home:** the agent account's own agent directory on one machine, where the item sits as if
|
||||
written there by hand. Since [issue 340](../../04-ISSUES/340-an-agent-can-plant-a-hook-in-every-agent-session-on-the-mesh/00-report.md)
|
||||
it is taken only for the node serving the call, and only where that home is an agent account's own
|
||||
(§9), never the operator's;
|
||||
- **operator** (ADR 0273, §10): the operator's own items, rendered into the agent's home on every node that
|
||||
names an operator account — the operator's own home where the agent runs as the operator, the agent
|
||||
account's home where the node names one.
|
||||
|
||||
Settings take the first two scopes only. In the managed settings file they are laid in this order:
|
||||
the operator's `managed_settings` setting (§2), then the mesh scope, then the node scope, then the
|
||||
mesh's own keys.
|
||||
Settings take the mesh and node scopes into the managed settings file, laid in this order: the operator's
|
||||
`managed_settings` setting (§2), then the mesh scope, then the node scope, then the mesh's own keys. The
|
||||
operator's own settings take the operator scope, into the home's `settings.json` (§10). **Since issue 340,
|
||||
hooks, permission rules, tool servers and every kind of shared text for the mesh or a node are set at the
|
||||
controller's terminal alone**, in the module's settings; the tools refuse them and say so.
|
||||
|
||||
**Instructions follow the scopes.** The managed instruction file holds the mesh's piece, then the node's
|
||||
piece: its role, and the sections registered for it. Further customisation per machine is a rule file
|
||||
placed at the home scope. The agent concatenates these and does not override, so the status tool names
|
||||
a section that contradicts another, or that calls a tool the mesh no longer serves.
|
||||
|
||||
**The home scope owns only what it placed.** The module records each home path it placed and touches
|
||||
only those (ADR 0182). It refuses to register a name the person already uses there.
|
||||
**The home is the module's** (ADR 0273, replacing "the home scope owns only what it placed"). The module
|
||||
renders every configuration file under the agent's home from the registrations that apply there, at the
|
||||
operator and home scopes, and records what it rendered. A name registered twice is refused: an operator-scope
|
||||
item is rendered first, and a home-scope item may not take its name.
|
||||
|
||||
The one exception is a removal the person asks for ([ADR 0243](../../02-DECISIONS/0243-the-agent-module-removes-a-home-item-it-did-not-place-only-on-the-persons-word-and-keeps-a-copy.md)).
|
||||
On the person's word, with a reason, the module removes an item it did not place. It keeps a checked copy in its own
|
||||
state and logs the removal, and can put the item back.
|
||||
A removal the person asks for ([ADR 0243](../../02-DECISIONS/0243-the-agent-module-removes-a-home-item-it-did-not-place-only-on-the-persons-word-and-keeps-a-copy.md))
|
||||
is now the operator's answer to a file nobody rendered (§10). On the person's word, with a reason, the
|
||||
module removes it, keeps a checked copy in its own state, logs the removal, and can put the item back.
|
||||
|
||||
**The tools.**
|
||||
|
||||
@@ -281,7 +300,7 @@ An item and its files are one value in the state, refused above 256 KiB.
|
||||
|
||||
**Changing the agent's own settings is the operator's act.** The vendor refuses an agent that loosens its
|
||||
own settings, and the module does not route around that refusal. A settings tool is called on the
|
||||
operator's word.
|
||||
operator's word — and, for the operator's own home, only at the terminal or on a warrant (§10).
|
||||
|
||||
## 9. The account the agent runs as
|
||||
|
||||
@@ -363,6 +382,121 @@ terminal. The next send names the operator account again: the guard lets the ope
|
||||
and the licence is fetched into the operator's home. The agent account is kept (the node-engine never deletes
|
||||
a login).
|
||||
|
||||
## 10. The home is the module's: personal rules, and the lessons an agent proposes
|
||||
|
||||
*Added 2026-10-09 by [ADR 0273](../../02-DECISIONS/0273-the-agents-home-is-the-modules-and-the-operators-personal-rules-and-lessons-are-registered-through-it.md).*
|
||||
|
||||
**What the module renders under the agent's home**, on every node, as the account the agent runs as (through
|
||||
the helper of §9 where that is an agent account):
|
||||
|
||||
| path | rendered from |
|
||||
|---|---|
|
||||
| the home instruction file | the index of the operator's personal rules: each rule's name, where it came from (the terminal, or the lesson it was accepted from) and when; a first line saying the file is the module's and names the tools that change it |
|
||||
| `rules/<name>.md` | one personal rule each |
|
||||
| `skills/<name>/`, `commands/<name>.md`, `agents/<name>.md` | the operator's own skills, commands and subagents at the operator scope, under their own names; and the home-scope items where the home is an agent account's own |
|
||||
| `hooks/` | nothing: a hook is the terminal's, in the managed settings. The directory is kept empty |
|
||||
| `output-styles/<name>.md`, `keybindings.json` | the operator's output styles and keybindings at the operator scope |
|
||||
| `settings.json` | the operator's registered settings, preference keys only, with the keys the program itself writes on a person's choice (the model, the spinner, the drafts, the other preferences the vendor's own commands set) taken over into the registration when the program changes them, so a choice made in one session follows the operator. A key that decides what runs (`hooks`, `permissions`, `mcpServers`, `env`, `apiKeyHelper`, `statusLine`, `autoMode`, any key not on the list) is never taken over: changed on disk, it is written back on the look that finds it. `autoMode` is a permission rule, so it lives in the managed settings at the terminal |
|
||||
|
||||
"Completely empty", the operator's words, means: nothing in the home was put there by hand. What is not the
|
||||
module's rendering is the program's own working state, excluded by name in the module's documentation and
|
||||
never written or removed: the credentials file (§5), the history, the sessions and their environments, the
|
||||
projects and their memory, `todos/`, `plans/`, `statsig/`, the shell snapshots, the file history, the debug,
|
||||
cache, paste and telemetry directories, the statistics, the daemon's files (`daemon-auth-cooldown` among
|
||||
them), the dot-markers (`.last-cleanup`), `skills/synced/`, and the backups, worktrees, jobs, feedback, state
|
||||
and IDE directories. **Two are the program's and watched**: `settings.local.json` (the permission answers a
|
||||
person gives) and `plugins/` (marketplaces and installed plugins). **Named as neither**: `.hal-binding.json`,
|
||||
a predecessor's leftover, and every copy of the credentials file (`.credentials.json` followed by anything,
|
||||
such as `.credentials.json.testbak`): reported, and removed on the operator's word, no copy kept because it
|
||||
holds a token. **Any other entry is a condition** until the operator removes it or the module names it.
|
||||
|
||||
**Who may register into the operator's home.** A registration at the operator scope, or at the home scope
|
||||
into a home that is the operator's own, is taken by two routes and refused through any other:
|
||||
|
||||
1. **at the controller's terminal** ([ADR 0272](../../02-DECISIONS/0272-the-operator-reaches-the-controller-with-mesh-cli-on-every-node-and-a-node-is-the-terminal-only-where-no-agent-runs-as-its-operator.md)):
|
||||
the module's settings `personal_rules`, `personal_skills`, `personal_commands`, `personal_agents`,
|
||||
`personal_output_styles`, `personal_keybindings`, `personal_settings`, `lessons_accepted` and
|
||||
`lessons_declined`, set with `nox settings set claude-code …`. The module's whole layer is already the
|
||||
terminal's (issue 340);
|
||||
2. **on a warrant** ([ADR 0259](../../02-DECISIONS/0259-an-ask-is-a-message-on-the-bus-any-channel-answers-it-and-the-asker-acts-on-a-warrant.md)):
|
||||
the module uses the `operator-channel` seat as an asker. A register, import or remove tool called for the
|
||||
operator's home acts on nothing: it publishes an approve ask showing the item in full, its digest binding
|
||||
the text, name, kind and scope, and acts once on the warrant for that ask.
|
||||
|
||||
**The tools stay the operator's way in.** While no channel answers approve for the module, such a tool
|
||||
records the request as **pending** in the module's state and answers with the exact line to type,
|
||||
`nox settings set claude-code '<json>'`, the request merged into the current layer so that nothing else
|
||||
changes. The status tool lists pending requests; one is dropped once the layer holds it, or after 7 days.
|
||||
|
||||
Nothing that runs — a hook, a permission rule (`autoMode` included), a tool server, an environment or
|
||||
key-helper setting, a skill or command holding a shell line or the front matter issue 340 refuses — is taken
|
||||
at the operator scope by either route. Issue 340's protection stands: a tool call without a warrant writes
|
||||
nothing into the operator's home. A home-scope name never shadows an operator-scope item of the same kind.
|
||||
|
||||
**A hand edit is a condition, undone on the look that finds it.** At least once a minute, and at once on a
|
||||
change on disk, the module compares the rendered paths with the disk and reads the two watched entries. Any
|
||||
difference is said in its health as a finding on that node, and the controller raises **`agent-home-drift`**,
|
||||
a warning that needs the operator, naming each path. That look acts:
|
||||
|
||||
- a rendered file changed by hand (`settings.json` above all) is written back, the hand version kept in
|
||||
`home-copies` and shown by the status tool; only a preference key the program changed is taken over;
|
||||
- a rendered file missing is rendered again;
|
||||
- a file nobody rendered under `rules/`, `skills/`, `commands/`, `agents/`, `hooks/` or `output-styles/` is,
|
||||
**before the node's first adoption** (the imports below, recorded once per node), left and reported for the
|
||||
operator to import or remove (ADR 0243); **after it**, moved into `home-copies`, restorable, and raised;
|
||||
- an entry under neither list, a key other than `permissions` in `settings.local.json`, and a marketplace or
|
||||
plugin in `plugins/` other than the vendor's and `nox-mesh` are raised, naming each, and left.
|
||||
|
||||
The condition clears on the first clean look; the first-node gate never reads it as a fault of a build
|
||||
([ADR 0254](../../02-DECISIONS/0254-a-wait-for-a-persons-new-login-is-not-a-gates-failure-and-each-module-of-a-send-keeps-its-own-pass.md)'s
|
||||
exemption). **The guard** (§3) refuses, naming the module's tool and saying a rule the agent wants is a
|
||||
lesson: any shell command or file edit that writes, appends to, copies or moves into, edits in place or
|
||||
removes a rendered path, `settings.local.json` or a `.json` file under `plugins/`; and `claude plugin
|
||||
install`, `claude plugin marketplace add` and their in-session forms. It does not watch the rest of the
|
||||
working state. The operator's override (ADR 0245 §5) applies.
|
||||
|
||||
**The first registrations** are imports, the node's first adoption: the operator's two rule files on the
|
||||
laptop as the first personal rules; the laptop's skills and the workstation's subagent imported or removed on
|
||||
the operator's word; the preference keys of the laptop's `settings.json` as the operator's settings, and its
|
||||
`autoMode` key moved to the managed settings at the terminal; `.hal-binding.json` and the credentials copy
|
||||
removed on the operator's word; the retired `cleanup` skill and the three stale instruction files found on
|
||||
four nodes on 2026-10-09 removed with copies kept. Each is a condition until imported or removed.
|
||||
|
||||
**Lessons.** The module keeps a `lessons` register in its state: per lesson, when and on which node it was
|
||||
proposed, the session where known, the **trigger** (the operator's words), the **cause** (what the agent did),
|
||||
the **rule** (a personal rule in the operator's second person), an optional **before** (a tool name, command
|
||||
prefix or path pattern), and its state — `proposed`, `accepted` with who, how and when, or `declined` with the
|
||||
reason. The mesh's instruction section tells every agent to record a lesson, in the same turn, when the
|
||||
operator expresses frustration or corrects a way of working, after first working out why, and to say in one
|
||||
line that it did. A correction goes to the register, not to the agent's own per-project memory, which stays its
|
||||
notebook for facts about a project. **The first lessons are the agent's existing corrections**: once the
|
||||
register exists on a node, the agent there proposes, unasked, every note in its own memory that records a
|
||||
correction the operator gave, and says so in one line.
|
||||
|
||||
- **A proposed lesson is not a rule.** It is rendered nowhere. The module shows it to the operator for a yes:
|
||||
an approve ask with Accept and Decline, or, while no channel answers approve, the list tool shows it and the
|
||||
operator accepts or declines at the terminal (`lessons_accepted`, `lessons_declined`). A proposal expires
|
||||
after 30 days, declined with that reason.
|
||||
- **An accepted lesson is a personal rule**, registered at the operator scope with the lesson as its source,
|
||||
rendered into every agent's home, and named in the home instruction file as accepted from that lesson.
|
||||
- **A declined lesson stays**, with its reason; a new proposal whose rule matches a declined one is refused
|
||||
with that reason, and the agent is told to raise it with the operator instead.
|
||||
- **The agent is reminded.** The plugin's session-start hook (the module's own binary, beside the guard)
|
||||
prints at session start the rules accepted from lessons in the last 30 days and those whose `before`
|
||||
matches the working directory or repository. Before a tool runs, the guard prints the rule of every
|
||||
accepted lesson whose `before` matches the tool, command or path — a reminder, once per session per rule,
|
||||
never a refusal.
|
||||
- **The register is readable by any agent on any node** through the list tool, each state marked, so a
|
||||
proposal is never read as a rule. Recording a proposal needs no yes.
|
||||
|
||||
**The tools of this section**: `claude_code_personal_rule_register`, `_list`, `_unregister`;
|
||||
`claude_code_personal_settings_get`, `_set`; the existing register tools with `scope: operator` for skills,
|
||||
commands and subagents; `claude_code_config_import` taken again, at the operator scope, by the two routes;
|
||||
`claude_code_config_status` extended with the drift findings, the unknown entries, the watched entries
|
||||
and the pending requests;
|
||||
`claude_code_lesson_register`, `_list`, `_accept`, `_decline`. Every tool that writes into the operator's
|
||||
home goes through the two routes above.
|
||||
|
||||
## How it is checked
|
||||
|
||||
| Check | Defends |
|
||||
@@ -373,8 +507,18 @@ a login).
|
||||
| a switch asked of the seat through the mesh MCP server 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 module's render test: a registered skill, subagent, command, hook and output style land in the `nox-mesh` plugin; a tool server, a setting and an instruction section in their managed files; for one machine of two, a mesh item on both, a node item on one, a home item only in that home; a setting naming the marketplace keys is overridden | ADR 0216 |
|
||||
| the module's test: a home name the person already uses is refused, unregistering removes only the placed path, and an item above 256 KiB is refused | ADR 0216, ADR 0182 |
|
||||
| the module's render test: a registered skill, subagent, command and output style land in the `nox-mesh` plugin (a hook and a setting are refused since issue 340, and the terminal's land from the module's settings); a tool server and an instruction section in their managed files; for one machine of two, a mesh item on both, a node item on one, a home item only in that home; a setting naming the marketplace keys is overridden | ADR 0216, issue 340 |
|
||||
| the module's test: a name registered twice is refused, unregistering removes only the rendered path, and an item above 256 KiB is refused | ADR 0216, ADR 0182 |
|
||||
| the module's render test over a seeded home: every configuration path of §10 matches the registrations byte for byte, every excluded entry is byte-identical before and after, an unknown entry is reported; a program-written key of `settings.json` is taken over into the operator's settings, a key outside the list is drift | ADR 0273 §1 |
|
||||
| the module's render test for one machine of three: a personal rule lands under `rules/` and in the home instruction file in the operator's own home on two and in the agent account's home on the third; a hook, a permission rule, a tool server and an item holding a shell line are refused at the operator scope | ADR 0273 §2 |
|
||||
| the module's tests through its real tool dispatch: a register, import or remove at the operator scope, or for an operator's home, publishes an ask and writes nothing; with a warrant for that ask it acts once; another ask's warrant, or a replayed one, is refused; while no channel answers approve the ask is refused naming the terminal's line | ADR 0273 §3, issue 340 |
|
||||
| the module's tests with a clock: a rendered file changed by hand (`settings.json` among them) is written back on the look that finds it, within a minute, its hand version kept and shown; a `hooks`, `permissions`, tool-server or `autoMode` key added on disk is written back and never taken over; a missing file is rendered again on that look; before a node's first adoption an unrendered file under `rules/`, `skills/`, `commands/`, `agents/`, `hooks/`, `output-styles/` is left and reported, after it the same file is moved into `home-copies`, restorable, and raised; an entry under neither list, `.hal-binding.json` and a `.credentials.json.*` copy are raised and left; a key other than `permissions` in `settings.local.json` and a marketplace or plugin other than the vendor's and `nox-mesh` are raised; the condition clears on a clean look; the controller's test that `agent-home-drift` is a warning the first-node gate never reads as a fault | ADR 0273 §1, §4 |
|
||||
| the module's guard tests: a write, append, copy, move, in-place edit and removal under each rendered path, and aimed at `settings.local.json` and a `.json` under `plugins/`, refused naming the tool; `claude plugin install`, `claude plugin marketplace add` and their in-session forms refused; a write under `projects/` passes | ADR 0273 §4 |
|
||||
| the module's test: a home-scope registration of a name and kind already registered at the operator scope is refused naming it, and the render holds the operator's item | ADR 0273 §3 |
|
||||
| the module's test: a tool called for the operator's home writes nothing there, records one pending request and answers a `nox settings set claude-code '…'` line that, applied to the current layer, adds that item and changes no other key; the request is dropped once the layer holds it, and after 7 days | ADR 0273 §3 |
|
||||
| the module's tests over the lessons register: register, list, accept, decline, expiry; only accepted lessons reach `rules/`; a proposal matching a declined rule is refused with the reason; the session-start hook prints the recent and matching rules; the guard prints a matching rule once per session and refuses nothing for it | ADR 0273 §5 |
|
||||
| the module's render test: the rendered managed instruction file's `working-on-the-mesh` section holds the paragraph on when a lesson is recorded, that a correction goes to the register and not to the agent's memory, and that existing corrections are proposed first; the test fails when the paragraph is taken out | ADR 0273 §5 |
|
||||
| live: on the laptop, `~/.claude` holds the rendered configuration and the working state and nothing else; a rule written by hand into `rules/` raises `agent-home-drift` within a minute; a rule set at the terminal is in a new session on every node; a correction given in a session is in the lessons list in that turn, accepted at the terminal, and named at the start of the next session on another node | ADR 0273, the exit of WP7–WP9 |
|
||||
| live: a skill registered at the mesh scope is offered as `nox-mesh:<name>` in a new session on each machine | ADR 0216 |
|
||||
| the module's test: the "instead of" table is generated from the controller's answers, ordered and cut; the guard refuses ssh to a mesh machine by name, domain, internal name, address, alias and jump, and the local work-arounds for a mesh name, naming the tool — and lets git to the forge, ssh beyond the mesh and a command merely mentioning ssh through; the override is honoured only from the session's start and only when recorded | ADR 0245 |
|
||||
| the mesh MCP server's provision resolves by co-location; a machine without the mesh MCP server refuses the module by name | ADR 0027, ADR 0152 |
|
||||
@@ -392,6 +536,8 @@ a login).
|
||||
directory in it, bound to its own licence through the manager (to-be 39 §5); that is for when workers
|
||||
exist, and nothing here changes for it.
|
||||
- **The package repository seat** (§7).
|
||||
- **A per-project lesson, lessons for an agent the mesh runs, and a surface of the operator's own** that can
|
||||
tell the operator from an agent, which would be a third route into the operator's home (ADR 0273).
|
||||
- **How the module's tools are run** is decided: the node's tool runner, host-side
|
||||
([ADR 0175](../../02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md)).
|
||||
The managed files and the credential write are tools of this module that runtime serves. Until the
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: implemented
|
||||
status: in-progress
|
||||
code: [mesh-catalog modules/claude-code, mesh-catalog modules/claude-licence-manager]
|
||||
updated: 2026-10-04
|
||||
updated: 2026-10-09
|
||||
decisions:
|
||||
- 02-DECISIONS/0273-the-agents-home-is-the-modules-and-the-operators-personal-rules-and-lessons-are-registered-through-it.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/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
|
||||
@@ -194,6 +195,131 @@ designs 36 and 39 set to `implemented` with the as-is written
|
||||
**Proof.** `claude_code_status` answers on every node; the refusal's notification arrived; the catalogue
|
||||
has no module built on the old placement.
|
||||
|
||||
## The home, personal rules and lessons — WP7 to WP10
|
||||
|
||||
*Added 2026-10-09 by [ADR 0273](../../02-DECISIONS/0273-the-agents-home-is-the-modules-and-the-operators-personal-rules-and-lessons-are-registered-through-it.md);
|
||||
design 36 §10 is the authority on what is built.* WP0–WP6 ran and stay as recorded above; these four packages
|
||||
open the document again. All of it is in `mesh-catalog`, in the `claude-code` module, except where a line
|
||||
says otherwise. The controller needs nothing new: the module's settings layer is already the terminal's
|
||||
(issue 340), and the asker's client is the shared library's (ADR 0259 §10 step 3).
|
||||
|
||||
```
|
||||
WP7 the home is the module's: render, drift, guard (mesh-catalog) ── two days
|
||||
WP8 personal rules and the operator's items, live (the live mesh) ── half a day, the operator's
|
||||
WP9 lessons: register, ask, reminders, the instruction section (mesh-catalog) ── two days
|
||||
WP10 the rest of the nodes, and the as-is (the live mesh) ── half a day
|
||||
```
|
||||
|
||||
### WP7 — The home is the module's
|
||||
|
||||
*mesh-catalog. Two days.*
|
||||
|
||||
1. **The manifest.** The module `uses` the `operator-channel` seat, as an asker. New settings, each a list
|
||||
of items whose text is a list of lines (a setting holds no line break, issue 339): `personal_rules`,
|
||||
`personal_skills`, `personal_commands`, `personal_agents`, `personal_output_styles`,
|
||||
`personal_keybindings`, `personal_settings` (preference keys only; `autoMode`, `hooks`, `permissions`,
|
||||
tool-server, `env`, `apiKeyHelper` and `statusLine` refused), `lessons_accepted`, `lessons_declined`. New
|
||||
state buckets: `personal` (the operator-scope registrations and the lessons accepted into them), `lessons`
|
||||
(WP9), `home-copies` (the hand versions, the files moved out after adoption, and the removal copies ADR 0243
|
||||
already keeps; never a credentials copy), `pending` (requests waiting for the terminal), and a per-node
|
||||
record of the first adoption. The documentation gains the lists of design 36 §10: the rendered paths, the
|
||||
excluded working state, the two watched entries, and the entries named as neither.
|
||||
2. **The renderer.** From the operator-scope registrations and the terminal's settings, the home instruction
|
||||
file, `rules/`, `skills/`, `commands/`, `agents/`, `output-styles/`, an empty `hooks/`, `keybindings.json`
|
||||
and `settings.json`, written as the
|
||||
agent's account (through §9's helper where that is an agent account), only when content changed, and a
|
||||
record of what was rendered. The program-written preference keys of `settings.json` taken over into
|
||||
`personal_settings` when the program changes them; every other key written back.
|
||||
3. **Drift.** A comparison at least once a minute and at once on a file watch, over the rendered paths, the
|
||||
two watched entries and the directory's top level; findings in the module's health statement (ADR 0240)
|
||||
naming each path and what differs. **The look that finds a difference acts**: a changed rendered file is
|
||||
written back on that look, its hand version kept in `home-copies` first (a key that decides what runs is
|
||||
never taken over); a missing one rendered again; a file nobody rendered under `rules/`, `skills/`,
|
||||
`commands/`, `agents/`, `hooks/` or `output-styles/` left and reported before the node's first adoption,
|
||||
and after it moved into `home-copies`, restorable, and raised; an entry under neither list, a
|
||||
`settings.local.json` key other than `permissions`, and a marketplace or plugin other than the vendor's and
|
||||
`nox-mesh` raised and left. The controller:
|
||||
the condition kind `agent-home-drift`, a warning that needs the operator, exempt at the first-node gate as
|
||||
the relogin and directory-used-as-found kinds are (one small change in `mesh-controller`).
|
||||
4. **The two routes.** Every register, import and remove tool at the operator scope or for an operator's
|
||||
home publishes an approve ask through the asker's client, with the item in full and a digest over text,
|
||||
name, kind and scope, and acts once on the warrant; while the router carries no channel that answers
|
||||
approve for this module, it records the request in `pending` and answers the exact
|
||||
`nox settings set claude-code '<json>'` line, merged into the current layer so nothing else changes. A
|
||||
home-scope name an operator-scope item of the same kind uses is refused. The home scope into an agent
|
||||
account's own home stays as issue 340 built it.
|
||||
5. **The guard.** A refusal of any shell command or file edit whose target is under a rendered path, is
|
||||
`settings.local.json`, or is a `.json` file under `plugins/` (a redirect, append, copy, move, in-place
|
||||
edit, removal), and of `claude plugin install`, `claude plugin marketplace add` and their in-session forms,
|
||||
naming the tool; nothing for the rest of the working state; the override of ADR 0245 §5.
|
||||
6. **The tools.** `claude_code_personal_rule_register`, `_list`, `_unregister`;
|
||||
`claude_code_personal_settings_get`, `_set`; `scope: operator` on the skill, command and agent register
|
||||
tools; `claude_code_config_import` taken again at the operator scope by the two routes;
|
||||
`claude_code_config_status` extended with the drift findings, the entries the module knows under
|
||||
neither list, the watched entries and the pending requests.
|
||||
|
||||
**Proof, before anything runs live.** The module's tests of design 36 "How it is checked" for ADR 0273 §1,
|
||||
§2, §3 and §4, each failing on the commit before. The catalogue's checks pass.
|
||||
|
||||
### WP8 — Personal rules and the operator's items, live
|
||||
|
||||
*The live mesh. Half a day, and it is the operator's.* One workstation first, as every package here.
|
||||
|
||||
1. The module with WP7 reaches the laptop, where no adoption is recorded yet. `agent-home-drift` is raised
|
||||
there, naming the two rules, the five skills, `settings.json` (its keys differ from an empty registration),
|
||||
`.hal-binding.json` and `.credentials.json.testbak` as entries under neither list, and nothing else.
|
||||
2. At the terminal the operator sets `personal_rules` with the two rules, `personal_settings` with the five
|
||||
preference keys, `managed_settings` (for the laptop or the mesh) with the `autoMode` key, which is a
|
||||
permission rule and cannot be imported at the operator scope, and `personal_skills` with the skills they
|
||||
keep; removes the rest, `.hal-binding.json` and the credentials copy with the removal tool (no copy kept of
|
||||
the credentials copy). The operator then records the laptop's first adoption. The condition clears.
|
||||
3. The module reaches the other three nodes. On each, the condition names the retired `cleanup` skill and
|
||||
the instruction file (and the workstation's subagent); the operator removes or imports each, once, and
|
||||
records that node's first adoption.
|
||||
|
||||
**Proof.** On every node `~/.claude` holds the rendered configuration and the program's working state and
|
||||
nothing else; a new session on the workstation has the laptop's two rules in its context; a rule written by
|
||||
hand into `rules/` on the laptop is moved into the copies and raised within a minute; a hand edit of
|
||||
`settings.json` is written back within a minute with the hand version shown by the status tool.
|
||||
|
||||
### WP9 — Lessons
|
||||
|
||||
*mesh-catalog. Two days.*
|
||||
|
||||
1. **The register**: the `lessons` bucket; `claude_code_lesson_register` (trigger, cause, rule, optional
|
||||
before; refused when its rule matches a declined lesson's, with the reason), `_list` (the three states
|
||||
marked apart), `_accept` and `_decline` (the two routes of WP7 step 4, since they write into the
|
||||
operator's home); expiry of a proposal after 30 days; `lessons_accepted` and `lessons_declined` read
|
||||
from the terminal's settings.
|
||||
2. **Accepted lessons as personal rules**: registered at the operator scope with the lesson as their source;
|
||||
the home instruction file names each as accepted from its lesson.
|
||||
3. **The reminders**: a session-start hook in the `nox-mesh` plugin, the module's own binary, printing the
|
||||
rules accepted in the last 30 days and those whose `before` matches the working directory or repository;
|
||||
the guard printing the rule of a matching accepted lesson once per session before the tool runs,
|
||||
refusing nothing for it.
|
||||
4. **The instruction section**: the mesh's `working-on-the-mesh` section gains the paragraph of design 36
|
||||
§10 on when an agent records a lesson, that a correction goes to the register and not to the agent's own
|
||||
memory, and that its existing corrections are proposed first; a render test holds the section to it; and the `talking-to-the-operator` section one line saying the agent
|
||||
says in one line that it did. Both are set at the terminal, as every mesh-wide section is since issue 340.
|
||||
|
||||
**Proof, before anything runs live.** The module's tests for ADR 0273 §5 in design 36 "How it is checked".
|
||||
|
||||
5. **The first lessons**: on the laptop, once the register exists, the agent proposes, unasked, every note in
|
||||
its memory that records a correction the operator gave (at least 9 on 2026-10-09).
|
||||
|
||||
**Proof, live, on one workstation.** The laptop's memory corrections are in `claude_code_lesson_list` as
|
||||
proposals. In a session, the operator corrects a way of working; the lesson is in
|
||||
`claude_code_lesson_list` in that turn; the operator accepts it at the terminal; the next session on another
|
||||
node names it at start; a tool whose name matches its `before` is preceded by the rule once.
|
||||
|
||||
### WP10 — The rest of the nodes, and the as-is
|
||||
|
||||
*The live mesh. Half a day.* WP9 on every node; the as-is of the agent
|
||||
([00-as-is/15](../00-as-is/15-the-agent-and-its-licences.md)) rewritten to say what the home holds and how
|
||||
a lesson travels; design 36 and this document set to `implemented` ([playbook 02](../../00-META/process/02-graduation.md)).
|
||||
|
||||
**Proof.** The live line of ADR 0273's "How it is checked", on every node.
|
||||
|
||||
## What is deliberately not here
|
||||
|
||||
- **The package repository seat** for a distribution that does not carry the agent's package (design 36
|
||||
@@ -213,4 +339,5 @@ Each package's proof is run when the package is finished and its line here gains
|
||||
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.
|
||||
the as-is written. WP6's did, on 2026-10-04. ADR 0273 opened WP7–WP10 on 2026-10-09, so design 36 and this
|
||||
document are `in-progress` again until WP10's proof runs.
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ status: located
|
||||
opened: 2026-10-09
|
||||
located-in: [mesh-catalog (modules/claude-code), mesh-controller (internal/catalogue/terminal_keys.go)]
|
||||
fixed-by:
|
||||
amended-design:
|
||||
amended-design: 03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md
|
||||
---
|
||||
|
||||
# 340. An agent can plant a hook in every agent session on the mesh
|
||||
|
||||
Reference in New Issue
Block a user