M5c spec: the owner's answers

A turn without complete speaker information is not sent; every peer in
the fleet workspace is a member, so fleet notes are messages that one
search finds, with a retraction message for an undone note (Honcho
cannot delete a message); the fleet workspace derives nothing
(reasoning, summaries and dreaming off); API keys per workspace are
first-class, with a runbook entry for a refused key.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-27 22:25:20 -07:00
co-authored by Claude Opus 5.5
parent 23c9ebec12
commit fdc03cf8ba
+51 -32
View File
@@ -1,7 +1,8 @@
# M5c design: Honcho
Status: draft for the owner's review, 2026-09-27. Designed with the owner the same day (the
decisions are marked "owner"). Supersedes P23 where they differ (section 14).
Status: draft for the owner's review, 2026-09-27; section 15's answers folded in. Designed with the
owner the same day (the decisions are marked "owner"). Supersedes P23 where they differ (section
14).
## 1. What M5c proves
@@ -44,7 +45,7 @@ Projects in memory stay out of M5c (open row in `docs/decisions.md`).
|---|---|---|---|---|
| its **trusted** one | turns from clean sessions; conclusions mirroring its trusted memory writes | the agent (through `ask_memory`); other agents, as the owner labels it | trusted | `private` |
| its **untrusted** one | turns from sessions that read untrusted content | the agent only (through `ask_memory`) | untrusted, fixed | `private` |
| the **fleet** one, shared by every agent | conclusions from untrusted notes, each agent under its own peer | every agent | untrusted, fixed in code | `private` |
| the **fleet** one, shared by every agent | untrusted notes, as messages from each agent's own peer | every agent | untrusted, fixed in code | `private` |
A workspace's class is also its ceiling for writes: nothing above `private` is written to Honcho
(owner). A `public` turn is written as `private`, since `public` records where a result came
@@ -56,6 +57,8 @@ workspace (owner).
- the owner: one peer, the same name in every workspace of the fleet (`[honcho] owner`); observed;
- other people who speak in a channel the agent answers: a peer each, named from their
Mattermost username (a `.` becomes `_`: Honcho allows `[a-zA-Z0-9_-]`); observed;
- every peer in the fleet workspace is a member of the fleet, whether or not this agent's
configuration names it (owner);
- the agent: a peer under its own name (`[honcho] agent`), **not observed** (`observe_me = false`).
Its replies are written as messages and serve as context when Honcho reasons about the people
it talks to, but Honcho draws no conclusions about the agent from them: they are the agent's
@@ -80,7 +83,7 @@ limit) is written as several messages of at most 25,000, in order.
| clean, class `public` or `private` | the trusted workspace |
| untrusted (read untrusted content), class `public` or `private` | the untrusted workspace |
| class `secret` | nowhere |
| no single speaker (a burst joined from several people) | nowhere |
| no speaker, or incomplete speaker information (a burst joined from several people) | nowhere (owner) |
| the text holds a known-prefix secret (`sanitize::refuse`'s secret rules) | nowhere |
**Memory writes.** Every write is mirrored as a conclusion the moment it is journalled (owner),
@@ -94,9 +97,10 @@ spotting the phrase:
| an entry dropped from identity (`remove`, or dropped by `consolidate`) | its conclusion deleted |
| an entry moved out of identity by `consolidate` | nothing: its conclusion is already there |
| a trusted note | a conclusion in the trusted workspace |
| an untrusted note | a conclusion in the fleet workspace |
| an untrusted note | a message in the fleet workspace, from the agent's peer |
| a flagged note (a sanitizer flag rule matched) | nothing: it stays local (owner) |
| `bxctl memory undo` of an edit whose conclusions were written | those conclusions deleted; any it had deleted, written again |
| `bxctl memory undo` of an untrusted note already in the fleet | a retraction message beside it, `retracted: <the note>`, from the agent's peer; the original stays, since Honcho cannot delete a message (owner) |
A conclusion's observer and observed are the same peer, which is Honcho's own picture of that
peer: a `user.md` entry is the owner's (it is about the owner); a `memory.md` entry or a note is
@@ -124,7 +128,8 @@ rebuilt from them.
ledger.
5. **Send.** One worker thread in `memoryd` sends what is decided and not yet sent, oldest first:
for a turn it creates the Honcho workspace, peers and session on first use (section 6), then
adds the two messages as one batch; for a memory write it creates or deletes the conclusion.
adds the two messages as one batch; for a memory write it creates or deletes the conclusion,
or, for an untrusted note, adds the message to the fleet workspace (or its retraction).
Each send is recorded in the ledger with Honcho's ids.
6. **Honcho down** (a connection error, a timeout, a 5xx): the worker waits and tries again,
doubling the wait from 5 s to 10 minutes. It logs the outage once, when it starts, and once
@@ -160,7 +165,10 @@ passes (checked 2026-09-27).
- `roncho`'s `Status` and `Decode` errors carry up to 4 KiB of the response, which can be memory
text: `memoryd` logs only the error's kind and status, never its `Display`.
- On creation, `memoryd` configures each workspace: the trusted and untrusted ones with reasoning,
summaries and dreaming on (Honcho's defaults); the fleet one holds conclusions only.
summaries and dreaming on (Honcho's defaults); the fleet one with `reasoning`, `summary` and
`dream` all `enabled = false`, and every agent's peer in it unobserved, so no model reads the
fleet's notes to derive anything (Honcho enqueues summaries before it checks reasoning, and
schedules dreams by their own setting, so all three are set).
- Honcho ids allow `[a-zA-Z0-9_-]`, at most 512 characters. Boxmaker session ids fit; names from
configuration are checked at start.
@@ -179,7 +187,7 @@ One tool, as today, searching (owner):
|---|---|---|---|
| the local index | FTS5, as today | as today (`identity`, `memory`, `memory-tainted`) | as recorded |
| each other agent's trusted workspace in `[[honcho.read]]` | message search, and a conclusion query for that agent's peer and the owner's | `memory` if the owner labelled it trusted, else `memory-tainted`; the agent's name | the workspace's `class` |
| the fleet workspace | a conclusion query for this agent's peer and each agent's in `[[honcho.read]]` | `memory-tainted`; the writing agent's name | `private` |
| the fleet workspace | message search over the whole workspace | `memory-tainted`; the writing peer's name | `private` |
The agent's own workspaces are not searched: the local index holds every turn and note of this
agent with exact labels, and would return the same text twice. Each source runs on its own thread
@@ -189,12 +197,12 @@ ranked within their source and interleaved to `k`, the local index first. Text f
neutralised on arrival and size-capped like every recall snippet. Any `memory-tainted` hit marks
the session untrusted, and every hit raises the session's class to its label, as local hits do.
Honcho's search endpoints find messages only, and the fleet workspace holds only conclusions, as
do the mirrored memory writes in another agent's trusted workspace: so `recall` also queries
conclusions (`POST /conclusions/query`, which needs the observer and the observed; for a
conclusion `memoryd` wrote, both are the same peer). `roncho`'s blocking client cannot list a
workspace's peers, so the agents whose fleet notes an agent reads are itself and those in
`[[honcho.read]]`.
Honcho's search endpoints find messages only, and the mirrored memory writes in another agent's
trusted workspace are conclusions: so for those `recall` also queries conclusions
(`POST /conclusions/query`, which needs the observer and the observed; for a conclusion `memoryd`
wrote, both are the same peer). The fleet workspace holds messages, so one search finds the notes
of every peer in it, including agents this one was never told about (owner); a retracted note is
found beside its retraction.
## 9. `ask_memory`
@@ -240,9 +248,15 @@ trust = "untrusted" # the default; "trusted" must be written out
class = "private"
```
Keys come from `bxcore::secrets` (M5b). An invalid `[honcho]` section stops `memoryd` at start
with the reason (runbook `honcho-config-invalid`). `docs/egress.md` gains rift: `memoryd` →
the configured URL, for writes, searches and chat.
**API keys** (owner: the design must support them): each workspace this agent writes or reads has
its key, `honcho_trusted`, `honcho_untrusted`, `honcho_fleet` and `honcho_read_<agent>`, from
`bxcore::secrets` (M5b), and `memoryd` sends it as a bearer token on every call to that workspace
and never logs it. A workspace without a key configured is called without one; rift has auth off
today (2026-09-27), and turning it on needs only the keys. A refused key (401 or 403) stops the
sends to that workspace, is logged once with the workspace's name (runbook `honcho-key-refused`),
and is shown by `bxctl memory sync`; the others go on. An invalid `[honcho]` section stops
`memoryd` at start with the reason (runbook `honcho-config-invalid`). `docs/egress.md` gains rift:
`memoryd` → the configured URL, for writes, searches and chat.
## 11. Links between the roles
@@ -259,11 +273,11 @@ Wire changes (fixtures change): `Turn` gains `speaker: Option<String>`, which `g
the Mattermost username when every post of the burst is from one person, and `bxctl chat` sets
to the owner's name from its configuration; `loopd` logs it on the `User` record (a new optional
field: older logs read as `None`); `SessionUpdated` gains `turn: u64`. A turn whose speaker is
`None` is not synced.
`None`, or not a valid peer name, is not synced (owner).
## 12. Runbook
New entries, given as fragments: `honcho-unreachable`, `honcho-send-refused`,
New entries, given as fragments: `honcho-unreachable`, `honcho-send-refused`, `honcho-key-refused`,
`broker-state-unavailable`, `honcho-config-invalid`. Each names what `bxctl memory sync` shows.
## 13. Testing
@@ -281,6 +295,12 @@ that records requests and answers from fixtures taken from rift's OpenAPI docume
- **Peers and sessions**: the agent's peer not observed; a new speaker starts the next Honcho
session; names fit Honcho's pattern.
- **`roncho`**: a `roncho.toml` with an `api_key` in the test's `HOME` is not read.
- **Keys**: a configured key is sent as a bearer token to its workspace only, and appears in no
log line or error; without a key, no `Authorization` header; a 401 stops that workspace's sends
only, once logged.
- **The fleet**: an untrusted note is a message from the agent's peer; its undo is a retraction
message; the fleet workspace is created with reasoning, summaries and dreaming off; a note
from a peer this agent's configuration does not name is found by `recall`.
- **`recall`**: the fleet sources with their tags and classes; a source that times out is one
`unavailable` line and the rest still answer; the agent's own workspaces are not searched.
- **`ask_memory`**: two at a time; the per-workspace limit; labels; unknown workspace names.
@@ -309,17 +329,16 @@ With this spec, as their own commits:
rebuild); rows for the class ceiling, the peers, mirroring, the ledger, and `recall`'s sources.
- `docs/milestones.md`, M5c: its check "Honcho's contents are rebuilt from the logs" goes.
## 15. For the owner's review
## 15. The owner's answers (2026-09-27)
1. **The speaker on each turn** (section 11) is new: Honcho needs a peer per message, and today's
turn text carries no name. A burst joined from several people is not synced at all, rather
than guessed at.
2. **Which fleet notes an agent reads** (section 8): its own and those of the agents in
`[[honcho.read]]`, since the client cannot list the fleet's peers. An agent the owner has not
listed contributes to the fleet but is not read.
3. **For `roncho`'s agent** (found 2026-09-27, none blocking): make the config file opt-in (it
supplies an `api_key` today; the README's "reads no environment variables" is wrong); evidence
decoding requires `observer_id` and `observed_id`, which rift's schema lacks; a message's
`configuration` (reasoning per message) is not exposed; the tag `v1.0.0` names `b4e7864` on
`origin` and `1f5ea42` on crates.io.
4. **Rift**: auth is still off; the keys in section 10 are ready for it.
1. **The speaker**: a turn with no speaker, or incomplete speaker information, is not sent
(sections 4 and 11).
2. **The fleet**: every peer in the fleet workspace is a member of the fleet. Fleet notes are
therefore messages, which one search finds whoever wrote them; an undone note gets a
retraction message, since Honcho cannot delete a message (sections 3, 4, 8).
3. **For `roncho`'s agent**, accepted (found 2026-09-27, none blocking): make the config file
opt-in (it supplies an `api_key` today; the README's "reads no environment variables" is
wrong); evidence decoding requires `observer_id` and `observed_id`, which rift's schema lacks;
a message's `configuration` (reasoning per message) is not exposed; the tag `v1.0.0` names
`b4e7864` on `origin` and `1f5ea42` on crates.io.
4. **API keys**: the design supports them per workspace (section 10); rift has auth off today.