From 20bb2ff6fc622ff21928327cc9414fe1a1ce14f3 Mon Sep 17 00:00:00 2001 From: "K. Isom" Date: Thu, 24 Sep 2026 13:46:00 -0700 Subject: [PATCH] M5a: the check stays short and fixed; a sanitizer from injection research becomes M5b hardening Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/decisions.md | 1 + docs/milestones.md | 2 +- docs/specs/2026-09-24-m5a-local-memory.md | 5 +++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/decisions.md b/docs/decisions.md index c9d83a3..3b03918 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -6,6 +6,7 @@ Newest first. A decision that changes `docs/design.md` lands in the same commit | Date | Decision | Reason | |---|---|---| +| 2026-09-24 | M5a's check on memory writes stays short and fixed: control characters, invisible characters, chat-template markup, six phrases. A research thread on prompt-injection defences feeds a sanitizer module for memory writes (and possibly other persisted text), built as hardening in M5b. Memory writes are not posted to the chat; `bxctl memory log` lists them. | Owner. The archive routing of untrusted sessions' writes is the main defence; a pattern list is defence in depth and should rest on research, not guesses. | | 2026-09-24 | Memory writes need no approval, as in Hermes (the owner's `~/.hermes` has no `write_approval`). Writes from a session that has seen untrusted content go to `memory/archive.md`, labelled untrusted, not into the prompt. Every write is scanned, journalled (listed by `bxctl memory log`, not posted anywhere) and can be undone; nothing is deleted. Brief change P21 proposed; the M5a spec is revised to match. Session-scoped approvals for other tools become an open decision. | Owner: an agent harness should act without a stream of questions, and reading and writing memory is what an agent is. The only risk the approval guarded is injected text reaching every future prompt, and that needs a session that read untrusted content; `brokerd` knows which sessions those are. | | 2026-09-24 | M5a spec review: the policy of `recall` (`auto`) and `memory` (`ask`) is built into `brokerd`, and grant files cannot name them; tool calls are indexed by name and arguments, tool results are not; summaries of tool output, where a tool opts in, come with M5c's summariser. | Owner: an agent that cannot write memories is no better than a plain chat window, and a grant file must not be able to turn edits off or make them unattended. A web fetch's summary is worth keeping; most tools' output is not. | | 2026-09-24 | Compaction (new epochs) comes with the scheduler in M5c, not in M5a. Until then a session has one epoch, and M5a's rules say "the next session" where the brief says "the next epoch". `soul.md` starts from a generic file in the repository; `memory.md` and `user.md` start empty. M5a spec drafted: `docs/specs/2026-09-24-m5a-local-memory.md`. | Owner. `loopd` has no epochs yet (every session is epoch 0; a full context is `SessionFull`); compaction is an idle-time job on the background slot, which is what M5c builds. Over Mattermost each thread is a session, so a new thread already picks up approved edits. | diff --git a/docs/milestones.md b/docs/milestones.md index cd9a540..3e645cb 100644 --- a/docs/milestones.md +++ b/docs/milestones.md @@ -14,7 +14,7 @@ One session per milestone. The design brief is `docs/design.md`. | M4a | `gatewayd` + Mattermost conversations | Bot connection over TLS, user-ID allowlist, threads as sessions, bursts as one turn, typing while working, catching up after downtime, the secret store, no listening port | A direct message answered on the real server; a non-allowlisted user gets no response | | M4b | Approvals over Mattermost | `gateway.sock`, approval posts, answers by `!approve`/`!deny` in the thread, approvers list, long approvals in parts | An `ask` call approved and refused from Mattermost; the audit log names the post | | M5a | Local memory | `memoryd` without a network: the three memory files served to `loopd` once per epoch, the `memory` tool (add, replace, remove, `consolidate`) at `ask`, `memory/archive.md`, FTS5 `recall` over session logs and the archive, `recall` hit counts, `bxctl reindex` | A fact from one session is recalled in another; an entry moved by `consolidate` is found by `recall`; a memory edit changes the prompt only in the next session (next epoch once M5c adds epochs); the baseline with the owner's files stays under 3,000 tokens | -| M5b | Honcho | `memoryd` syncs finished turns to its own Honcho workspace through `roncho`, `recall` and `ask_memory` over Honcho, other workspaces read with the owner's trust labels, moved entries written as conclusions | A turn from a tainted session never reaches Honcho; a result from an untrusted workspace taints the session; Honcho's contents are rebuilt from the logs | +| M5b | Honcho and hardening | A sanitizer module for memory writes, from the prompt-injection research of 2026-09-24; `memoryd` syncs finished turns to its own Honcho workspace through `roncho`, `recall` and `ask_memory` over Honcho, other workspaces read with the owner's trust labels, moved entries written as conclusions | A turn from a tainted session never reaches Honcho; a result from an untrusted workspace taints the session; Honcho's contents are rebuilt from the logs | | M5c | Scheduler and compaction | Heartbeat and cron on their own slot in isolated sessions; compaction at idle, starting a new epoch from the baseline and a summary; summaries of tool output for the tools that opt in (a web fetch, say), indexed with the output's label; the tiering job, which proposes what stays at which tier | A scheduled run leaves the main slot's cache intact; a fact from epoch 1 is recalled in epoch 2, and a memory edit reaches the next epoch; the tiering job's proposals reach the owner as ordinary approvals and change nothing on their own | | M6 | Subagents + cloud consult | Subagents on their slot returning summaries; consult tool gated by data-class grants with full payload log | A tainted session is refused; payload log matches what was sent | | M7 | Split deployment | Each role in its own rootless container; `loopd` with no network; socket volume permissions; egress proxy for tools; host ACL policy in `deploy/` | From inside the `loopd` container, requests to the internet, the tailnet and the host all fail; a tool container without a host grant cannot reach the tailnet | diff --git a/docs/specs/2026-09-24-m5a-local-memory.md b/docs/specs/2026-09-24-m5a-local-memory.md index 3b8a081..f39e5c0 100644 --- a/docs/specs/2026-09-24-m5a-local-memory.md +++ b/docs/specs/2026-09-24-m5a-local-memory.md @@ -429,6 +429,7 @@ untrusted; `bxctl memory undo` of the latest edit. to the archive (section 5). 3. Decided: entries are named by their full text, not by number (section 5). 4. Decided: the starter `soul.md` is generic (section 3). -5. Open: the phrase list of the check (section 5) is short on purpose. Add phrases you have seen - in the wild, or drop the list and keep only rules 1 to 3. +5. Decided: M5a starts with the check as written, the short fixed phrase list and rules 1 to 3 + (section 5). A research thread on prompt-injection defences is running; its findings become a + sanitizer module for memory writes, and possibly other persisted text, as hardening in M5b. 6. Decided: writes are not announced; `bxctl memory log` lists them (section 5).