# 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 is `tools/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 `bxcore` only; `memoryd` also on `roncho` (task 00). No `unsafe`, no async runtime. - Every `roncho` client is built with `.without_config_file()` (spec 6). - Nothing is logged that holds memory text or a key: `roncho` errors are logged by kind and status only (spec 6). - Nothing above `private` is written, and never a `secret` session'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 `_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, `-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) 1. **`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; a `bxctl chat` turn without the flag has no speaker and is not synced (spec 4). Task 04. 2. **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) 1. **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 sync` counts them. 2. **Conclusions go to `_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. 3. **No retraction.** A note cannot be undone (notes are append-only), so nothing is retracted, and the row went from spec section 4. `Action::Retract` stays in the ledger's schema, unused; the worker leaves one waiting. Tasks 12, 13. 4. **Sends are at least once.** The worker records `sent` after Honcho answers: a crash between the two, or an owner removing a `sent` line, sends the item again (Honcho cannot delete a message). Tasks 07, 11. ## Changes to the outline - The runbook entry `broker-state-unavailable` moved 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 (fixed in review) `gatewayd`'s `gateway_sock` test `brokerd_closing_first_ends_it_as_no_longer_pending` failed now and then on a loaded machine (2 of 10 runs without any M5c-1 change, at a load average near 10, on talos), and `loopd`'s `pointers` once in task 15. Tasks 13 to 15 told the implementer to run the gate once more if it was the one test that failed. Both were fixed in the review (review finding 7 in `docs/implementer-log.md`): the first was a race in `gatewayd`'s handler, which said an approval had ended while it still held the answer's receiver; the second was the serve tests stopping `loopd` as soon as its socket appeared, before it had printed its startup lines. ## 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 ```sh 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.)