Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
127 lines
8.7 KiB
Markdown
127 lines
8.7 KiB
Markdown
# 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 `<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)
|
|
|
|
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 `<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.
|
|
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
|
|
|
|
`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
|
|
|
|
```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.)
|