Files
kyleandClaude Opus 5.5 3dd95c5791 review: a memory write wakes the Honcho worker
A write, an undo or an accepted write decided into the ledger waited out the
worker's 30 s idle sleep. Mirror::journalled now says whether it added a line,
and memoryd wakes the worker when it did. The Wake lock is a leaf, taken under
the index lock as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 14:07:38 -07:00

132 lines
4.3 KiB
Rust

//! A memory write wakes the Honcho worker (M5c spec, section 4: a write reaches Honcho "at once";
//! review of M5c-1, finding 3): with the worker asleep for its 30 s idle wait, the memory tool's
//! conclusion and the owner's undo's deletion both reach the fake Honcho within 5 s.
#[path = "support/honcho.rs"]
mod honcho;
#[path = "support/tmp.rs"]
mod tmp;
use std::os::unix::net::UnixStream;
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use bxcore::{
CallId, DataClass, Envelope, MemoryAdmin, MemoryCall, MemoryCommand, Message, SessionId,
ToolRequest, read_frame, write_frame,
};
use honcho::FakeHoncho;
use memoryd::config::Config;
use memoryd::honcho::worker::Tuning;
use memoryd::serve;
use tmp::TempDir;
fn ask(socket: &Path, msg: Message) -> Message {
let mut stream = UnixStream::connect(socket).unwrap();
stream
.set_read_timeout(Some(Duration::from_secs(10)))
.unwrap();
let env = Envelope {
v: 1,
id: 1,
r#final: true,
msg,
};
write_frame(&mut stream, &env).unwrap();
read_frame(&mut stream).unwrap().msg
}
/// How many requests of `method` to a path ending `suffix` the fake has had.
fn count(fake: &FakeHoncho, method: &str, suffix: &str) -> usize {
fake.requests()
.iter()
.filter(|r| {
r.method == method && r.path.contains("/conclusions") && r.path.ends_with(suffix)
})
.count()
}
/// Waits up to 5 s for `done`; true if it came.
fn within_5s(done: impl Fn() -> bool) -> bool {
let deadline = Instant::now() + Duration::from_secs(5);
while Instant::now() < deadline {
if done() {
return true;
}
std::thread::sleep(Duration::from_millis(10));
}
done()
}
#[test]
fn a_memory_write_and_an_undo_wake_the_worker() {
// The worker `serve` starts waits this long when idle: far past every deadline here.
assert!(Tuning::DEFAULT.idle >= Duration::from_secs(30));
let fake = FakeHoncho::start();
let dir = TempDir::new("wake-serve");
let soul = dir.write("etc/soul.md", "You are Boxmaker.\n");
let text = format!(
"[paths]\nhome = \"{}\"\n[memory]\nsoul_starter = \"{}\"\n[honcho]\nurl = \"{}\"\nagent = \"boxmaker0\"\nowner = \"kyle\"\ntrusted = \"boxmaker0-trusted\"\nuntrusted = \"boxmaker0-untrusted\"\nfleet = \"fleet\"\n",
dir.path().display(),
soul.display(),
fake.url
);
let config = Config::parse(&text).unwrap();
let lines = Arc::new(Mutex::new(Vec::new()));
let sink = Arc::clone(&lines);
let log: serve::Log = Arc::new(move |l: &str| sink.lock().unwrap().push(l.to_string()));
let running = serve::start(config.clone(), log).unwrap();
std::thread::spawn(move || running.run());
// Let the worker find nothing to send and fall asleep.
std::thread::sleep(Duration::from_millis(200));
let call = Message::MemoryCall(MemoryCall {
request: ToolRequest {
session: SessionId::new("mm-a").unwrap(),
call: CallId(1),
tool: "memory".to_string(),
arguments: r#"{"action":"add","file":"user","text":"Prefers UTC."}"#.to_string(),
},
taint: DataClass::Private,
untrusted: false,
});
assert!(matches!(
ask(&config.broker_socket(), call),
Message::ToolResponse(_)
));
assert!(
within_5s(|| count(&fake, "POST", "/conclusions") == 1),
"the conclusion waited: {:?} {:?}",
fake.requests(),
lines.lock().unwrap()
);
std::thread::sleep(Duration::from_millis(200));
let log = ask(
&config.admin_socket(),
Message::MemoryAdmin(MemoryAdmin {
command: MemoryCommand::Log,
arg: String::new(),
}),
);
let Message::MemoryAdminReply(reply) = log else {
panic!("{log:?}")
};
let id = reply.text.split(" ").next().unwrap().to_string();
let undone = ask(
&config.admin_socket(),
Message::MemoryAdmin(MemoryAdmin {
command: MemoryCommand::Undo,
arg: id,
}),
);
assert!(matches!(undone, Message::MemoryAdminReply(_)), "{undone:?}");
assert!(
within_5s(|| count(&fake, "DELETE", "") == 1),
"the deletion waited: {:?} {:?}",
fake.requests(),
lines.lock().unwrap()
);
}