From 213987b2cc94699fa2a8ee63e661adc17f949f8b Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 13 Oct 2026 15:57:48 +0200 Subject: [PATCH 1/2] Give ADR 0343 a home in design 36: a session's memory follows the operator (issue 367) --- .../36-the-operators-agent-on-a-machine.md | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md b/03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md index 3043b61f..1c4d0f8f 100644 --- a/03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md +++ b/03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md @@ -2,8 +2,9 @@ layer: to-be status: in-progress code: [mesh-catalog modules/claude-code] -updated: 2026-10-10 +updated: 2026-10-13 decisions: + - "ADR 0343" - "ADR 0274" - "ADR 0273" - "ADR 0266" @@ -402,7 +403,7 @@ the helper of §9 where that is an agent account): "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, +projects and their memory (each project's memory folder is also carried between nodes since ADR 0343; see §12), `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 @@ -520,6 +521,33 @@ A node away for more than the 30 days the router keeps its record, or joining la approval and does not render it; the list says so. The module uses the `operator-channel` seat, which grants it the asker's subjects and its own records there. +## 12. A session's memory follows the operator + +*Added 2026-10-13 by ADR 0343 (issue 367).* + +A session started on another node starts with what the sessions on every other node learned. **The module +carries each project's memory folder**, `~/.claude/projects//memory/`, and nothing else of the home. + +| Step | What happens | +|---|---| +| pick up | the module's own long-running process on each node looks at the memory folders at least every ten seconds; a file whose text differs from the version last carried is put in the module's kept `memory` state, one key per home kind and file: the project, the path, the text, its digest, the digest of the version it replaced, the node and when it changed | +| carry | every node watches the `memory` state (the whole set at start, then each change) and writes an incoming version where its own file is still the version it replaced, or absent; staged and renamed in, never through a symbolic link, the replaced version kept in `memory-copies/` in the module's state directory for 30 days | +| conflict | a file changed on two nodes since the version last carried becomes one file holding both, the newer first, then a line naming each version's node and time, then the older; that file is carried, so every node ends on it, and the operator is told by a warning through the operator channel's `notify`, cleared on the file's next change without a conflict | +| remove | a carried file removed from a memory folder that still exists is put in the state as a removal marker for 30 days; a node still holding the removed version moves it into `memory-copies/`, a node that changed it keeps and carries it again; a missing folder or an absent key removes nothing | +| first carrying | a node with no record of what it carried takes an equal file as agreed and a different one as a conflict, so every version learned before is kept | +| list | `claude_code_memory`: what this node carries, each file's fingerprint and last version, what was not carried and why, open conflicts and copies; never the text | + +**What is not carried**: a symbolic link, a file over 128 KiB, and a file holding a key block, a signed +token, a known token prefix or a password in an address. Each is listed with why. + +**Homes of one kind only.** Memory moves among the homes where the agent runs as the operator's account, +and separately among the homes of an agent account of its own (§9); never from one kind to the other, so a +confined agent's text never reaches a session run as the operator (issue 340). + +**No tool writes memory.** The `memory` state and the folders are written only by the module's own process, +from what the module on another node carried; issue 340's boundary holds. A value put straight into the +state by a holder of a tool runner's credential is not yet told apart (issue 638). + ## How it is checked | Check | Defends | @@ -547,6 +575,8 @@ asker's subjects and its own records there. | live: a proposal for every node shows on the operator's phone with Approve and Decline; after Approve a new session on another node reads it | ADR 0274 | | live: a skill registered at the mesh scope is offered as `nox-mesh:` 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 module's tests over temporary homes and a state in memory, never the live folders: a memory file written on one node appears unchanged on another; only `projects/*/memory/` is carried, and a symbolic link, an oversized file and a key block are not; homes of the two kinds never take each other's memory; a file changed on both ends as one file holding both, newer first, with a warning; a removal moves the other node's unchanged file into `memory-copies/` and a changed one is kept and carried again; a missing folder and an absent key remove nothing; a first carrying loses nothing | ADR 0343 | +| live: a file written in a project's memory folder on the laptop is listed by `claude_code_memory` on the workstation with the same fingerprint within a minute; a file changed on both is kept twice in one file, with a warning on the operator's channel | ADR 0343 | | 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 | | the account the agent runs as: the facts fall back to the operator account; an agent account of its own is judged unable to become root, or raises `agent-can-become-root`; its files are written only through the helper; a session as another account is refused with no override | ADR 0266 | | a new session on the assigned workstation lists the mesh MCP server's five tools under `mesh` (ADR 0195) and answers "which node am I" from the instruction file | the exit of the build | From 319c2106246e652f3a0baefc4dbb538b888de55e Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 13 Oct 2026 16:03:37 +0200 Subject: [PATCH 2/2] =?UTF-8?q?Design=2036=20=C2=A712:=20a=20node=20that?= =?UTF-8?q?=20was=20away=20takes=20the=20newest=20memory,=20and=20two=20la?= =?UTF-8?q?rge=20versions=20are=20kept=20in=20two=20files=20(ADR=200343=20?= =?UTF-8?q?as=20reviewed)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md b/03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md index 1c4d0f8f..e3d657fd 100644 --- a/03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md +++ b/03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md @@ -531,13 +531,13 @@ carries each project's memory folder**, `~/.claude/projects//memory/`, | Step | What happens | |---|---| | pick up | the module's own long-running process on each node looks at the memory folders at least every ten seconds; a file whose text differs from the version last carried is put in the module's kept `memory` state, one key per home kind and file: the project, the path, the text, its digest, the digest of the version it replaced, the node and when it changed | -| carry | every node watches the `memory` state (the whole set at start, then each change) and writes an incoming version where its own file is still the version it replaced, or absent; staged and renamed in, never through a symbolic link, the replaced version kept in `memory-copies/` in the module's state directory for 30 days | -| conflict | a file changed on two nodes since the version last carried becomes one file holding both, the newer first, then a line naming each version's node and time, then the older; that file is carried, so every node ends on it, and the operator is told by a warning through the operator channel's `notify`, cleared on the file's next change without a conflict | +| carry | every node watches the `memory` state (the whole set at start, then each change) and writes an incoming version where its own file holds no change it did not carry (the version replaced, the version it last carried, or none); staged and renamed in, never through a symbolic link, the replaced version kept in `memory-copies/` in the module's state directory for 30 days | +| conflict | a file changed on two nodes since the version last carried becomes one file holding both, the newer first, then a line naming each version's node and time, then the older (two versions too large for one file: the newer in it, the older beside it as `.older-from-`); what was written is carried, so every node ends on it, and the operator is told by a warning through the operator channel's `notify`, cleared on the file's next change without a conflict | | remove | a carried file removed from a memory folder that still exists is put in the state as a removal marker for 30 days; a node still holding the removed version moves it into `memory-copies/`, a node that changed it keeps and carries it again; a missing folder or an absent key removes nothing | | first carrying | a node with no record of what it carried takes an equal file as agreed and a different one as a conflict, so every version learned before is kept | | list | `claude_code_memory`: what this node carries, each file's fingerprint and last version, what was not carried and why, open conflicts and copies; never the text | -**What is not carried**: a symbolic link, a file over 128 KiB, and a file holding a key block, a signed +**What is not carried**: a symbolic link, a file over 128 KiB or too large for one value of the state once encoded, and a file holding a key block, a signed token, a known token prefix or a password in an address. Each is listed with why. **Homes of one kind only.** Memory moves among the homes where the agent runs as the operator's account,