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:
@@ -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"),
|
||||
};
|
||||
|
||||
@@ -134,7 +134,7 @@ fn run_serve(path: &Path) -> ExitCode {
|
||||
eprintln!("loopd: serving on {}", socket.display());
|
||||
|
||||
let memory = Box::new(MemoryPort::new(cfg.memory_socket(), MEMORY_WAIT));
|
||||
let ctx = Arc::new(Context::new(cfg, client, port, Registry::m5a(), memory));
|
||||
let ctx = Arc::new(Context::new(cfg, client, port, Registry::m5c(), memory));
|
||||
if let Err(e) = channel::serve(listener, ctx) {
|
||||
eprintln!("loopd: the channel server stopped: {e}");
|
||||
return ExitCode::from(1);
|
||||
|
||||
@@ -106,6 +106,7 @@ impl MemorySource for MemoryPort {
|
||||
|
||||
pub const RECALL: &str = "recall";
|
||||
pub const MEMORY: &str = "memory";
|
||||
pub const ASK_MEMORY: &str = bxcore::memory::ASK_MEMORY;
|
||||
|
||||
pub fn recall_schema() -> ToolSchema {
|
||||
ToolSchema {
|
||||
@@ -125,6 +126,30 @@ pub fn recall_schema() -> ToolSchema {
|
||||
}
|
||||
}
|
||||
|
||||
/// `ask_memory` (M5c spec, section 9), as `bxcore::AskMemoryArgs` reads it: `question` required,
|
||||
/// `workspaces` at most two names. Short: every schema counts toward the baseline's budget.
|
||||
pub fn ask_memory_schema() -> ToolSchema {
|
||||
ToolSchema {
|
||||
name: ASK_MEMORY.to_string(),
|
||||
description: "Ask Honcho what you and the other agents of the fleet know. Answers can \
|
||||
take a minute or more."
|
||||
.to_string(),
|
||||
parameters: serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"question": { "type": "string" },
|
||||
"workspaces": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"maxItems": 2,
|
||||
"description": "own (the default), untrusted, fleet, or another agent's name."
|
||||
}
|
||||
},
|
||||
"required": ["question"]
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn memory_schema() -> ToolSchema {
|
||||
ToolSchema {
|
||||
name: MEMORY.to_string(),
|
||||
|
||||
@@ -83,7 +83,7 @@ impl Registry {
|
||||
}
|
||||
|
||||
/// The tool set for the M5a epoch: `clock`, `recall` and `memory` (core), then the four tools
|
||||
/// `brokerd` decides on. `loopd serve` uses this one.
|
||||
/// `brokerd` decides on.
|
||||
pub fn m5a() -> Registry {
|
||||
let mut entries = Registry::m3a().entries;
|
||||
entries.insert(
|
||||
@@ -103,6 +103,20 @@ impl Registry {
|
||||
Registry::new(entries)
|
||||
}
|
||||
|
||||
/// The tool set for the M5c epoch: `m5a()`'s, with `ask_memory` (core) after `memory`.
|
||||
/// `loopd serve` uses this one; a session keeps the tools array it started with.
|
||||
pub fn m5c() -> Registry {
|
||||
let mut entries = Registry::m5a().entries;
|
||||
entries.insert(
|
||||
3,
|
||||
Entry {
|
||||
schema: crate::memory::ask_memory_schema(),
|
||||
core: true,
|
||||
},
|
||||
);
|
||||
Registry::new(entries)
|
||||
}
|
||||
|
||||
/// The fixed tools array: core entries, then `find_tool`, then `call_tool`.
|
||||
pub fn core_schemas(&self) -> Vec<ToolSchema> {
|
||||
let mut schemas: Vec<ToolSchema> = self
|
||||
|
||||
Reference in New Issue
Block a user