Merge remote-tracking branch 'origin/main' into issues/356-the-operators-command-ran-a-stale-engine-and-a-hand-over-had-no-channel
This commit is contained in:
+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
|
||||
|
||||
@@ -182,6 +182,18 @@ work:
|
||||
descriptions call a build request an *ask*;
|
||||
- the nftables module's tool `firewall_rules`, which serves the packet filter.
|
||||
|
||||
> **Progressive insight — 2026-10-09.** This record deferred the code renames in four places and named no
|
||||
> issue, owner or date for them: this section, §2 ("listed in §6 for a later rename and is not renamed here"),
|
||||
> §3 ("Code that still carries an old name is named on an *Identifier until renamed* line") and §4's table
|
||||
> ("`mesh-host` an identifier until the code rename"). Two days later the operator believed them done, and
|
||||
> nothing in this record said otherwise. They are tracked by
|
||||
> [issue 354](../04-ISSUES/354-a-decided-rename-was-never-scheduled-and-the-operator-believed-it-live/00-report.md),
|
||||
> which holds the inventory of what carries the old names, the risk of renaming the paths of a self-updating
|
||||
> daemon on live nodes, and the staged plan; each stage is owned by the repository it names, and the
|
||||
> glossary's *Identifier until renamed* lines point at the issue. The decision stands. From this date a
|
||||
> record that defers work names the issue that tracks it, with an owner, or the deferral is not accepted
|
||||
> (playbooks 02 and 04; `cycle.py` checks it).
|
||||
|
||||
## Options rejected
|
||||
|
||||
- **Machine as the one word, node only as an identifier** — the research's proposal, and what the newest
|
||||
|
||||
+5
@@ -154,6 +154,11 @@ The table of ADR 0239 decision 2 gains these rows:
|
||||
held merge of it stays held for a person, as before this record. Reading "every machine running it" from
|
||||
the first machines when they are all the machines would close it, and is left for a later change.
|
||||
|
||||
> **Progressive insight — 2026-10-09.** The one-machine case above was left for a later change with no
|
||||
> issue, owner or date, which is how ADR 0244's deferred renames went unnoticed (issue 354). It is tracked by
|
||||
> [issue 355](../04-ISSUES/355-a-held-delivery-of-a-module-that-runs-on-one-machine-only-is-never-covered/00-report.md),
|
||||
> owned by mesh-delivery in the controller's repository. The decision stands.
|
||||
|
||||
> **Progressive insight — 2026-10-09.** *The gap was wider than one machine.* A build the mesh held back for
|
||||
> a person and sent on the operator's word travels by a backlog walk, whose record gives one step per
|
||||
> machine and no "every machine running it" or "the rest running it"; and that walk's last record keeps only
|
||||
|
||||
+425
@@ -0,0 +1,425 @@
|
||||
---
|
||||
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.
|
||||
|
||||
> **Progressive insight — 2026-10-09.** The four things this section leaves undecided — clearing old copies
|
||||
> of hand versions, a per-project lesson, lessons for an agent the mesh runs, and a channel of the operator's
|
||||
> own — were left with no issue, owner or date, which is how ADR 0244's deferred renames went unnoticed
|
||||
> (issue 354). They are tracked by
|
||||
> [issue 357](../04-ISSUES/357-what-adr-0273-left-undecided-has-no-owner/00-report.md). The decision stands.
|
||||
|
||||
## 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.
|
||||
Reference in New Issue
Block a user