M5a spec: built-in policy for recall and memory; tool calls indexed; output summaries in M5c

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-24 13:33:37 -07:00
co-authored by Claude Opus 5.5
parent e86d203740
commit 817fb9d47f
3 changed files with 28 additions and 14 deletions
+26 -13
View File
@@ -121,6 +121,8 @@ its source and label, and each snippet at most 600 characters:
Owner prefers answers in UTC.
[3] session chat-1789…, 2026-09-21 09:15 UTC, the assistant said · private · untrusted
…
[4] session mm-dh45qg…, 2026-09-22 18:17 UTC, the assistant called · private
http_fetch {"url":"https://example.org/notes"}
```
`(no matches for "…")` when nothing is found. The query goes into FTS5 only as quoted terms: it
@@ -177,9 +179,12 @@ appended.
`{home}/index/memory.sqlite3`, directory 0700, file 0600, owned by `memoryd`. Disposable: deleting
it and restarting `memoryd`, or `bxctl reindex`, rebuilds it from `sessions/` and `archive.md`.
**What is indexed.** From session logs: the `content` of `User` records and of `Assistant`
records that have content. Not tool results (the bulk of every log, and the way injected text
arrives), not `reasoning_content`, not tool call arguments. From `archive.md`: every entry.
**What is indexed.** From session logs: the `content` of `User` records, of `Assistant` records
that have content, and each tool call the assistant made, as its tool name and arguments
(`read_file {"path":"/srv/notes/q3.txt"}`), so `recall` can answer "did we read that, and when".
Not tool results (the bulk of every log, and the way injected text arrives) and not
`reasoning_content`. A summary of a tool's output is M5c's (owner, 2026-09-24): it needs a model
to write it. From `archive.md`: every entry.
`memory.md` and `user.md` are not indexed: they are already in every prompt.
**Labels.** Walking a session log in order, `memoryd` keeps the session's label so far: the
@@ -230,10 +235,11 @@ connection, the existing frames (`proto::frame`, `MAX_FRAME`). New message kinds
- **Tools.** `ToolName` gains `Recall` and `Memory`; `ToolArgs` gains their parsed arguments
(`proto` holds the serde structs, as for the other tools). Parsing enforces the shapes and
limits in section 5. Neither tool has constraints (`paths`, `hosts`, `patterns` must be empty).
- **Grants.** Ordinary grant files. The shipped examples:
- **Grants: built in** (owner, 2026-09-24: an agent that cannot write memory is not worth
running). `brokerd` holds two fixed grants, always present whatever is in `grants/`:
```toml
# grants/recall.toml
# built in: recall
tool = "recall"
mode = "auto"
max_taint = "secret"
@@ -242,7 +248,7 @@ connection, the existing frames (`proto::frame`, `MAX_FRAME`). New message kinds
```
```toml
# grants/memory.toml
# built in: memory
tool = "memory"
mode = "ask"
max_taint = "secret"
@@ -250,8 +256,13 @@ connection, the existing frames (`proto::frame`, `MAX_FRAME`). New message kinds
untrusted = false
```
A grant of `memory` at `auto` is refused when grants are loaded: every memory edit is the
owner's (brief, memory 7). The fixed sentence names the grant.
Their names are `builtin-recall` and `builtin-memory`, and their `grant_sha256` is the hash of
that text, so the audit log names them like any other grant. A file in `grants/` whose `tool`
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`. So no grant file can turn
memory edits off, or make them unattended; every edit is the owner's (brief, memory 7). With
section 9 (no session without the memory block), an agent that can talk can always read its
memory and propose edits to it.
- **Where a decided call runs.** A composite runtime sends `Recall` and `Memory` specs to
`memoryd`'s `brokerd` socket as a `tool_request`, and everything else to the configured runtime
(Podman, or the refusing one). `[memory] socket` in `brokerd.toml` names the socket; without it,
@@ -326,7 +337,8 @@ Offline, in `make gate`:
archive and journal lines; the block; the quoted-terms query builder against FTS5 operators
(`NEAR`, `*`, `^`, quotes, column filters); labels walked from a log; `max_class`; catching up
from `progress`; a reindex equal to the incremental index; the recall log.
- `brokerd`: parsing both tools; `memory` at `auto` refused at load; the composite runtime; the
- `brokerd`: parsing both tools; the built-in grants present with an empty `grants/`; a grant
file naming either tool refused at load; the composite runtime; the
stricter label, with a runtime that returns a stricter and a laxer one.
- `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.
@@ -339,10 +351,11 @@ which `recall` then finds; a hit from a session that read a fetched page coming
## 15. For the owner's review
1. Tool results are not indexed (section 6). It keeps injected text out of `recall`, and keeps
the index small; the price is that `recall` cannot find something only a tool returned, such
as a file's contents, unless the assistant repeated it.
2. `memory` at `auto` is refused at load (section 8), not merely discouraged.
1. Decided: tool results are not indexed; tool calls are, by name and arguments (section 6).
Summaries of a tool's output, where the tool opts in (a web fetch, say), come with M5c's
summariser, labelled like the output they summarise.
2. Decided: the policy of `recall` (`auto`) and `memory` (`ask`) is built in, and grant files
cannot name them (section 8): memory edits can be neither turned off nor made unattended.
3. Entries are named by their full text, not by number (section 5), so approvals are readable and
stale edits fail; the arguments of a `consolidate` of a full `memory.md` are about 4,500
characters, one approval post.