Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
231 lines
12 KiB
Markdown
231 lines
12 KiB
Markdown
# 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 08 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 08, and `recall` uses it from task 09.
|
|
|
|
- **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 `neutralise`d, 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 08
|
|
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):
|
|
|
|
```rust
|
|
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
|
|
|
|
```rust
|
|
pub mod read;
|
|
```
|
|
|
|
becomes
|
|
|
|
```rust
|
|
pub mod read;
|
|
pub mod recalled;
|
|
```
|
|
|
|
### `crates/memoryd/src/recall.rs`
|
|
|
|
**R1.** The lines
|
|
|
|
```rust
|
|
/// `2026-09-20 14:02 UTC` from `2026-09-20T14:02:00.000Z` or `2026-09-20T14:02Z`.
|
|
fn when(time: &str) -> String {
|
|
```
|
|
|
|
become
|
|
|
|
```rust
|
|
/// `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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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.
|