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:
@@ -1,7 +1,8 @@
|
|||||||
# M5c design: Honcho
|
# M5c design: Honcho
|
||||||
|
|
||||||
Status: draft for the owner's review, 2026-09-27. Designed with the owner the same day (the
|
Status: draft for the owner's review, 2026-09-27; section 15's answers folded in. Designed with the
|
||||||
decisions are marked "owner"). Supersedes P23 where they differ (section 14).
|
owner the same day (the decisions are marked "owner"). Supersedes P23 where they differ (section
|
||||||
|
14).
|
||||||
|
|
||||||
## 1. What M5c proves
|
## 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 **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` |
|
| 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
|
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
|
(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;
|
- 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
|
- 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;
|
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`).
|
- 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
|
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
|
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 |
|
| clean, class `public` or `private` | the trusted workspace |
|
||||||
| untrusted (read untrusted content), class `public` or `private` | the untrusted workspace |
|
| untrusted (read untrusted content), class `public` or `private` | the untrusted workspace |
|
||||||
| class `secret` | nowhere |
|
| 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 |
|
| 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),
|
**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 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 |
|
| an entry moved out of identity by `consolidate` | nothing: its conclusion is already there |
|
||||||
| a trusted note | a conclusion in the trusted workspace |
|
| 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) |
|
| 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 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
|
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
|
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.
|
ledger.
|
||||||
5. **Send.** One worker thread in `memoryd` sends what is decided and not yet sent, oldest first:
|
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
|
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.
|
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,
|
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
|
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
|
- `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`.
|
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,
|
- 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
|
- Honcho ids allow `[a-zA-Z0-9_-]`, at most 512 characters. Boxmaker session ids fit; names from
|
||||||
configuration are checked at start.
|
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 |
|
| 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` |
|
| 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
|
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
|
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
|
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.
|
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
|
Honcho's search endpoints find messages only, and the mirrored memory writes in another agent's
|
||||||
do the mirrored memory writes in another agent's trusted workspace: so `recall` also queries
|
trusted workspace are conclusions: so for those `recall` also queries conclusions
|
||||||
conclusions (`POST /conclusions/query`, which needs the observer and the observed; for a
|
(`POST /conclusions/query`, which needs the observer and the observed; for a conclusion `memoryd`
|
||||||
conclusion `memoryd` wrote, both are the same peer). `roncho`'s blocking client cannot list a
|
wrote, both are the same peer). The fleet workspace holds messages, so one search finds the notes
|
||||||
workspace's peers, so the agents whose fleet notes an agent reads are itself and those in
|
of every peer in it, including agents this one was never told about (owner); a retracted note is
|
||||||
`[[honcho.read]]`.
|
found beside its retraction.
|
||||||
|
|
||||||
## 9. `ask_memory`
|
## 9. `ask_memory`
|
||||||
|
|
||||||
@@ -240,9 +248,15 @@ trust = "untrusted" # the default; "trusted" must be written out
|
|||||||
class = "private"
|
class = "private"
|
||||||
```
|
```
|
||||||
|
|
||||||
Keys come from `bxcore::secrets` (M5b). An invalid `[honcho]` section stops `memoryd` at start
|
**API keys** (owner: the design must support them): each workspace this agent writes or reads has
|
||||||
with the reason (runbook `honcho-config-invalid`). `docs/egress.md` gains rift: `memoryd` →
|
its key, `honcho_trusted`, `honcho_untrusted`, `honcho_fleet` and `honcho_read_<agent>`, from
|
||||||
the configured URL, for writes, searches and chat.
|
`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
|
## 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
|
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
|
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
|
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
|
## 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.
|
`broker-state-unavailable`, `honcho-config-invalid`. Each names what `bxctl memory sync` shows.
|
||||||
|
|
||||||
## 13. Testing
|
## 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
|
- **Peers and sessions**: the agent's peer not observed; a new speaker starts the next Honcho
|
||||||
session; names fit Honcho's pattern.
|
session; names fit Honcho's pattern.
|
||||||
- **`roncho`**: a `roncho.toml` with an `api_key` in the test's `HOME` is not read.
|
- **`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
|
- **`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.
|
`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.
|
- **`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.
|
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.
|
- `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
|
1. **The speaker**: a turn with no speaker, or incomplete speaker information, is not sent
|
||||||
turn text carries no name. A burst joined from several people is not synced at all, rather
|
(sections 4 and 11).
|
||||||
than guessed at.
|
2. **The fleet**: every peer in the fleet workspace is a member of the fleet. Fleet notes are
|
||||||
2. **Which fleet notes an agent reads** (section 8): its own and those of the agents in
|
therefore messages, which one search finds whoever wrote them; an undone note gets a
|
||||||
`[[honcho.read]]`, since the client cannot list the fleet's peers. An agent the owner has not
|
retraction message, since Honcho cannot delete a message (sections 3, 4, 8).
|
||||||
listed contributes to the fleet but is not read.
|
3. **For `roncho`'s agent**, accepted (found 2026-09-27, none blocking): make the config file
|
||||||
3. **For `roncho`'s agent** (found 2026-09-27, none blocking): make the config file opt-in (it
|
opt-in (it supplies an `api_key` today; the README's "reads no environment variables" is
|
||||||
supplies an `api_key` today; the README's "reads no environment variables" is wrong); evidence
|
wrong); evidence decoding requires `observer_id` and `observed_id`, which rift's schema lacks;
|
||||||
decoding requires `observer_id` and `observed_id`, which rift's schema lacks; a message's
|
a message's `configuration` (reasoning per message) is not exposed; the tag `v1.0.0` names
|
||||||
`configuration` (reasoning per message) is not exposed; the tag `v1.0.0` names `b4e7864` on
|
`b4e7864` on `origin` and `1f5ea42` on crates.io.
|
||||||
`origin` and `1f5ea42` on crates.io.
|
4. **API keys**: the design supports them per workspace (section 10); rift has auth off today.
|
||||||
4. **Rift**: auth is still off; the keys in section 10 are ready for it.
|
|
||||||
|
|||||||
Reference in New Issue
Block a user