drop the host side of the agent broker send
Remove the agent-side Send and AckUntil requests, their hive-c0re handlers (handle_send with its 4 KiB size cap, handle_ack_until) and Broker::ack_until. Everything that existed only for them goes with them: the OkWarn response (its sole producer was Send's reserved-name warning), Recv's max parameter and RECV_BATCH_MAX (the harness never asked for more than one message), OPERATOR_SEND_REFUSAL, and the MESSAGE_MAX_BYTES / check_size limit (the reminder timer keeps its own copy of the cap, so its comment is reworded). Messages.remaining stays on the wire (default on read) because a harness built before this change still requires it. Update the conventions docs.
This commit is contained in:
parent
aeef4634e1
commit
1c3b5e52cf
1 changed files with 58 additions and 446 deletions
|
|
@ -190,11 +190,6 @@ async fn serve(stream: UnixStream, agent: String, coord: Arc<Coordinator>) -> Re
|
|||
/// positive `wait_seconds`.
|
||||
pub(crate) const RECV_LONG_POLL_MAX: std::time::Duration = std::time::Duration::from_mins(3);
|
||||
|
||||
/// Server-side hard cap on `Recv.max` — canonical value lives in
|
||||
/// `hive_sh4re::inbox::RECV_BATCH_MAX` so the harness's wake-prompt hint and
|
||||
/// this enforcement site can't drift apart.
|
||||
pub(crate) const RECV_BATCH_MAX: u32 = hive_sh4re::inbox::RECV_BATCH_MAX;
|
||||
|
||||
pub(crate) fn recv_timeout(wait_seconds: Option<u64>) -> std::time::Duration {
|
||||
match wait_seconds {
|
||||
Some(s) => std::time::Duration::from_secs(s).min(RECV_LONG_POLL_MAX),
|
||||
|
|
@ -210,13 +205,8 @@ async fn dispatch(
|
|||
coord: &Arc<Coordinator>,
|
||||
) -> hive_core_agent_sock::Response {
|
||||
match req {
|
||||
hive_core_agent_sock::Request::Send {
|
||||
to,
|
||||
body,
|
||||
in_reply_to,
|
||||
} => handle_send(coord, agent, to, body, *in_reply_to),
|
||||
hive_core_agent_sock::Request::Recv { wait_seconds, max } => {
|
||||
handle_recv(coord, agent, *wait_seconds, *max).await
|
||||
hive_core_agent_sock::Request::Recv { wait_seconds } => {
|
||||
handle_recv(coord, agent, *wait_seconds).await
|
||||
}
|
||||
hive_core_agent_sock::Request::Status => handle_status(coord, agent),
|
||||
hive_core_agent_sock::Request::Wake { from, body } => handle_wake(coord, agent, from, body),
|
||||
|
|
@ -226,7 +216,6 @@ async fn dispatch(
|
|||
handle_get_agent_meta(coord, agent, name.as_ref()).await
|
||||
}
|
||||
hive_core_agent_sock::Request::AckTurn => handle_ack_turn(coord, agent),
|
||||
hive_core_agent_sock::Request::AckUntil { up_to } => handle_ack_until(coord, agent, *up_to),
|
||||
hive_core_agent_sock::Request::RequeueInflight => handle_requeue_inflight(coord, agent),
|
||||
hive_core_agent_sock::Request::GracefulStopComplete => {
|
||||
// Harness drained + is exiting: clear the fence so the
|
||||
|
|
@ -246,13 +235,12 @@ async fn dispatch(
|
|||
}
|
||||
}
|
||||
|
||||
/// `Recv` — long-poll the broker for up to `max` messages (capped at
|
||||
/// `RECV_BATCH_MAX`), mapping deliveries onto the wire response.
|
||||
/// `Recv` — long-poll the broker for the next message, mapping the delivery
|
||||
/// onto the wire response.
|
||||
async fn handle_recv(
|
||||
coord: &Arc<Coordinator>,
|
||||
agent: &str,
|
||||
wait_seconds: Option<u64>,
|
||||
max: Option<u32>,
|
||||
) -> hive_core_agent_sock::Response {
|
||||
// Graceful-stop fence: while a graceful stop is pending for this agent,
|
||||
// return `GracefulStop` instead of polling the broker. The harness runs
|
||||
|
|
@ -263,32 +251,27 @@ async fn handle_recv(
|
|||
if coord.is_graceful_stop_pending(agent) {
|
||||
return hive_core_agent_sock::Response::GracefulStop;
|
||||
}
|
||||
let cap = max.unwrap_or(1).min(RECV_BATCH_MAX) as usize;
|
||||
match coord
|
||||
.broker
|
||||
.recv_blocking_batch(agent, recv_timeout(wait_seconds), cap)
|
||||
.recv_blocking_batch(agent, recv_timeout(wait_seconds), 1)
|
||||
.await
|
||||
{
|
||||
Ok(deliveries) => {
|
||||
// `recv_batch` stamps `delivered_at` on every popped row, so a
|
||||
// `count_pending` here excludes the just-popped batch and reports
|
||||
// exactly how many still-pending messages remain to drain. A count
|
||||
// error is non-fatal — fall back to 0 rather than fail the recv.
|
||||
let remaining = coord.broker.count_pending(agent).unwrap_or(0);
|
||||
hive_core_agent_sock::Response::Messages {
|
||||
messages: deliveries
|
||||
.into_iter()
|
||||
.map(|d| hive_sh4re::inbox::DeliveredMessage {
|
||||
from: d.message.from.to_string(),
|
||||
body: d.message.body,
|
||||
id: d.id,
|
||||
redelivered: d.redelivered,
|
||||
in_reply_to: d.message.in_reply_to,
|
||||
})
|
||||
.collect(),
|
||||
remaining,
|
||||
}
|
||||
}
|
||||
Ok(deliveries) => hive_core_agent_sock::Response::Messages {
|
||||
messages: deliveries
|
||||
.into_iter()
|
||||
.map(|d| hive_sh4re::inbox::DeliveredMessage {
|
||||
from: d.message.from.to_string(),
|
||||
body: d.message.body,
|
||||
id: d.id,
|
||||
redelivered: d.redelivered,
|
||||
in_reply_to: d.message.in_reply_to,
|
||||
})
|
||||
.collect(),
|
||||
// Still sent: a harness built before the field went unused
|
||||
// requires it (see the `Messages` doc). A count error is
|
||||
// non-fatal, so fall back to 0 rather than fail the recv.
|
||||
remaining: coord.broker.count_pending(agent).unwrap_or(0),
|
||||
},
|
||||
Err(e) => hive_core_agent_sock::Response::Err {
|
||||
message: format!("{e:#}"),
|
||||
},
|
||||
|
|
@ -296,8 +279,8 @@ async fn handle_recv(
|
|||
}
|
||||
|
||||
/// `Wake` — inject a wake into `agent`'s own inbox. Persisted through
|
||||
/// the sqlite broker like any other message so the agent can ack it
|
||||
/// via `AckUntil` and it appears in message history for post-mortem.
|
||||
/// the sqlite broker like any other message so it appears in message history
|
||||
/// for post-mortem.
|
||||
fn handle_wake(
|
||||
coord: &Arc<Coordinator>,
|
||||
agent: &str,
|
||||
|
|
@ -428,21 +411,6 @@ fn handle_ack_turn(coord: &Arc<Coordinator>, agent: &str) -> hive_core_agent_soc
|
|||
}
|
||||
}
|
||||
|
||||
/// `AckUntil` — bulk-ack every message addressed to `agent` with row
|
||||
/// id `<= up_to` (the agent-side backlog-triage escape hatch).
|
||||
fn handle_ack_until(
|
||||
coord: &Arc<Coordinator>,
|
||||
agent: &str,
|
||||
up_to: i64,
|
||||
) -> hive_core_agent_sock::Response {
|
||||
match coord.broker.ack_until(agent, up_to) {
|
||||
Ok(count) => hive_core_agent_sock::Response::Acked { count },
|
||||
Err(e) => hive_core_agent_sock::Response::Err {
|
||||
message: format!("{e:#}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// `RequeueInflight` — resurface `agent`'s unacked in-flight messages
|
||||
/// (crash recovery on harness boot).
|
||||
fn handle_requeue_inflight(
|
||||
|
|
@ -472,73 +440,6 @@ fn handle_get_loose_ends(coord: &Arc<Coordinator>, agent: &str) -> Response {
|
|||
}
|
||||
}
|
||||
|
||||
/// `Send` handler. Applies the
|
||||
/// 4 KiB body cap, then routes broadcast (`to == "*"`) / unicast through
|
||||
/// their respective broker calls.
|
||||
pub(crate) fn handle_send(
|
||||
coord: &Arc<Coordinator>,
|
||||
agent: &str,
|
||||
to: &str,
|
||||
body: &str,
|
||||
in_reply_to: Option<i64>,
|
||||
) -> Response {
|
||||
if let Err(message) = crate::limits::check_size("send", body) {
|
||||
return Response::Err { message };
|
||||
}
|
||||
if to == "*" {
|
||||
let errors = coord.broadcast_send(agent, body);
|
||||
return if errors.is_empty() {
|
||||
Response::Ok
|
||||
} else {
|
||||
Response::Err {
|
||||
message: format!("broadcast failed for agents: {}", errors.join(", ")),
|
||||
}
|
||||
};
|
||||
}
|
||||
// Validate that the recipient is a known local agent. Without this
|
||||
// check a typo in `to` silently queues a message nobody will ever read.
|
||||
//
|
||||
// Cross-hive messaging (`name@hive` qualified names) and the operator
|
||||
// are not reached through the broker — the Matrix MCP tools are.
|
||||
if to.contains('@') {
|
||||
return Response::Err {
|
||||
message: format!(
|
||||
"send failed: cross-hive recipient `{to}` is not supported \
|
||||
via the broker — use Matrix MCP tools for cross-hive messaging"
|
||||
),
|
||||
};
|
||||
}
|
||||
if to == hive_sh4re::manager::OPERATOR_RECIPIENT {
|
||||
return Response::Err {
|
||||
message: hive_sh4re::manager::OPERATOR_SEND_REFUSAL.to_owned(),
|
||||
};
|
||||
}
|
||||
// A name that doesn't parse as an Ident can't be a local agent, so
|
||||
// it collapses into the same "unknown recipient" error as a valid
|
||||
// name with no state dir.
|
||||
let exists =
|
||||
hive_types::Ident::parse(to).is_ok_and(|id| crate::paths::agent_state_dir(&id).exists());
|
||||
if !exists {
|
||||
return Response::Err {
|
||||
message: format!(
|
||||
"send failed: unknown recipient `{to}` \
|
||||
(no agent with that name exists on this hive)"
|
||||
),
|
||||
};
|
||||
}
|
||||
match coord.broker.send(&Message {
|
||||
from: hive_sh4re::manager::trusted_sender(agent),
|
||||
to: to.to_owned(),
|
||||
body: body.to_owned(),
|
||||
in_reply_to,
|
||||
}) {
|
||||
Ok(()) => Response::Ok,
|
||||
Err(e) => Response::Err {
|
||||
message: format!("{e:#}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use hive_sock_client::REQUEST_LINE_MAX_BYTES;
|
||||
|
|
@ -560,23 +461,6 @@ mod tests {
|
|||
(dir, Arc::new(coord))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_to_operator_is_refused_naming_matrix_and_the_no_matrix_fallback() {
|
||||
let (_dir, coord) = coordinator();
|
||||
let resp = handle_send(
|
||||
&coord,
|
||||
"iris",
|
||||
hive_sh4re::manager::OPERATOR_RECIPIENT,
|
||||
"hi",
|
||||
None,
|
||||
);
|
||||
assert!(
|
||||
matches!(&resp, Response::Err { message }
|
||||
if message.contains("mcp__matrix__") && message.contains("set_status")),
|
||||
"{resp:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn serve_answers_an_over_long_line_with_an_error_and_closes() {
|
||||
let (_dir, coord) = coordinator();
|
||||
|
|
|
|||
Loading…
Reference in a new issue