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.
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.
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.
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:
```text
§ noted 2026-09-24T21:02Z in session mm-dh45qg… · untrusted
§ noted 2026-09-24T21:02Z in session mm-dh45qg… · private · untrusted
<the entry>
§ moved 2026-09-24T20:14Z from memory.md in session mm-y73sxj3popgwumyccptb56p1ah
<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
`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'
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
`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
(Podman, or the refusing one). `[memory] socket` in `brokerd.toml` names the socket; without it,
memory calls fail with a fixed sentence and a runbook pointer (`memoryd-unreachable`).
- **Labels only get stricter.** `RunOutput` gains an optional label. `runner::run` answers with
the stricter of the grant's label and the runtime's: the higher class, and untrusted if either
is. Container runtimes return none, so nothing changes for them. The audit record and the
session's taint use the combined label, as today.
(Podman, or the refusing one). The socket is `{home}/run/broker-memory/memory.sock`, or
`[memory] socket` in `brokerd.toml`; the section is optional, since the memory tools are always
on. When `memoryd` does not answer in 30 seconds, the call fails with a fixed sentence and a
runbook pointer (`memoryd-unreachable`). The session's state reaches the runtime through the
`Decision` (which `decide` already receives it for) and the `RunSpec`. The Podman runtime
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
before anything is added.
## 9. `loopd`
- `[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
refusing, fails the new session with the runbook pointer `memory-unavailable`.
- Baseline: `system.md`, a blank line, then the block from `memoryd`
(`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
`clock`, `recall`, `memory`, `find_tool`, `call_tool`.
- 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`
- `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
old one, so `recall` keeps working meanwhile.
- `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