From fdc03cf8baddfc8f35913561b6d420f1a376578e Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Sun, 27 Sep 2026 22:25:20 -0700 Subject: [PATCH] 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) --- docs/specs/2026-09-27-m5c-honcho.md | 83 ++++++++++++++++++----------- 1 file changed, 51 insertions(+), 32 deletions(-) diff --git a/docs/specs/2026-09-27-m5c-honcho.md b/docs/specs/2026-09-27-m5c-honcho.md index 9a9ba8d..a9ae7d8 100644 --- a/docs/specs/2026-09-27-m5c-honcho.md +++ b/docs/specs/2026-09-27-m5c-honcho.md @@ -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: `, 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_`, 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`, 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.