loopd: ask_memory as a core tool, and a wait of its own on brokerd

Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
This commit is contained in:
2026-09-28 22:41:37 -07:00
co-authored by ornith-1.5-35b-a3b
parent 7ac7e8094a
commit b33341c53a
9 changed files with 359 additions and 7 deletions
+30 -3
View File
@@ -22,6 +22,10 @@ pub const TOO_LARGE: &str = "the request is too large for the tool broker";
/// The longest `BrokerPort` waits for an answer after a pending frame, whatever its `expires`
/// says, so a turn cannot be parked for ever by a far expiry.
pub const MAX_PENDING_WAIT: Duration = Duration::from_secs(24 * 60 * 60);
/// The longest `BrokerPort` waits for an `ask_memory` answer, unless `[broker] timeout_ms` is
/// longer: `brokerd` waits 170 s for `memoryd`, whose Honcho chats have 150 s (M5c spec,
/// section 9).
pub const ASK_MEMORY_WAIT: Duration = Duration::from_secs(180);
/// The runbook entry every outage line ends with.
pub const POINTER: &str = "see docs/runbook.md#broker-unavailable";
@@ -58,6 +62,7 @@ impl std::io::Read for Deadline<'_> {
pub struct BrokerPort {
socket: PathBuf,
timeout: Duration,
ask_timeout: Duration,
pending_cap: Duration,
log: Box<dyn Fn(&str) + Send + Sync>,
}
@@ -67,11 +72,31 @@ impl BrokerPort {
BrokerPort {
socket,
timeout,
ask_timeout: ASK_MEMORY_WAIT,
pending_cap: MAX_PENDING_WAIT,
log: Box::new(|l| eprintln!("{l}")),
}
}
/// The same port with another wait for `ask_memory` (`ASK_MEMORY_WAIT`). The tests use a short
/// one.
pub fn with_ask_wait(self, ask_timeout: Duration) -> BrokerPort {
BrokerPort {
ask_timeout,
..self
}
}
/// The wait for one call of `tool`, used wherever the port waits for that call: the timeout,
/// or for `ask_memory` the longer of the timeout and its own wait.
pub fn timeout_for(&self, tool: &str) -> Duration {
if tool == crate::memory::ASK_MEMORY {
self.timeout.max(self.ask_timeout)
} else {
self.timeout
}
}
/// The same port with another cap on the wait after a pending frame (`MAX_PENDING_WAIT`).
pub fn with_pending_cap(self, pending_cap: Duration) -> BrokerPort {
BrokerPort {
@@ -88,6 +113,7 @@ impl BrokerPort {
BrokerPort {
socket,
timeout,
ask_timeout: ASK_MEMORY_WAIT,
pending_cap: MAX_PENDING_WAIT,
log,
}
@@ -97,9 +123,10 @@ impl BrokerPort {
impl ToolPort for BrokerPort {
fn call(&self, request: &ToolRequest, on_pending: &mut dyn FnMut(&Pending)) -> ToolResponse {
let want = request.call.0;
let timeout = self.timeout_for(&request.tool);
// The first deadline, taken before connecting.
let mut until = match Instant::now().checked_add(self.timeout) {
let mut until = match Instant::now().checked_add(timeout) {
Some(until) => until,
None => return self.unavailable("the timeout is too large"),
};
@@ -114,7 +141,7 @@ impl ToolPort for BrokerPort {
};
// Exit 3. `set_write_timeout` fails for a zero timeout.
if let Err(e) = stream.set_write_timeout(Some(self.timeout)) {
if let Err(e) = stream.set_write_timeout(Some(timeout)) {
return self.unavailable(&format!("cannot set a timeout: {e}"));
}
@@ -191,7 +218,7 @@ impl ToolPort for BrokerPort {
Some(until) => until,
None => return self.unavailable("an expiry too far away"),
};
until = match until.checked_add(self.timeout) {
until = match until.checked_add(timeout) {
Some(until) => until,
None => return self.unavailable("an expiry too far away"),
};