Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
18 KiB
M5c-2 task 05: memoryd's readers, the Honcho workspaces it reads
Branch: m5c2. Check it: git branch --show-current prints m5c2 and git status --short
is empty; otherwise stop. Never switch branch.
Commit subject: memoryd: the Honcho workspaces it reads, with their clients and labels
Log row: the Task cell is M5c2/05-memoryd-honcho-readers.
Goal
memoryd writes to three Honcho workspaces through the Clients of M5c-1 (the agent's trusted
and untrusted ones and the fleet's), which move into the sync worker's thread. M5c-2 also reads
(M5c spec, sections 3, 8 and 9): recall searches other agents' workspaces and the fleet's, and
ask_memory asks one or two workspaces by name. Reading needs clients of its own. This task
builds them at start and gives later tasks the types they use; nothing searches or asks yet
(tasks 08 to 10).
Readers(new modulecrates/memoryd/src/honcho/read.rs, given whole): aReaderfor- each
[[honcho.read]]workspace, in the order written, with that entry'strustandclassand the keyhoncho_read_<agent>; - the fleet's workspace (key
honcho_fleet), always untrusted andprivate; - the agent's own trusted workspace (key
honcho_trusted), trusted,private, and its own untrusted one (keyhoncho_untrusted), untrusted,private. These two are forask_memoryonly:recallnever searches the agent's own workspaces (spec, section 8).
- each
- Two clients per reader. A
ronchoclient has one read timeout. Searches wait 5 s (SEARCH_READ) and chat 150 s (CHAT_READ, which this task changes from 180 s: spec 9 gives chat 150 s, insidebrokerd's 170 s); both connect within 5 s (CONNECT). - The label of each reader's results: the name each hit shows (the agent's name,
fleet,ownoruntrusted), the trust, and the class.Label::tag()ismemoryfor a trusted workspace andmemory-taintedotherwise, as a localrecallhit's tag. recall_sources(): the read workspaces, then the fleet's.resolve(name): the readerask_memorymeans byown,untrusted,fleetor a read agent's name; any other name is the sentence the model is shown, which lists the names it may use.- Reserved names. Since
ask_memorynames this agent's own workspacesown,untrustedandfleet, a[[honcho.read]]agent with one of those names is now refused at start (honcho-config-invalid). Otherwise a read agent calledfleetwould shadow the fleet inask_memory. - At start,
start::openbuilds the readers beside the writers'Clientsand keeps them inOpened::readers. A read key that cannot be read stopsmemoryd, as a written workspace's does. The three written workspaces' keys are read by both; each key file's warning is logged once.serve.rsdoes not change: nothing uses the readers until task 09, which takesopened.readersbefore the worker's thread takes the rest ofOpened.
Every place that changes
This is the complete list.
| Place | What happens there |
|---|---|
crates/memoryd/src/honcho/read.rs |
new, given whole: copy it |
crates/memoryd/src/honcho/mod.rs |
pub mod read; (edit H1) |
crates/memoryd/src/honcho/client.rs |
CHAT_READ is 150 s; client and key become pub(crate), for read.rs (edits C1 to C3) |
crates/memoryd/src/honcho/config.rs |
RESERVED, and the check that uses it (edits G1, G2) |
crates/memoryd/src/honcho/start.rs |
Opened::readers, built in open (edits O1 to O3) |
docs/runbook.md, honcho-config-invalid |
the reserved names among the reasons (edit D1) |
crates/memoryd/tests/honcho_read.rs |
new, given |
crates/memoryd/tests/honcho_client.rs, honcho_start.rs |
new versions, given |
| everything else | no change: not serve.rs, not recall.rs, not worker.rs, no Cargo.toml |
Files
- Copy, from
docs/plans/M5c2/files/(the same path under it): the new testcrates/memoryd/tests/honcho_read.rs; new versions of (they replace the files in the tree)crates/memoryd/tests/honcho_client.rsandcrates/memoryd/tests/honcho_start.rs; and the new sourcecrates/memoryd/src/honcho/read.rs, which is complete: it has notodo!(), and you do not change it. - Modify:
crates/memoryd/src/honcho/mod.rs,crates/memoryd/src/honcho/client.rs,crates/memoryd/src/honcho/config.rs,crates/memoryd/src/honcho/start.rs,docs/runbook.md,docs/implementer-log.md. - Do not touch: any other file.
Facts you need
-
read.rsneeds fromclient.rs:CONNECT,CHAT_READ,Timeouts, and the two helpersclient(url, workspace, key, timeouts) -> Result<Client, String>andkey(config, name, env, warnings) -> Result<Option<secrets::Secret>, String>, which are private today. Edits C2 and C3 make thempub(crate): visible inmemoryd, not outside it. Fromconfig.rsit needsFLEET_KEY,TRUSTED_KEY,UNTRUSTED_KEY,read_key,TrustandHonchoConfig, all public already, and frombxcoreDataClassandbxcore::memory::OWN_WORKSPACE("own", task 01). -
What
Readersoffers (for tasks 08 to 10; readread.rsfor the whole):pub const SEARCH_READ: Duration; // 5 s pub const UNTRUSTED: &str; // "untrusted" pub const FLEET: &str; // "fleet" pub struct ReadTimeouts { pub connect: Duration, pub search: Duration, pub chat: Duration } pub enum Source { Agent(String), Fleet, Own, OwnUntrusted } pub struct Label { pub name: String, pub trust: Trust, pub class: DataClass } impl Label { pub fn tag(&self) -> &'static str; pub fn untrusted(&self) -> bool } pub struct Reader { pub source: Source, pub label: Label, /* two clients */ } impl Reader { pub fn search(&self) -> &Client; pub fn chat(&self) -> &Client } impl Readers { pub fn new(config: &HonchoConfig, env: &dyn Fn(&str) -> Option<OsString>, timeouts: ReadTimeouts) -> Result<(Readers, Vec<String>), String>; pub fn recall_sources(&self) -> Vec<&Reader>; pub fn resolve(&self, name: &str) -> Result<&Reader, String>; } -
roncho::blocking::ClientisClone,SendandSync(its documentation says so, and a given test checks it at compile time), and itsDebugshows the timeouts and whether a key is present, never the key. This task's table ofroncho's API is complete;roncho's source (the Cargo registry,~/.cargo/registry) is outside the repository and off limits. -
Why the warnings are filtered.
Clients::newandReaders::neweach read the keys of the three written workspaces, and each returns a key file's plaintext warning. Edit O3 logs the writers' warnings, then only those of the readers' that are not among them. -
The given tests.
honcho_read.rs(8 tests), against the fake Honcho: the client and the readers areSend + Sync; the timeouts, read from each client'sDebug; each reader searches its own workspace with its own key (the fake records each request'sAuthorizationheader), and no header without a key; no key in a reader'sDebug, an error, or the error of a key that cannot be read;recall_sourcesnever holds the agent's own workspaces; each source's label and tag;resolvefor every name, and the exact sentence for an unknown one; a read agent namedown,untrustedorfleetrefused byconfig::check, with its sentence.honcho_client.rs: one line changes,CHAT_READis 150 s.honcho_start.rs: a new test,a_read_key_that_cannot_be_read_stops_memoryd, and one new assertion ina_valid_section_with_keys_starts_memoryd_as_before: the fleet key file's warning is printed once.
-
The items the edits use. This table is complete.
Item Where Readers::new,ReadTimeouts::DEFAULT(fromread.rs)O3 StartError::Secret(String)O3, as Clients::new's error beside itVec::contains,Iterator::filter,Iterator::chainO3 Arc::newO3 [&str; 3]::contains(&&str),String::as_strG2: RESERVED.contains(&entry.agent.as_str())
The edits, exactly
Nothing else in these files changes. Each quoted "before" block occurs exactly once in its file when its turn comes (make them in order). Keep the fields in the order shown.
crates/memoryd/src/honcho/mod.rs
H1. Add one line. The line
pub mod naming;
becomes
pub mod naming;
pub mod read;
crates/memoryd/src/honcho/client.rs
C1. The lines
/// A chat's read: a non-streamed chat sends nothing until it is done (M5c-2's `ask_memory`).
pub const CHAT_READ: Duration = Duration::from_secs(180);
become
/// A chat's read: a non-streamed chat sends nothing until it is done (M5c spec, section 9).
pub const CHAT_READ: Duration = Duration::from_secs(150);
C2. The lines
/// One workspace's client: no `roncho.toml` read, the timeouts given, the key when there is one.
fn client(
become
/// One workspace's client: no `roncho.toml` read, the timeouts given, the key when there is one.
pub(crate) fn client(
C3. The lines
/// The key named `name` in `[secrets]`, if one is configured, read now.
fn key(
become
/// The key named `name` in `[secrets]`, if one is configured, read now.
pub(crate) fn key(
crates/memoryd/src/honcho/config.rs
G1. The lines
/// The names of the keys in `[secrets.<name>]`, one per workspace. A read workspace's key is
become
/// The names `ask_memory` gives this agent's own workspaces and the fleet's (M5c spec, section 9),
/// which no `[[honcho.read]]` agent may have.
pub const RESERVED: [&str; 3] = ["own", "untrusted", "fleet"];
/// The names of the keys in `[secrets.<name>]`, one per workspace. A read workspace's key is
G2. In read_workspace, the lines (its spaces are checked: the sentence is one string split
across two lines with a \ at the end of the first, and the given test compares it letter for
letter)
"[[honcho.read]] agent {:?} is this agent ([honcho] agent)",
entry.agent
));
}
become
"[[honcho.read]] agent {:?} is this agent ([honcho] agent)",
entry.agent
));
}
if RESERVED.contains(&entry.agent.as_str()) {
return Err(format!(
"[[honcho.read]] agent {:?} is a name ask_memory gives this agent's own workspaces: \
own, untrusted and fleet cannot be agents' names",
entry.agent
));
}
crates/memoryd/src/honcho/start.rs
O1. Add one line. The line
use crate::honcho::mirror::Mirror;
becomes
use crate::honcho::mirror::Mirror;
use crate::honcho::read::{ReadTimeouts, Readers};
O2. The lines
pub struct Opened {
pub sync: Arc<Mutex<TurnSync>>,
clients: Clients,
become
pub struct Opened {
pub sync: Arc<Mutex<TurnSync>>,
/// The workspaces `recall` and `ask_memory` read (M5c spec, sections 8 and 9).
pub readers: Arc<Readers>,
clients: Clients,
O3. In open, the lines
for warning in warnings {
(log)(&warning);
}
Ok(Opened {
sync: Arc::new(Mutex::new(sync)),
clients,
become
let (readers, read_warnings) =
Readers::new(honcho, &env, ReadTimeouts::DEFAULT).map_err(StartError::Secret)?;
// The readers load the keys of the three written workspaces again: each warning once.
let read_warnings: Vec<String> = read_warnings
.into_iter()
.filter(|w| !warnings.contains(w))
.collect();
for warning in warnings.into_iter().chain(read_warnings) {
(log)(&warning);
}
Ok(Opened {
sync: Arc::new(Mutex::new(sync)),
readers: Arc::new(readers),
clients,
docs/runbook.md
D1. In the entry honcho-config-invalid, the lines (its spaces are checked: the entry is
read by people, and scripts/check-runbook.sh reads the file)
- A `[[honcho.read]]` names this agent, names an agent twice, or names one of this agent's three
workspaces; or its `trust` is not `trusted` or `untrusted`, or its `class` not `public`,
`private` or `secret` (all lower case).
become
- A `[[honcho.read]]` names this agent, names an agent twice, names an agent `own`, `untrusted`
or `fleet` (the names `ask_memory` gives this agent's own workspaces), or names one of this
agent's three workspaces; or its `trust` is not `trusted` or `untrusted`, or its `class` not
`public`, `private` or `secret` (all lower case).
Steps
-
1. Copy the given tests. From the repository root, three commands:
cp docs/plans/M5c2/files/crates/memoryd/tests/honcho_read.rs crates/memoryd/tests/ cp docs/plans/M5c2/files/crates/memoryd/tests/honcho_client.rs crates/memoryd/tests/ cp docs/plans/M5c2/files/crates/memoryd/tests/honcho_start.rs crates/memoryd/tests/Then
git status --shortlistshoncho_client.rsandhoncho_start.rsas modified andhoncho_read.rsas new, all undercrates/memoryd/tests/. -
2. See them fail. Two commands, each with a shell timeout of 600000 ms:
cargo build --tests --keep-going -p memoryd: one test target,honcho_read, does not compile:E0432forunresolved import `memoryd::honcho::read`and forunresolved import `memoryd::honcho::config::RESERVED`. Every other target compiles.cargo test --no-fail-fast -p memoryd --test honcho_client --test honcho_start: two tests fail:the_timeoutsinhoncho_client(CHAT_READis 180 s) anda_read_key_that_cannot_be_read_stops_memorydinhoncho_start(memoryddoes not read that key yet, so it starts, and the test stops it after 10 s).
-
3. The new module and the edits. Copy the module:
cp docs/plans/M5c2/files/crates/memoryd/src/honcho/read.rs crates/memoryd/src/honcho/Then make edits H1, C1 to C3, G1, G2, O1 to O3 and D1, in order.
cargo check -p memorydprints no error and no warning. -
4. The tests. With a shell timeout of 600000 ms, every test passes: 13 in
honcho_client, 8 inhoncho_config, 3 inhoncho_keys(and 1 ignored, as before), 8 inhoncho_read, 5 inhoncho_start:cargo test -p memoryd --test honcho_read --test honcho_client --test honcho_start \ --test honcho_config --test honcho_keys -
5. Check. Two commands:
git diff --stat -- crates/memoryd/srclists exactlyclient.rs,config.rs,mod.rsandstart.rsundercrates/memoryd/src/honcho/(the newread.rsis untracked, sogit diffdoes not list it), andgit status --short crates/memoryd/srcshowsread.rsas new.git diff --stat -- crates/memoryd/src/serve.rs crates/memoryd/src/recall.rs Cargo.toml Cargo.lockprints nothing.
-
6. Run the gate.
cargo fmt --all, thenmake gate, with a shell timeout of 600000 ms. Expected last line:gate: ok. The test count is the passed total ofcargo test --workspaceplus one, for the ignoredend_to_endtest that the gate runs on its own: 1403 after this task, 1394 before it; the 9 new ones are the 8 tests ofhoncho_read.rsand the new test inhoncho_start.rs. To count, add up theN passedof everytest result:line the gate'scargo test --workspaceprinted, and add 1. Never run the gate again only to count. -
7. Log and commit. Add your row to
docs/implementer-log.md; its Task cell isM5c2/05-memoryd-honcho-readers.git status --shortlists the four edited sources, the two replaced tests,docs/runbook.mdand your log as modified, andread.rsandhoncho_read.rsas new. Stage withgit add crates/memoryd docs/runbook.md docs/implementer-log.md Cargo.lock, then commit asAGENTS.mdsays.Cargo.lockdoes not change in this task; thegit addis harmless.
Done when
make gateprintsgate: okwith 1403 tests.git diff HEAD~1 --statlists only the four edited sources,read.rs, the three test files of step 1,docs/runbook.mdanddocs/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 when its turn comes, or is there more than once.
- Step 2 shows a different result: another target failing, other errors, other tests failing.
cargo checkin step 3 prints an error or a warning. Quote it, and stop. Do not changeread.rsto make it compile.- A test fails in step 4. Do not change a test: stop, and give the failing tests' names and their messages.
- The fix seems to need a change anywhere else:
serve.rs,recall.rs,worker.rs, a change toread.rs, a new dependency. It does not; stop and say why you think so. - A test outside the given files 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 1403.
- A comment in an edit does not fit the code under it. Quote it and stop.
What the given tests pin
Each of these changes to the finished code makes at least one given test fail (24 tried, all
caught): the search clients given chat's timeout, or chat's the search one; either timeout
constant changed; a read workspace given the fleet's key, the fleet the trusted key, or the own
untrusted workspace the trusted key; the own reader on the untrusted workspace; a key not sent;
a read workspace's trust or class ignored; the fleet trusted, or the own workspace untrusted;
the tags swapped, or untrusted() inverted; recall_sources holding the own workspace, or not
the fleet's; untrusted resolved to the trusted workspace; read agents not resolved, or
resolved by workspace instead of name; the sentence without the read agents' names; the
reserved names allowed; the readers' errors ignored at start; the key file's warning logged
twice.