Files
boxmaker/docs/plans/M5c2/06-memoryd-honcho-recalled.md
T
2026-09-28 18:48:35 -07:00

12 KiB

M5c-2 task 06: Honcho's results as recall items

Branch: m5c2. Check it: git branch --show-current prints m5c2 and git status --short is empty; otherwise stop. Never switch branch. Commit subject: memoryd: Honcho's results as recall items, kept, labelled and interleaved Log row: the Task cell is M5c2/06-memoryd-honcho-recalled.

Goal

From task 07 on, recall searches other agents' Honcho workspaces and the fleet's beside the local index (M5c spec, section 8). This task writes the pure part: what recall makes of Honcho's results. A new module, crates/memoryd/src/honcho/recalled.rs, turns one source's results into items, interleaves the sources' items after the local hits, and labels the answer. It does no I/O; nothing calls it until task 07.

  • The inputs, per source: its Label (task 05); the messages Honcho's workspace search returned (roncho::Message); and for another agent's workspace, the conclusions of two queries, about that agent's peer and then about the owner's (roncho::Conclusion). The fleet has no conclusions: both lists are empty.
  • Relevance. Honcho's search has no relevance cutoff: a query that matches nothing still gets the nearest messages (recorded from rift, fixture 31). So a result is kept only when it shares a whole word with the query (spec 8, amended), as the local index's full-text match requires. Words are runs of alphanumeric characters (char::is_alphanumeric), lowercased; tea does not match steam or teatime.
  • Duplicates. Within one source, a result whose cleaned text equals an earlier kept one's is dropped. Fixture 30 holds the same message twice.
  • Each item: a header in the local format, [n] {tag} · {from} · {class}, then its text.
    • tag is the source's (memory when the owner trusts it, else memory-tainted), class its class, written as the local index writes one (index::class_name).
    • from is {name}: {peer} said, {when} for a message and {name}: a conclusion about {peer}, {when} for a conclusion. {name} is the source's name (the agent's, or fleet), so a fleet hit reads fleet: boxmaker2 said, …, naming the peer that wrote it. {peer} is the message's peer_id or the conclusion's observed_id, and {when} its created_at as a local hit shows a time (2026-09-28 23:19 UTC, recall::when).
    • The text is neutralised, every control character (line breaks among them) and Unicode line or paragraph separator becomes a space, and it is cut to its first 600 characters (recall::SNIPPET), as a local snippet is cut: no mark is added.
    • The text goes on the line after the header, indented by two spaces. So every line that starts with [ is a real header: a text shaped like a header ([1] memory · …, or a line break and then one) can never begin a line (tip T37). Local items keep their own format.
  • Order within a source: messages, then the conclusions about the agent's peer, then those about the owner's, each in Honcho's order.
  • Interleaving to k. The local index's hits come first, as today. Honcho's items fill what is left of k: each source's first, in recall_sources() order, then each source's second, and so on. If the local hits alone fill k, no Honcho item is shown: this is the spec's "local index first". Honcho's items are numbered after the local ones.
  • The label of the answer's Honcho part: the highest class of the items shown, private at the least, and untrusted when any shown item is. An item dropped as irrelevant, as a duplicate, or because k was full labels nothing (T58).
  • {name}: unavailable: one line per source that did not answer, after the items. Task 07 decides which did not.

Every place that changes

This is the complete list.

Place What happens there
crates/memoryd/src/honcho/recalled.rs new: a skeleton you copy, then fill 3 bodies
crates/memoryd/src/honcho/mod.rs pub mod recalled; (edit H1)
crates/memoryd/src/recall.rs when becomes pub(crate), for recalled.rs (edit R1)
crates/memoryd/tests/honcho_recalled.rs new, given
everything else no change: not serve.rs, not recall's own function, no Cargo.toml

Files

  • Copy, from docs/plans/M5c2/files/ (the same path under it): the new test crates/memoryd/tests/honcho_recalled.rs, and the skeleton crates/memoryd/src/honcho/recalled.rs.
  • Modify: crates/memoryd/src/honcho/mod.rs, crates/memoryd/src/recall.rs, docs/implementer-log.md.
  • Do not touch: any other file, and in the skeleton anything but its three todo!() bodies.

Facts you need

  • The skeleton is written except three bodies: items, interleave and label. Their comments say what to write. Everything else in it (Item, Item::render, words, relevant, clean, message_from, conclusion_from, render, unavailable) is written; read it before you fill the bodies.

  • Why words is new. The local index's recall::terms splits a query the same way but returns the FTS5 expression SQLite reads (quoted terms joined by OR), not the words, and does not lowercase them: SQLite's tokenizer folds case itself. words gives the words, lowercased, so that both sides of a comparison are.

  • roncho's types, as the bodies use them (this table and the next are complete; roncho's source, in the Cargo registry, is outside the repository and off limits):

    pub struct Message { pub id: String, pub content: String, pub peer_id: String,
                         pub session_id: String, pub created_at: String, /* more */ }
    pub struct Conclusion { pub id: String, pub content: String, pub observer_id: String,
                            pub observed_id: String, pub created_at: String, /* more */ }
    

    Both are roncho::Message and roncho::Conclusion, deserialised from Honcho's JSON; the given test reads them from the recorded responses in crates/memoryd/tests/fixtures/honcho/.

  • Label (task 05, crate::honcho::read::Label): name: String, trust, class: DataClass; tag() -> &'static str and untrusted() -> bool.

  • DataClass is ordered Public < Private < Secret; DataClass::max(a, b) is the higher.

  • The calls the bodies use:

    Item What it does
    words(&str) -> Vec<String>, relevant(&[String], &str) -> bool, clean(&str) -> String in the skeleton
    message_from(&Label, &Message) -> String, conclusion_from(&Label, &Conclusion) -> String in the skeleton
    slice::iter, Iterator::map, Iterator::chain, Iterator::any, Vec::push items
    Vec::into_iter, std::vec::IntoIter<Item>, Iterator::next, usize::saturating_sub interleave
    Iterator::fold(DataClass::Private, DataClass::max), Iterator::any label
  • The given test honcho_recalled.rs (11 tests): words and whole-word relevance; fixture 31 keeps nothing; fixture 30 keeps its two different messages once each, with their exact items and a rendered one; the order messages, agent's conclusions, owner's (fixtures 30, 33, 32); a duplicate across a message and a conclusion; a fleet hit, tainted and naming its writer; neutralising, one line, the 600-character cap; forged headers kept inside their items (T37); interleaving round-robin after the local hits, k full, and numbering; the label from shown items only, with a tainted secret item past k and then within it (T58); the unavailable lines. A chat-template tag in it is built from pieces, and no assertion prints one.

