Merge remote-tracking branch 'origin/main' into issues/356-the-operators-command-ran-a-stale-engine-and-a-hand-over-had-no-channel
mesh/merge-gate pass: the change touches no module of the mesh's graph
mesh/repo-check fail: its merge-check.sh failed: words: 2 problem(s)
mesh/delivery superseded: a newer head of the same pull request

This commit is contained in:
jochen
2026-10-09 19:46:03 +02:00
19 changed files with 1370 additions and 27 deletions
+14 -1
View File
@@ -62,7 +62,20 @@ number is how a record is cited, and `main` lags every open pull request, so two
allocate the same one). And a core issue — one opened from 2026-10-07 whose `located-in` names a core
repository — resolves only with `replay:` (an id in mesh-lab's replays register) or `replay-none:` saying
why none is possible ([ADR 0237](../../02-DECISIONS/0237-a-change-is-judged-against-the-mesh-that-runs-before-it-merges-on-the-build-seat.md));
it failed on a resolved core issue carrying neither before it passed. `python3 00-META/checks/cycle.py`
it failed on a resolved core issue carrying neither before it passed. And an accepted record that puts work
off — "until renamed", "left for later", "not decided here", "postponed", "future work", "deferred", "a later
change" — names the issue that tracks it, as tracking it ("tracked by issue N", or "issue N tracks"), in that
paragraph or list item, or in a dated progressive insight that speaks about it: one in the same section, or one
that quotes it. An issue cited for another reason tracks nothing, and one insight does not excuse every deferral
in a record. A rejected option is not work put off and is not held, judged by its `##` section, so an option under a
`###` subheading of "Considered Options" is skipped too
([issue 354](../../04-ISSUES/354-a-decided-rename-was-never-scheduled-and-the-operator-believed-it-live/00-report.md):
ADR 0244 deferred the code renames with no issue, and two days later the operator believed them done). Held to
it: a record dated from 2026-10-09, and an older one corrected under the rule. Measured from ADR 0244's date
it found deferrals without an issue in ADR 0241, 0244 and 0270; held from 2026-10-09 it failed on 0270 and 0273
(whose "not decided here" sits beside a citation of issue 340 that tracks none of it), and on 0244's four
deferral places once its insight brought it under the rule — on three of them while the insight, in §6, named
the issue without quoting them — before each named its issue (355, 357, 354). `python3 00-META/checks/cycle.py`
## words.py
+101
View File
@@ -56,6 +56,75 @@ CORE = re.compile(r"\b(mesh-controller|mesh-host|mesh-tools|mesh-sdk|node-tools|
REPLAYS_FROM = "2026-10-07"
REPLAY_ID = re.compile(r"^R\d+\b")
# A decision that defers work names the issue that tracks it (issue 354). ADR 0244 left the code
# renames of the node-engine and the tool runner "for a later rename", with no issue, owner or date;
# two days later the operator believed them done, and nothing in the record said otherwise. A deferral
# nothing tracks is indistinguishable from work that was done. Held to it: an accepted record dated
# from DEFERRALS_FROM, and an older one corrected under the rule (a progressive insight dated from then
# on): an edit made under the rule is held to it. The phrases are the ones records use to put work off;
# "for later" alone is not one, because records say "never queued for later" and "too narrow for later".
# The tracking issue is named as tracking it, beside the deferral or in a dated insight in the same section
# or quoting it (ISSUE_REF, covers).
DEFERRALS_FROM = "2026-10-09"
DEFERRAL = re.compile(r"(?i)until (?:it is |they are |the code (?:is )?)?renamed|until the code rename|"
r"\ba later rename|not renamed here|identifier to rename|"
r"left (?:for|to) (?:a )?later|not decided here|\bpostponed\b|future work|"
r"\bdeferred\b|later work|not built here|\ba later change")
# The issue that tracks a deferral is named as tracking it -- "tracked by issue N", or "issue N tracks" --
# because a record cites issues for many reasons, and an issue cited as context beside a deferral (ADR 0273
# names issue 340 in the very sentence that leaves a channel undecided) tracks nothing.
ISSUE_REF = re.compile(r"(?i)tracked (?:by|in|as)\s+\[?issue (\d{3})|\[?issue (\d{3})\]?(?:\([^)]*\))?\s+tracks\b")
INSIGHT = re.compile(r"\*\*Progressive insights?[ \t]*[—–-][ \t]*(\d{4}-\d{2}-\d{2})")
def paragraphs(path):
"""A record's body as (first line number, section heading, chapter, text) paragraphs: frontmatter and fenced
code left out, a block quote's markers stripped, so a quoted insight reads as the paragraph it is. The
section is the nearest heading above the paragraph; a heading is its own paragraph and its own section.
The chapter is the nearest `#` or `##` heading, which a `###` subheading inside it does not change."""
text = open(path, encoding="utf-8").read()
m = re.match(r"^---\n.*?\n---\n?", text, re.S)
body, first = (text[m.end():], text[: m.end()].count("\n") + 1) if m else (text, 1)
body = re.sub(r"(?ms)^\s*```.*?^\s*```", lambda x: re.sub(r"[^\n]", " ", x.group(0)), body)
out, line, section, chapter = [], first, "", ""
for chunk in body.split("\n\n"):
stripped = "\n".join(re.sub(r"^\s*>\s?", "", l) for l in chunk.split("\n")).strip()
if stripped:
heading = re.match(r"^#{1,6}\s+(.*)", stripped)
if heading:
section = heading.group(1).strip()
if len(stripped) - len(stripped.lstrip("#")) <= 2:
chapter = section
# A list's top-level items are units of their own: a Consequences list is one paragraph, and an
# issue named in one item tracks nothing in the next.
items = re.split(r"\n(?=[-*] |\d+\. )", stripped)
at = line + chunk[: len(chunk) - len(chunk.lstrip())].count("\n")
for item in items:
out.append((at, section, chapter, item))
at += item.count("\n") + 1
line += chunk.count("\n") + 2
return out
QUOTE = re.compile(r"\"([^\"]{12,})\"|\u201c([^\u201d]{12,})\u201d")
def flat(text):
"""Text compared for a quotation: markup and case dropped, whitespace collapsed."""
return re.sub(r"\s+", " ", re.sub(r"[*`_]", "", text)).strip().lower()
def covers(insight_section, insight_text, section, text):
"""An insight speaks about a deferral paragraph when it stands in the same section, or quotes it."""
if insight_section == section:
return True
body = flat(text)
return any(flat(a or b) in body for a, b in QUOTE.findall(insight_text))
def issue_refs(text, issues):
return {n for a, b in ISSUE_REF.findall(text) for n in (a, b) if n and n in issues}
def rel(path):
return os.path.relpath(path, ROOT)
@@ -233,6 +302,38 @@ def main():
bad(path, "an accepted decision nothing in the cycle cites -- give it a home in a "
"design doc's decisions:, a research became:, an issue, or 00-META")
# ---- deferrals -------------------------------------------------------------------
# A paragraph or list item that puts work off names the issue that tracks it, or a dated
# progressive insight that speaks about it (its section, or a quotation of it) does. An issue that has
# since resolved still counts: the deferred work landed, which is what the pointer was for.
for name, (path, status) in records.items():
front = frontmatter(path) or {}
decided = str(front.get("date") or "")
if status != "accepted":
continue
paras = paragraphs(path)
insights = [(d, sec, text) for line, sec, _, text in paras for d in INSIGHT.findall(text)]
held = decided >= DEFERRALS_FROM or any(d >= DEFERRALS_FROM for d, _, _ in insights)
if not held:
continue
# A dated insight naming an issue covers the deferrals it speaks about -- those in its own section,
# or ones it quotes -- and no other: one insight must not excuse every deferral in the record.
tracking = [(sec, text) for d, sec, text in insights if issue_refs(text, seen)]
for line, section, chapter, text in paras:
m = DEFERRAL.search(text)
# A rejected option is a decision not to do it, not work put off -- judged by its chapter, so an
# option under a `###` subheading of "## Considered Options" is skipped as well.
if re.match(r"(?i)(options? rejected|considered options|rejected)", chapter):
continue
if not m or issue_refs(text, seen):
continue
if any(covers(isec, itext, section, text) for isec, itext in tracking):
continue
bad(path, "line %d defers work (%r) and names no issue that tracks it -- a deferral nothing "
"tracks reads as done (issue 354). Name the issue beside it, with its owner, or in a "
"dated progressive insight that says what it tracks"
% (line + text.count("\n", 0, m.start()), m.group(0)))
checked = (
len(glob.glob(os.path.join(ROOT, "03-DESIGN", "0*", "*.md")))
+ len(glob.glob(os.path.join(ROOT, "04-ISSUES", "*", "00-report.md")))
+13 -2
View File
@@ -135,14 +135,16 @@ every other domain has something to run on. **Recorded by** the controller and t
in the tools' descriptions it is also an SSH server's or a container runtime's own word, so a
description that means the node-engine by it is found by review.
*Not:* ~~host agent~~, ~~the host~~ (hq), ~~node host~~
*Identifier until renamed:* `mesh-host` — the repository, its binary and its service unit
*Identifier until renamed:* `mesh-host` — the repository, its binary and its service unit; the rename is
[issue 354](../04-ISSUES/354-a-decided-rename-was-never-scheduled-and-the-operator-believed-it-live/00-report.md)
- **tool runner** — the one program on each node that loads every assigned module's tools bundle and
serves every tool and every held seat's verb on the subjects the memberships issue; the node-engine
supervises it as a process beside the services it runs, never a container
([ADR 0175](../02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md),
which called it "node tools"). Its mode on loopback is the mesh MCP server.
*Not:* ~~node tools~~, ~~tool runtime~~ (hq)
*Identifier until renamed:* `node-tools` — the module, its unit and its bus account
*Identifier until renamed:* `node-tools` — the module, its unit and its bus account; the rename is
[issue 354](../04-ISSUES/354-a-decided-rename-was-never-scheduled-and-the-operator-believed-it-live/00-report.md)
- **foundation** — the store and the bus, raised at genesis before any module system exists. The
foundation is not a third thing beside the store and the bus — it *is* those two, named together.
*Not:* ~~substrate~~
@@ -499,6 +501,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
+7
View File
@@ -13,6 +13,13 @@
2. **Record the decision.** Write a record in [`02-DECISIONS/`](../../02-DECISIONS/) taking the next free
number. Format and rules are in [`02-DECISIONS/README.md`](../../02-DECISIONS/README.md). State evidence,
not assertion, and record the options that were rejected — that is the half worth keeping.
**A record that puts work off** — a rename, a check, a case it does not handle — names the issue that
tracks it and the owner who will do it, in the sentence that defers it ("tracked by issue N"), or the
deferral is not accepted:
a deferral nothing tracks reads as done
([issue 354](../../04-ISSUES/354-a-decided-rename-was-never-scheduled-and-the-operator-believed-it-live/00-report.md),
ADR 0244's code renames). [`cycle.py`](../checks/cycle.py) holds every accepted record dated from
2026-10-09, and an older one corrected under the rule, to it.
3. **Write the design.** Create the document under `03-DESIGN/01-to-be/` with frontmatter:
```yaml
+4
View File
@@ -24,6 +24,10 @@
## Rules
- Every merge is a human checkpoint, without exception.
- What the handoff leaves for later is an issue with an owner, named in the design or the decision that
leaves it — never a sentence saying "later". The design's `code:` names who owns what is built; the issue
names who owns what is not. [`cycle.py`](../checks/cycle.py) checks the decision record
([issue 354](../../04-ISSUES/354-a-decided-rename-was-never-scheduled-and-the-operator-believed-it-live/00-report.md)).
- Never open a pull request unprompted. A permissions list saying it is allowed is not a
request.
- Work in an isolated worktree, never a shared checkout. A failed `cd` in a shared checkout
@@ -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.
@@ -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.
@@ -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
@@ -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
@@ -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
@@ -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.
@@ -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.
@@ -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
@@ -0,0 +1,75 @@
---
status: located
opened: 2026-10-09
located-in: [mesh-host (the node-engine: its repository, module, binary, units, launcher and paths), mesh-tools node-tools (the tool runner: its module, unit and bus account), mesh-controller (the names it keys the core on, and the words it shows), mesh-catalog (the registrations, and the modules that name the two), hq (the designs' code lines, repos.md, the glossary's identifier lines)]
fixed-by:
amended-design:
---
# 354. A decided rename was never scheduled, and the operator believed it live
## Symptom
[ADR 0244](../../02-DECISIONS/0244-the-mesh-is-described-in-domains-and-one-word-names-one-thing.md)
(2026-10-07) settled the words: the program on every node is the **node-engine**, and the program that serves
every module's tools on a node is the **tool runner**. It left the code renames out on purpose — "`mesh-host`
an identifier until the code rename", "`node-tools` an identifier until renamed", and its §6 *What is not
renamed here*: "code is renamed by the repositories that own it, each with its own change" — and named no
issue, no owner and no date for them.
On 2026-10-09 the operator took the rename as long implemented and live. It is not. As of that day:
- **The repositories**, at their heads (the node-engine's at `b0d03be`, the controller's at `e6b00e2e`, the
catalogue's at `6ae112d`): the node-engine's repository and module are `mesh-host`; its binary is
`nox-mesh-host`; its service units are `nox-mesh-host.service`, `nox-mesh-host-resume.service` and
`nox-mesh-host-roused.service`; its launcher is `/usr/lib/nox-mesh-host/launch`; it is delivered under
`/usr/lib/nox-mesh-host/versions/<version>` and keeps its state under `/var/lib/mesh-host`. The tool
runner is the module `node-tools`, the unit `node-tools.service` and the bus account `node-tools`.
- **The live mesh**, read through the controller's `node` verb for the laptop at 18:05: `mesh-host` and
`node-tools` stand among its assigned modules, and `node-tools.runtime process node-tools.service` among
what it runs.
- **The conditions.** An open condition on the control-node (messenger not healthy) says that
`mesh-host hand-over` at the machine hands a directory to the mesh; every hand-act record of the day
names its caller as `<the laptop>.node-tools`.
- **The record.** `records_search` for "until the code rename" finds ADR 0244's own table row and nothing
else; for "rename mesh-host", nothing. No issue, design or record tracks the rename.
## Why it is an issue
- **A deferral nothing tracks reads as done.** ADR 0244 is accepted; the glossary says node-engine; the
governing documents were reworded the same day. Everything a reader meets says the new name, and the one
sentence that says the code is not renamed says "later" with nobody's name on it. Two days later the
operator believed it done. This is the rule of `AGENTS.md` — an unenforced rule is indistinguishable from
a wrong one — in its deferral form: a record that puts work off without an issue is indistinguishable
from one whose work landed.
- **It is not one record's slip.** The check this issue adds, run over every accepted record from the
date ADR 0244 was accepted, found deferrals without an issue in ADR 0241 ("a later change requires them
once both holders serve them"), ADR 0270 ("left for a later change"), ADR 0273 ("not decided here", four
items) and ADR 0244 itself. ADR 0270 and 0273 are dated 2026-10-09, are held to the rule, and have
[issue 355](../355-a-held-delivery-of-a-module-that-runs-on-one-machine-only-is-never-covered/00-report.md)
and [issue 357](../357-what-adr-0273-left-undecided-has-no-owner/00-report.md) now; ADR 0241 is dated
before the rule's start and stands as found.
- **Impact.** The operator plans against a mesh that does not exist: in conversation the mesh says
node-engine, while every file, unit, package and condition on a node says `mesh-host`, so a person at a
machine (`systemctl status nox-mesh-host`, `ls /var/lib/mesh-host`) and a person reading this repository
do not describe the same thing. The glossary's *Identifier until renamed* lines tell an agent the name is
temporary and never say when.
## The fix, in two parts
1. **This rename** is tracked here. The inventory of what carries the old names, the risk of renaming the
paths of a self-updating daemon on live nodes, and a staged plan are in
[`01-diagnosis.md`](01-diagnosis.md). Acceptance: **`mesh-cli node show <node>` and every condition say
node-engine; no file, unit or package on a node is named `mesh-host`; the tool runner likewise.**
2. **The rule**, from 2026-10-09: a decision that defers work names the issue that tracks it, with an
owner, or the deferral is not accepted. Playbooks [02](../../00-META/process/02-graduation.md)
(graduation) and [04](../../00-META/process/04-build-handoff.md) (handoff) say it;
[`cycle.py`](../../00-META/checks/cycle.py) checks it: an accepted record dated from 2026-10-09, or an
older one corrected under the rule, whose text puts work off ("until renamed", "left for later", "not
decided here", "postponed", "future work", "deferred", "a later change") names the issue as tracking it
("tracked by issue N"), in that paragraph or list item, or in a dated progressive insight in the same
section or quoting it. ADR 0244 carries the insight that points here.
This resolves when the acceptance sentence holds on every node. It is a core issue
([ADR 0237](../../02-DECISIONS/0237-a-change-is-judged-against-the-mesh-that-runs-before-it-merges-on-the-build-seat.md)):
it resolves with a replay, or with `replay-none:` saying why none is possible.
@@ -0,0 +1,114 @@
# 354. Diagnosis
## 2026-10-09
**The record.** `records_search` for "until the code rename" finds only ADR 0244's table row; for the
old name with "rename", nothing. Issues 142, 194 and 210 carry the old name in their titles and keep it: an issue keeps
its words (ADR 0244 §5).
### What carries the old names
**This repository**, on `main` at `c613cf0`:
| Where | What | How much |
|---|---|---|
| `03-DESIGN/01-to-be/` | `mesh-host` and `node-tools` in prose and in `code:` lines | 24 documents, 97 lines; 19 designs name `mesh-host` in `code:`, which is the repository's name and stays right until the repository is renamed |
| `03-DESIGN/00-as-is/` | `code:` lines | 2 documents |
| `00-META/` | `repos.md` (the table says `mesh-host` exists, tier 0), playbook 07's list of repositories, the glossary's two *Identifier until renamed* lines | 3 documents |
| `02-DECISIONS/`, `01-RESEARCH/`, `04-ISSUES/` | the old names as written | 38, 9 and 135 documents; kept, because a record keeps its words |
**The code**, in the checkouts at the heads the report names:
- **The node-engine's repository, `mesh-host`.** Its manifest: module `mesh-host`, slug `host`, the
artifact's binary `nox-mesh-host`, the resource `launcher` at `/usr/lib/nox-mesh-host/launch`, the
resource `next` (the delivered archive) at `/usr/lib/nox-mesh-host/versions/<version>`. Its packaging:
`nox-mesh-host.service` (whose start command is the launcher), `nox-mesh-host-resume.service`,
`nox-mesh-host-roused.service`, `nox-mesh-host-launch`, `nox-mesh-host-rollback`,
`nox-mesh-host-network.sh` and an OpenRC script. The launcher's environment (`MESH_HOST_STATE_DIR`,
`MESH_HOST_LIBEXEC`, `MESH_HOST_BIN`, and its limits) and its fallback binary `/usr/bin/nox-mesh-host`.
The state directory `/var/lib/mesh-host`: the node's state, the foundation lock, and the launcher's
files (known-good, rolled-back, trial, the start counter). The commands `cmd/mesh-host` (its flag set is
named `mesh-host`) and `cmd/mesh-bootstrap`. The placement guard's `engineModule` (`mesh-host`) and
`engineTrees` (both directories). The words a report says, which conditions quote: `mesh-host hand-over` at
the machine. The Go module path, which the controller's builder check vendors. The
report names a socket under `/run/nox-mesh-host`; I did not find one in the checkout at `b0d03be`, so
the diagnosis in that repository confirms it or strikes it.
- **The tool runner**, in the `mesh-tools` repository under `node-tools/`: the module `node-tools`, the
unit `node-tools.service`, the bus account `node-tools`, the mesh MCP server's description of itself
("reached as <node>.node-tools"), and the caller's address every hand-act record keeps,
`<node>.node-tools`.
- **The controller, `mesh-controller`**: 51 lines naming `mesh-host`, 18 naming `node-tools`. The core
modules the merge gate and the checks key on (`coreModules`: module to repository), the module the probes
key on (`hostModule`), the runtime module (`RuntimeModule`, in the catalogue and the broker packages),
the bus account (`BusNodeTools`), the broker's kind (`KindNodeTools`), the lease witness
(`NodeToolsService`, `ComponentNodeTools`, and a component named `mesh-host`), the seat dependencies, the
reconnects, and the put-back ordering of a send, keyed on the strings `mesh-host` and `mesh-controller`.
The words it shows: `"mesh-host": "the node-engine"` and `"node-tools": "the node tools"` — the second
is a retired word.
- **The catalogue, `mesh-catalog`**: 20 files name one or both — the README, the desktop session code of
five modules, the container runtime's tools and tests, the bus's tools, the service manager's client,
the SSH client's manifest, the lab's tools and the forge's tests. And the registrations: a catalogue row
for `mesh-host` (built from its own repository, ADR 0069) and one for `node-tools` (from `mesh-tools`),
and every node's assignment of both.
The owner of each area is the repository named. For the symptom's words, the whole of the rename, and the
check, this repository.
### The risk
**Renaming the paths of a self-updating daemon on live nodes.** The node-engine delivers itself: its
launcher, placed by the module's own declaration under `/usr/lib/nox-mesh-host`, runs the newest delivered
version under that directory's `versions/` on trial, and goes back to the known-good one recorded under
`/var/lib/mesh-host` when the trial fails
([to-be 45 §8](../../03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md),
[ADR 0227](../../02-DECISIONS/0227-the-core-holds-nine-rules-each-checked-and-is-built-to-them-in-six-phases.md)
rule 8). Its unit is placed when the machine joins, not by the mesh
([to-be 21](../../03-DESIGN/01-to-be/21-the-installation-in-full.md): the installer does not install the
unit). So a build that writes under new paths while the launcher reads the old ones is a build the launcher
cannot find; and a launcher that reads only new paths cannot go back to a known-good version under the old
ones — it halts the node ("this node needs a person"), which on a server is a person at a keyboard. The
module's name is keyed in the controller's core handling (the put-back ordering, the gate's core modules,
the lease witness), so a renamed module is, to a controller that knows one name, a new module beside an old
one. The tool runner has the same shape at a smaller scale: the bus account and the unit are named in the
controller's user list and in every node's assignment.
Hence: **one release in which both names are accepted, on every side, before any release in which the old
one is dropped**, each release walked one node first — the laptop, where a halted node is the operator at a
keyboard — and then the rest. I think no stage should move before the delivery fixes of issues 351 and 352
are live, because a core walk is exactly what those broke; that is a guess from their symptoms, not a
measurement.
### The staged plan
0. **This repository** (this change): the issue; the glossary's identifier lines point here; the rule and
its check.
1. **The words shown**, safe at any time and in any order: the controller's shown words ("the node tools"
becomes the tool runner), the node-engine's report words (the hand-over as the node-engine's command),
the mesh MCP server's description of itself. No path changes.
2. **Both names accepted, for one release.** The controller treats `mesh-host` and the node-engine's new
module name as one core component (core modules, witness, put-back ordering), and `node-tools` and the
tool runner's as another (runtime module, bus account, kind). The node-engine's launcher and upgrade
package read the old and the new library and state directories, and the node-engine moves its state
once, the first time it runs under the new name. Both unit names run the same launcher. The conditions
and the `node` verb say node-engine. The merge gate composes every node green; walked one node first.
3. **The rename proper.** The new module names registered, assigned beside the old on the first node, the
old unassigned there, the acceptance sentence checked on that node, then the rest. The repository
renamed on the forge, with its build source and its Go module path; the packaging renamed. The tool
runner the same: module, unit, bus account — and the caller's address in hand-act records changes with
it, which whoever reads those records must know.
4. **The old names dropped**, one release later: the controller's acceptance of them, the launcher's old
paths, the glossary's two identifier lines (the old names move to *Not:* lines, and `words.py` then
fails on them in prose), the designs' `code:` lines and `repos.md` following the repository.
**Acceptance:** `mesh-cli node show <node>` and every condition say node-engine; no file, unit or package on a
node is named `mesh-host`; the tool runner likewise.
### Open
- **The new identifiers.** The glossary's words are node-engine and tool runner; the `nox-` prefix on the
binary and the units is the packaging's. Whether the binary is `nox-node-engine`, the directories
`/usr/lib/node-engine` and `/var/lib/node-engine`, and the tool runner's module `tool-runner`, is the
operator's call before stage 2 is built.
- **Whether the state directory moves at all.** What is under it is the node's own (the report sequence,
the known-good version): a new name beside a kept path is an option the diagnosis in the node-engine's
repository weighs.
@@ -0,0 +1,40 @@
---
status: open
opened: 2026-10-09
located-in: []
fixed-by:
amended-design:
---
# 355. A held delivery of a module that runs on one machine only is never covered
## Symptom
[ADR 0270](../../02-DECISIONS/0270-a-held-delivery-that-moves-nothing-or-that-a-later-delivered-merge-contains-ends-by-itself-and-a-hold-whose-reason-is-gone-lifts.md)
(2026-10-09) ends a held delivery by itself when a later merge that contains it was delivered to every
machine running its modules — the delivery is *covered*. Its consequences say: "A module that runs on one
machine only is never covered. A walk records 'the rest running it' only after its first machines passed
and the rest were sent. A module on one machine has no rest, so its steps never say it reached every
machine. mesh-delivery itself, on the control node alone, is such a module: a 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."
That later change had no issue, no owner and no date. The check added with
[issue 354](../354-a-decided-rename-was-never-scheduled-and-the-operator-believed-it-live/00-report.md)
found the sentence on the day the record was accepted. This issue is the pointer the record lacked; the
record carries a dated insight naming it.
## Why it is an issue
A held delivery of mesh-delivery — or of any module placed on one machine — that a later merge covered
stays held for a person, which is the hand-work ADR 0270 set out to remove; and nothing told the operator
this case was excluded except one sentence in the record's consequences. A deferral nothing tracks reads as
done (issue 354).
## Open
- Whether a walk whose first machines are all the machines should record that it reached every machine
(the record's own suggestion), or whether *covered* should read the machines running a module from the
placement rather than from the walk's steps.
- The owner is mesh-delivery, in the controller's repository, as the record says; `located-in:` is filled
when the diagnosis confirms where the walk's record is written.
@@ -0,0 +1,41 @@
---
status: open
opened: 2026-10-09
located-in: []
fixed-by:
amended-design:
---
# 357. What ADR 0273 left undecided has no owner
## Symptom
[ADR 0273](../../02-DECISIONS/0273-the-agents-home-is-the-modules-and-the-operators-personal-rules-and-lessons-are-registered-through-it.md)
(2026-10-09: the agent's home is the module's, and the operator's rules and lessons are registered through it)
says in its consequences that four things are not decided there, and names no issue, owner or date for any of
them. The check added with
[issue 354](../354-a-decided-rename-was-never-scheduled-and-the-operator-believed-it-live/00-report.md) found
them on the day the record was accepted. This issue is the pointer the record lacked; the record carries a
dated insight naming it.
1. **Clearing old copies of hand versions.** The agent module keeps the hand version of every file it undoes,
in its state, and "clearing old copies is not decided here, as ADR 0243 left it". Without it the module's
state only grows.
2. **A per-project lesson** — one bound to a repository rather than the operator.
3. **Lessons for an agent the mesh runs**, rather than the operator's own agent.
4. **A channel of the operator's own that can tell the operator from an agent** — what issue 340 calls
"once we build a UI" — which would become a third route for registering into the operator's home.
## Why it is an issue
A deferral nothing tracks reads as done (issue 354). Each of the four is a gap a reader of ADR 0273 can only
find by reading its consequences to the end; the first is also a store that grows without a bound.
## Open
- Whether each item needs a decision record of its own, or whether item 1 is a setting of the agent module
that its owner (the `claude-code` module in the catalogue) can decide in a change.
- Item 4 waits on the operator's channels (ADR 0259) and the controller's terminal; its owner is not known
until the channel is designed.
- `located-in:` is filled when each item's owner is confirmed. The item resolved first is struck through here
with its pull request, and the issue resolves when all four are decided or declined.