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>
132 lines
4.3 KiB
Rust
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()
|
|
);
|
|
}
|