Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
8.7 KiB
M5c-1 implementation plan: Honcho, the write path
For the implementer: each task file is one closed task for one fresh session. Read
AGENTS.md, then the task file, and do exactly that task. The driver istools/run-plan.sh.
Goal: finished turns and memory writes reach Honcho: turns to the agent's trusted or untrusted
workspace by the session's state at the turn's end (never a secret session's), trusted memory
writes mirrored as conclusions at once, untrusted notes to the fleet workspace as messages, all
through an append-only ledger that is also the outbox.
Architecture: bxcore gains the speaker on a turn, the turn number on SessionUpdated, and a
session-state question; loopd, gatewayd and bxctl chat carry the speaker; brokerd answers
the question on a new socket; memoryd gains a honcho module (configuration, ledger, decision,
roncho clients, a sync worker, mirrored memory writes) and bxctl memory sync shows its state.
Tech stack: Rust 1.98, edition 2024; roncho 1.0.0 (default-features = false, features = ["tls"], which brings blocking), added by the design model in task 00; no other new crate.
Spec: docs/specs/2026-09-27-m5c-honcho.md (approved 2026-09-27), sections 3 to 7 and 10 to
13. The read path (recall over the fleet, ask_memory) is M5c-2, a later plan.
Global constraints
- Roles depend on
bxcoreonly;memorydalso onroncho(task 00). Nounsafe, no async runtime. - Every
ronchoclient is built with.without_config_file()(spec 6). - Nothing is logged that holds memory text or a key:
ronchoerrors are logged by kind and status only (spec 6). - Nothing above
privateis written, and never asecretsession's turn (spec 3). - No file the implementer reads holds a chat-template tag whole (T33); no fake key whole.
- Every path a given test creates is removed by a guard (T44); every process a test starts has a deadline.
- Every fail-closed state has a runbook entry, added by the task that adds its pointer (T49's lesson from M5b: the gate needs the entry with the pointer).
- Commit trailer from the driver; gate with a 600000 ms timeout.
Tasks
The gate count is make gate's tests after the task. Mutations are one-line breaks of the
reference, each run against the task's given tests (checklist, T28); those not caught cannot change
behaviour, or are checked by the task's own greps instead, as the task says.
| # | Delivers | Crates | Gate | Mutations |
|---|---|---|---|---|
| 00 | (the design model, before the run) roncho added: workspace dependency, docs/dependencies.md, docs/egress.md (rift), cargo deny, the Nix build on straylight; saved Honcho responses for the fake server (below) |
all | ||
| 01 | Wire: Turn.speaker, LogRecord::User.speaker (optional, absent in old logs), SessionUpdated.turn, SessionState → SessionStateReply; fixtures |
bxcore | 1130 | |
| 02 | loopd logs the speaker on the User record and sends the turn number in SessionUpdated |
loopd | 1138 | |
| 03 | gatewayd sets the speaker: the Mattermost username when every post of the burst is from one person, else none |
gatewayd | 1148 | |
| 04 | bxctl chat --speaker NAME, opt-in, no default |
bxctl | 1158 | |
| 05 | brokerd serves run/memory-broker/state.sock from its state store; brokerd-listener-lost and socket-forbidden name it |
brokerd | 1171 | |
| 06 | memoryd's [honcho] section and its keys, checked at start; runbook honcho-config-invalid |
memoryd | 1188 | |
| 07 | The ledger, memory/honcho.jsonl: its records, append, read-through, and the outbox; runbook honcho-ledger-damaged |
memoryd | 1212 | 45: 41 caught; 3 (no sync, two writes) checked by the task's greps; 1 cannot change behaviour |
| 08 | The decision for a turn (a pure function: state, labels, speaker, text) | memoryd | 1230 | 36: all caught |
| 09 | On SessionUpdated, after loopd has its answer: ask brokerd, read the log, decide, record; undecided turns retried at the next report of any session; the ledger read through at start; runbook broker-state-unavailable |
memoryd | 1257 | 42: all caught |
| 10 | roncho clients per workspace, and making sure of workspaces, peers and sessions (the agent unobserved, the fleet with reasoning, summaries and dreaming off, the -2 sessions); the fake Honcho server |
memoryd | 1278 | 44: 43 caught, 1 cannot change behaviour |
| 11 | The worker for turns: messages in parts of 25,000, neutralised, one batch; backoff; outage logged once; refused sends and keys; runbooks honcho-unreachable, honcho-send-refused, honcho-key-refused |
memoryd | 1300 | 36: 35 caught, 1 cannot change behaviour |
| 12 | Memory writes decided as they are journalled, by spec 4's table (conclusions, their deletion, fleet messages, undo); the ledger's own lock | memoryd | 1314 | 21: 17 caught, 4 cannot change behaviour |
| 13 | The worker for memory writes: conclusions made in <id>_memory and deleted, fleet messages |
memoryd | 1324 | 23: all caught |
| 14 | bxctl memory sync: the ledger's counts and its failed items |
memoryd, bxctl, bxcore | 1332 | 14: all caught |
| 15 | Sync begins when Honcho is turned on (began, and a skip for older turns); conclusions matched by peer |
memoryd | 1345 | 15: all caught |
A later task that changes a given file gives the earlier task its own copy, <name>-taskNN (T19),
and that task's copy command names it.
After task 15, the design model runs the M5c-1 device check (spec 13) on straylight, rift and the owner's Mattermost.
The owner's answers (2026-09-27)
bxctl chat's speaker is opt-in:--speaker NAME, with no default. Console turns are mostly tests, and Honcho can neither forget them nor be shared without them; abxctl chatturn without the flag has no speaker and is not synced (spec 4). Task 04.- Saved Honcho responses for the fake server: the design model records real responses from
rift in task 00, in a throwaway workspace,
boxmaker-plan-probe, that it creates and deletes. It touches no other workspace on rift, in any way (not even a read): rift has no keys yet, so the rule is ours to keep.
The owner's decisions (2026-09-28)
- Sync starts when Honcho is turned on. The ledger's first line records when sync began, and
a turn from before it is skipped: turning on
[honcho]never sends earlier conversations. Task 15. The first report of a session that ran before then decides a skip for each of its earlier turns;bxctl memory synccounts them. - Conclusions go to
<id>_memory, the Boxmaker session's own Honcho session, holding the agent's and the owner's peers, made on first use whether or not a turn of the session is synced. An undo's conclusions carry the undone edit's session. Tasks 12, 13. - No retraction. A note cannot be undone (notes are append-only), so nothing is retracted,
and the row went from spec section 4.
Action::Retractstays in the ledger's schema, unused; the worker leaves one waiting. Tasks 12, 13. - Sends are at least once. The worker records
sentafter Honcho answers: a crash between the two, or an owner removing asentline, sends the item again (Honcho cannot delete a message). Tasks 07, 11.
Changes to the outline
- The runbook entry
broker-state-unavailablemoved from task 05, which did not add it, to task 09, which adds the pointer (the rule: the entry comes with the task that writes its pointer). - Task 12 decides only; task 13 sends. Task 15 was added for the owner's decision 1 and for the matching of conclusions by peer found while writing task 13.
Known issue
gatewayd's gateway_sock test brokerd_closing_first_ends_it_as_no_longer_pending fails now and
then on a loaded machine (2 of 10 runs without any M5c-1 change, at a load average near 10, on
talos): after Ended arrives, the handler can still hold the answer's receiver for a moment. It is
a timing race in the test, not in memoryd. Tasks 13 to 15 tell the implementer: if it is the one
test that fails, run the gate once more. To be fixed after M5c-1.
Before hand-over
Every task is worked through docs/plans/CHECKLIST.md with a reference in a scratch worktree
with its own build directory, replayed from its task text; mutations; /tmp compared around a
gate run; scratch files in the worktree's .state/; the reference moved to straylight as a bundle
before the run. Task files name their log row (T47); a scanner-like function gets a golden file
(T46); a request through another role is counted against its limits (T49).
Running it
git switch -c m5c1 master
OPENCODE_MODEL=straylight/ornith-1.5-35b-a3b RESUME=1 \
SLOTS_URL='http://straylight:11434/slots?model=ornith-1.5-35b-a3b' \
tools/run-plan.sh docs/plans/M5c1 01
(Or titan's Ornith, titan/… and http://127.0.0.1:8081/…, once its GPU is free.)