limits: unified 1 KiB cap on send/ask + reminder auto-file on overflow

This commit is contained in:
damocles 2026-05-17 11:36:12 +02:00
parent 753409a5ef
commit 0e6bac8388
6 changed files with 180 additions and 42 deletions

61
hive-c0re/src/limits.rs Normal file
View file

@ -0,0 +1,61 @@
//! Wire-protocol size limits shared across the agent + manager
//! sockets. Caps on inline message bodies stop a single chatty agent
//! (or a misbehaving extra-MCP server) from flooding the broker
//! sqlite with megabyte-sized rows that then bloat every recipient's
//! wake-prompt context. Anything genuinely larger should be written
//! to a state file and the path sent as the body.
//!
//! Reminders get a separate auto-file escape hatch (see
//! `agent_server::handle_remind`) so callers don't have to think
//! about it — oversized reminder bodies get persisted to disk
//! transparently and the inbox sees a pointer.
/// Per-message body cap. Applies to `send`, `ask_operator` question
/// text, and the stored inline form of a reminder. 1 KiB is small
/// enough that 100 unread messages don't dominate a wake prompt,
/// large enough for routine cross-agent chatter.
pub const MESSAGE_MAX_BYTES: usize = 1024;
/// Validate that `body` fits under [`MESSAGE_MAX_BYTES`]. Returns a
/// caller-ready error string (caller wraps in
/// `AgentResponse::Err`/`ManagerResponse::Err`) on failure.
///
/// `label` shows up in the error message verbatim — pass a short
/// noun like `"send"`, `"question"`, `"broadcast"` so the model can
/// tell which call got rejected.
pub fn check_size(label: &str, body: &str) -> Result<(), String> {
if body.len() > MESSAGE_MAX_BYTES {
Err(format!(
"{label} body too long ({} bytes, max {MESSAGE_MAX_BYTES}); write the \
payload to a file under your `/agents/<you>/state/` dir and send the \
path as the body instead",
body.len()
))
} else {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accepts_short_body() {
assert!(check_size("send", "hello").is_ok());
assert!(check_size("send", &"x".repeat(MESSAGE_MAX_BYTES)).is_ok());
}
#[test]
fn rejects_oversize_body() {
let err = check_size("send", &"x".repeat(MESSAGE_MAX_BYTES + 1)).unwrap_err();
assert!(err.contains("send body too long"));
assert!(err.contains(&format!("max {MESSAGE_MAX_BYTES}")));
}
#[test]
fn label_threads_through() {
let err = check_size("question", &"x".repeat(MESSAGE_MAX_BYTES + 1)).unwrap_err();
assert!(err.starts_with("question body too long"));
}
}