From 0e5b1d282090d3f3120e995f75e47ddb32272e4c Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Fri, 25 Sep 2026 17:44:35 -0700 Subject: [PATCH] M5a spec: the session's state follows a result's own label; plainer note answers Found on the device (2026-09-25): the ledger raised a session's state by the grant's label, so a clean session that recalled a memory-tainted note stayed trusted and could add it to memory.md. The spec's "as today" was wrong; section 8 now says the state is raised by the stricter of the grant's label and the result's, and the recall section says the session becomes untrusted. The note answers (section 3) name notes.md and, for an untrusted add, the file that was not changed: the model read the old ones as success. Tasks 21 and 22. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/specs/2026-09-24-m5a-local-memory.md | 29 +++++++++++++++-------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/docs/specs/2026-09-24-m5a-local-memory.md b/docs/specs/2026-09-24-m5a-local-memory.md index 7630f15..23f5cb7 100644 --- a/docs/specs/2026-09-24-m5a-local-memory.md +++ b/docs/specs/2026-09-24-m5a-local-memory.md @@ -175,7 +175,8 @@ FTS5's `bm25`. **The label.** The result's class is the highest class among the hits (at least `private`), and it is untrusted if any hit is tagged `memory-tainted`. `brokerd` combines that with the grant's label and keeps the -stricter of each (section 8). +stricter of each (section 8), and the session's state follows the combined label: a session that +recalls a `memory-tainted` hit becomes untrusted, so its identity writes become notes from then on. ### `memory` @@ -203,14 +204,17 @@ the file fails safely: every `old` must match an entry of the file as it is now, **Where a write goes.** `brokerd` sends each call with the session's state (section 7): - `note` is appended to `notes.md` in any session, labelled by the session (above). The answer: - `noted (untrusted)` or `noted`. + `saved in notes.md`, or in a session that has read untrusted content `saved in notes.md, marked + untrusted: this session has read untrusted content`. - In a session that has **not** seen untrusted content, the identity actions apply to `memory.md` or `user.md`. -- In a session that **has**, an identity `add` becomes a `note`, and the answer says so: `saved as - a note (untrusted): this session has read untrusted content`. It is remembered and `recall` - finds it; it never reaches the prompt on its own. `replace`, `remove` and `consolidate` are - refused with `this session has read untrusted content; it can take notes but not change - memory.md`. The owner can move a note into a file by editing it. +- In a session that **has**, an identity `add` becomes a `note`, and the answer says so, naming + the file it did not change: `not added to memory.md: this session has read untrusted content, so + it was saved in notes.md as an untrusted note` (or `user.md`). On the device (2026-09-25) the + model read the earlier, shorter answers as success and told the owner the file was changed. + The note is remembered and `recall` finds it; it never reaches the prompt on its own. + `replace`, `remove` and `consolidate` are refused with `this session has read untrusted content; + it can take notes but not change memory.md`. The owner can move a note into a file by editing it. **Rules**, checked by `brokerd` when parsing (shape) and by `memoryd` when applying (content): @@ -372,8 +376,12 @@ connection, the existing frames (`proto::frame`, `MAX_FRAME`). New message kinds - **Labels only get stricter.** `Runtime` gains `run_labelled`, returning an optional label with the output; its default calls `run` and returns none, so container runtimes and the given test runtimes are unchanged. `runner::run` answers with the stricter of the grant's label and the - runtime's: the higher class, and untrusted if either is. The audit record and the session's - taint use the combined label, as today. + runtime's: the higher class, and untrusted if either is. The audit record carries the combined + label. The ledger raises the session's state by the stricter of the grant's label and the + result's own, never by the grant's alone. (The first version of this spec said "as today", but + the ledger raised the state by the grant's label, which a result could not change before M5a. A + clean session could then recall a tainted note and write it into `memory.md`; found on the + device, 2026-09-25.) - **Size.** `policy.rs` (498 lines) and `ledger.rs` (487) are at the limit; the plan splits them before anything is added. @@ -462,7 +470,8 @@ Offline, in `make gate`: from `progress`; a reindex equal to the incremental index; the recall log. - `brokerd`: parsing both tools; the built-in grants present with an empty `grants/`; a grant file naming either tool refused at load; the session's state carried in `memory_call`; the composite runtime; the - stricter label, with a runtime that returns a stricter and a laxer one. + stricter label, with a runtime that returns a stricter and a laxer one; the session's state + raised by a result stricter than its grant, and by the grant when a result is laxer. - `loopd`: the baseline with a fake `memoryd`; the refusal and its pointer; `session_updated` sent after a finished turn and not after a turn ending in tool calls. - `bxctl`: `reindex` and each `memory` command against a fake socket.