The edits, exactly

Nothing else in these files changes. Each quoted "before" block occurs exactly once in its file.

crates/memoryd/src/honcho/mod.rs

H1. Add one line. The line

pub mod read;

becomes

pub mod read;
pub mod recalled;

crates/memoryd/src/recall.rs

R1. The lines

/// `2026-09-20 14:02 UTC` from `2026-09-20T14:02:00.000Z` or `2026-09-20T14:02Z`.
fn when(time: &str) -> String {

become

/// `2026-09-20 14:02 UTC` from `2026-09-20T14:02:00.000Z` or `2026-09-20T14:02Z`.
pub(crate) fn when(time: &str) -> String {

Steps

  • 1. Copy the given test. From the repository root, one command:

    cp docs/plans/M5c2/files/crates/memoryd/tests/honcho_recalled.rs crates/memoryd/tests/
    
  • 2. See it fail. cargo build --tests --keep-going -p memoryd, with a shell timeout of 600000 ms: one test target, honcho_recalled, does not compile: E0432, unresolved import `memoryd::honcho::recalled` . Every other target compiles.

  • 3. The skeleton and the edits. Copy the skeleton, then make edits H1 and R1:

    cp docs/plans/M5c2/files/crates/memoryd/src/honcho/recalled.rs crates/memoryd/src/honcho/
    

    cargo check -p memoryd: no error, and 11 warnings, all from the unfilled bodies: unused variables label, query, messages, agent, owner, local, k, sources and items, and the functions message_from and conclusion_from, never used.

  • 4. Fill the bodies, one at a time, from their comments, with cargo check -p memoryd after each: items, then interleave, then label. Delete each instruction comment and keep the doc comment above the function. After the last, cargo check -p memoryd prints no warning.

  • 5. The tests. With a shell timeout of 600000 ms, every test passes: 11 in honcho_recalled, and 6 in recall, as before:

    cargo test -p memoryd --test honcho_recalled --test recall
    
  • 6. Check. git diff --stat lists exactly crates/memoryd/src/honcho/mod.rs and crates/memoryd/src/recall.rs (1 line each), and git status --short shows recalled.rs and honcho_recalled.rs as new.

  • 7. Run the gate. cargo fmt --all, then make gate, with a shell timeout of 600000 ms. Expected last line: gate: ok. The test count is the passed total of cargo test --workspace plus one, for the ignored end_to_end test that the gate runs on its own: 1414 after this task, 1403 before it; the 11 new ones are the tests of honcho_recalled.rs. To count, add up the N passed of every test result: line the gate's cargo test --workspace printed, and add 1. Never run the gate again only to count.

  • 8. Log and commit. Add your row to docs/implementer-log.md; its Task cell is M5c2/06-memoryd-honcho-recalled. Stage with git add crates/memoryd docs/implementer-log.md Cargo.lock, then commit as AGENTS.md says. Cargo.lock does not change in this task; the git add is harmless.

Done when

  • make gate prints gate: ok with 1414 tests.
  • git diff HEAD~1 --stat lists only mod.rs, recall.rs, recalled.rs, honcho_recalled.rs and docs/implementer-log.md.

Stop and report if

  • The branch check at the top fails.
  • A "before" block is not in its file exactly as shown, or is there more than once.
  • Step 2 or 3 shows a different result: other targets failing, other errors, or warnings about anything but the unfilled bodies.
  • cargo check reports an error after a body, and its comment does not tell you how to fix it. Quote the error, and stop.
  • After step 4 a test fails although each body follows its comment. Do not change a test or the written parts of the skeleton: stop, and give the failing tests' names and their messages.
  • The fix seems to need a change anywhere else: serve.rs, recall's own function, read.rs, a new dependency. It does not; stop and say why you think so.
  • A test outside honcho_recalled.rs fails in the gate: check once whether it fails without your change (git stash -u, run it, git stash pop), then stop and report both results.
  • The gate passes with a count other than 1414.
  • A comment in the skeleton does not fit its signature. Quote it and stop.

What the given tests pin

Each of these changes to the finished code makes at least one given test fail (22 tried, all caught): words not lowercased; a substring match instead of whole words; no relevance check; no duplicate check; no neutralise; a line break kept; the text not indented; the cap moved by one; the owner's conclusions before the agent's, or the conclusions left out; a source's tag, trust or class ignored; the wrong peer in from; interleaving that ignores the local hits, takes one source's items all at once, or stops at the first empty source; numbering from 1 instead of after the local hits; the label's floor public, or untrusted only when every item is; the unavailable line's wording.