M5a spec: details settled while building the reference

The memory socket in brokerd.toml is optional, and bxctl's flag is
--memory-socket. A note's header carries its class. Built-in grants are
kept apart from the owner's, and builtin- ids are reserved. The session
state is carried through Decision and RunSpec, labels through
run_labelled. loopd's assemble_with and MemorySource. Baseline
measured at 881 and 2,432 tokens.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-24 14:58:33 -07:00
co-authored by Claude Opus 5.5
parent d80b0b3d8a
commit 19fd74db6e
+29 -13
View File
@@ -30,7 +30,9 @@ Facts this rests on, checked on 2026-09-24:
the bytes it started with. No write asks the owner anything. the bytes it started with. No write asks the owner anything.
4. What a session that has seen untrusted content wants to remember becomes a note, labelled 4. What a session that has seen untrusted content wants to remember becomes a note, labelled
untrusted: remembered and found by `recall`, never in the prompt. untrusted: remembered and found by `recall`, never in the prompt.
5. The baseline with the starter `soul.md` stays at 3,000 tokens or less, measured on the device. 5. The baseline with the starter `soul.md` stays at 3,000 tokens or less, measured on the device;
so does the baseline with every memory file at its limit. (Measured while building the
reference, 2026-09-24: 881 and 2,432 tokens with Ornith's tokenizer.)
6. A hit from a session that had seen untrusted content comes back marked untrusted. 6. A hit from a session that had seen untrusted content comes back marked untrusted.
7. The owner can list every write (`bxctl memory log`), and undo the latest one of a file. 7. The owner can list every write (`bxctl memory log`), and undo the latest one of a file.
@@ -78,7 +80,7 @@ each item with its own label. Not everything remembered is believed, but it is r
**`notes.md`.** Append-only. Each entry starts with one header line saying how it got there: **`notes.md`.** Append-only. Each entry starts with one header line saying how it got there:
```text ```text
§ noted 2026-09-24T21:02Z in session mm-dh45qg… · untrusted § noted 2026-09-24T21:02Z in session mm-dh45qg… · private · untrusted
<the entry> <the entry>
§ moved 2026-09-24T20:14Z from memory.md in session mm-y73sxj3popgwumyccptb56p1ah § moved 2026-09-24T20:14Z from memory.md in session mm-y73sxj3popgwumyccptb56p1ah
<the entry> <the entry>
@@ -345,24 +347,36 @@ connection, the existing frames (`proto::frame`, `MAX_FRAME`). New message kinds
is `recall` or `memory` is a grant problem at load (`grants-invalid`), with the fixed sentence is `recall` or `memory` is a grant problem at load (`grants-invalid`), with the fixed sentence
`the policy of recall and memory is built in; remove this grant`. No grant file can turn memory `the policy of recall and memory is built in; remove this grant`. No grant file can turn memory
off or put questions in front of it. What protects the prompt is where untrusted sessions' off or put questions in front of it. What protects the prompt is where untrusted sessions'
writes go (section 5), which `memoryd` decides from the state `brokerd` sends. writes go (section 5), which `memoryd` decides from the state `brokerd` sends. The built-ins
are kept apart from the owner's grants (`GrantSet::grants` lists only the owner's; matching sees
both), and grant ids starting `builtin-` are reserved. An invalid `grants/` still denies every
call, the memory tools included: that is the fail-closed state `grants-invalid` describes.
- **Where a decided call runs.** A composite runtime sends `Recall` and `Memory` specs to - **Where a decided call runs.** A composite runtime sends `Recall` and `Memory` specs to
`memoryd`'s `brokerd` socket as a `memory_call`, with the session's taint and untrusted flag `memoryd`'s `brokerd` socket as a `memory_call`, with the session's taint and untrusted flag
as `brokerd` holds them when it decides, and everything else to the configured runtime as `brokerd` holds them when it decides, and everything else to the configured runtime
(Podman, or the refusing one). `[memory] socket` in `brokerd.toml` names the socket; without it, (Podman, or the refusing one). The socket is `{home}/run/broker-memory/memory.sock`, or
memory calls fail with a fixed sentence and a runbook pointer (`memoryd-unreachable`). `[memory] socket` in `brokerd.toml`; the section is optional, since the memory tools are always
- **Labels only get stricter.** `RunOutput` gains an optional label. `runner::run` answers with on. When `memoryd` does not answer in 30 seconds, the call fails with a fixed sentence and a
the stricter of the grant's label and the runtime's: the higher class, and untrusted if either runbook pointer (`memoryd-unreachable`). The session's state reaches the runtime through the
is. Container runtimes return none, so nothing changes for them. The audit record and the `Decision` (which `decide` already receives it for) and the `RunSpec`. The Podman runtime
session's taint use the combined label, as today. refuses a memory tool with a fixed sentence rather than start a container for it.
- **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.
- **Size.** `policy.rs` (498 lines) and `ledger.rs` (487) are at the limit; the plan splits them - **Size.** `policy.rs` (498 lines) and `ledger.rs` (487) are at the limit; the plan splits them
before anything is added. before anything is added.
## 9. `loopd` ## 9. `loopd`
- `[memory] socket` in `loopd.toml` (default `{home}/run/loop-memory/memory.sock`). - `[memory] socket` in `loopd.toml` (default `{home}/run/loop-memory/memory.sock`).
- Baseline: `system.md`, a blank line, then the block from `memoryd`. `memoryd` unreachable, or - Baseline: `system.md`, a blank line, then the block from `memoryd`
refusing, fails the new session with the runbook pointer `memory-unavailable`. (`Baseline::assemble_with`; `Baseline::assemble` stays, with no memory, for tests and the
self-test). `memoryd` unreachable, or refusing, fails the new session with the runbook pointer
`memory-unavailable`. `Context` takes the memory source explicitly (a `MemorySource` trait:
`memoryd`'s client in `loopd serve`, `NoMemory` in tests), so `loopd serve` cannot leave it out.
`make verify-device` starts `memoryd` beside `loopd serve`.
- Registry: `recall` and `memory` are core, with the schemas of section 5. The model sees - Registry: `recall` and `memory` are core, with the schemas of section 5. The model sees
`clock`, `recall`, `memory`, `find_tool`, `call_tool`. `clock`, `recall`, `memory`, `find_tool`, `call_tool`.
- After a finished turn: `session_updated`, best effort. A failure is one log line; the next - After a finished turn: `session_updated`, best effort. A failure is one log line; the next
@@ -373,11 +387,13 @@ connection, the existing frames (`proto::frame`, `MAX_FRAME`). New message kinds
## 10. `bxctl` ## 10. `bxctl`
- `bxctl reindex [--admin-socket PATH]`: sends `reindex`, waits, and prints `reindexed: 42 logs, - `bxctl reindex [--memory-socket PATH]`: sends `reindex`, waits, and prints `reindexed: 42 logs,
1,208 records, 7 notes`. `memoryd` rebuilds into a new file and renames it over the 1,208 records, 7 notes`. `memoryd` rebuilds into a new file and renames it over the
old one, so `recall` keeps working meanwhile. old one, so `recall` keeps working meanwhile.
- `bxctl memory log [N]`, `bxctl memory undo <id>`, and with review on, `bxctl memory pending`, - `bxctl memory log [N]`, `bxctl memory undo <id>`, and with review on, `bxctl memory pending`,
`accept <id>`, `reject <id>` (section 5). Entries are printed escaped, as approvals are. `accept <id>`, `reject <id>` (section 5), each with `[--memory-socket PATH]` (default
`{home}/run/owner-memory/admin.sock`; `--admin-socket` stays `brokerd`'s). Entries are printed
escaped, as approvals are.
## 11. Configuration ## 11. Configuration