Files
boxmaker/docs/plans/M5c1
kyleandClaude Opus 5.5 0a59dc0b22 M5c-1 plan: task 02, loopd logs the speaker and sends the turn's number
loopd copies the Turn's speaker onto the User record it logs, as it
came (not checked, not neutralised), and never into a request; after a
finished turn it sends SessionUpdated with the turn's number, the count
of User records in the session's log (Session::turns), so the first
turn is 1, a failed turn that was logged counts, and the count goes on
after a reopen or a restart. Failed turns are still not told.

Given: speaker_turn.rs (8 tests) and new versions of loopd's memory.rs,
turn_broker.rs, end_to_end.rs and support/turn.rs (run_turn gains
speaker, MemorySource::updated gains turn). Task 01 now copies
memory.rs-task01, its own version.

Checks: the reference passes make gate with 1138 tests (1130 + 8); the
task replayed from its text on a fresh ref-01 worktree with its own
target is byte-identical to the reference and passes the gate; /tmp
unchanged around both gate runs; 15 one-line mutations (speaker not
logged or dropped by the channel or neutralised, speaker in the request
at log time or at replay, turn 0-based or +1, counting Assistant
records, all records or only speakers' turns, a global or per-process
counter, told before logging or on failure, MemoryPort sending 0) all
caught; speaker_turn run 60 times under load, no failure; no
chat-template tag in the plan or given files; task lines within 100
characters but tables.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 23:49:35 -07:00
..

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.95, edition 2024; roncho 1.0.0 (default-features = false, features = ["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 (outline, for the owner's review)

# Delivers Crates
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
02 loopd logs the speaker on the User record and sends the turn number in SessionUpdated loopd
03 gatewayd sets the speaker: the Mattermost username when every post of the burst is from one person, else none gatewayd
04 bxctl chat --speaker NAME, opt-in, no default bxctl
05 brokerd serves run/memory-broker/state.sock from its state store; runbook broker-state-unavailable brokerd
06 memoryd's [honcho] section and its keys, checked at start; runbook honcho-config-invalid memoryd
07 The ledger, memory/honcho.jsonl: its records, append, read-through, and the outbox memoryd
08 The decision for a turn (a pure function: state, labels, speaker, text) memoryd
09 On SessionUpdated: ask brokerd, read the labels, decide, record; undecided turns retried memoryd
10 roncho clients per workspace, and creating workspaces, peers and sessions (names, the agent unobserved, the fleet with reasoning, summaries and dreaming off, the -2 sessions); the fake Honcho server memoryd
11 The sync worker for turns: messages in parts of 25,000, neutralised; backoff; outage logged once; refused sends and keys; runbooks honcho-unreachable, honcho-send-refused, honcho-key-refused memoryd
12 Memory writes mirrored: each journalled edit decided by spec 4's table (conclusions, fleet messages, retractions, undo) memoryd
13 The sync worker for memory writes: conclusions created and deleted, fleet messages memoryd
14 bxctl memory sync: decided, sent, waiting, failed memoryd, bxctl

After task 14, 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.

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.)