broker: recv_batch(max) — drain a bursty inbox in one round-trip

This commit is contained in:
damocles 2026-05-19 00:40:31 +02:00
parent 96ffb0e39a
commit 77b89bf2c6
9 changed files with 354 additions and 11 deletions

View file

@ -174,6 +174,28 @@ pub struct InboxRow {
pub at: i64,
}
/// One delivered message in a `RecvBatch` response. Same fields as
/// `AgentResponse::Message` / `ManagerResponse::Message` without the
/// variant wrapper — a batch returns a `Vec<DeliveredMessage>` so the
/// harness can iterate without unpicking N separate top-level frames.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeliveredMessage {
pub from: String,
pub body: String,
/// Broker row id, mirrored from the `Delivery` struct. Opaque to
/// claude but tracked by the harness so the broker's in-memory
/// unacked list can be drained on `AckTurn`. Marked `default` for
/// wire backwards-compat — pre-feature peers parse to 0.
#[serde(default)]
pub id: i64,
/// `true` when this row was previously popped, never acked, and
/// resurfaced by `RequeueInflight`. The format helper prepends the
/// "may already be handled" hint to the rendered body so claude
/// sees the warning per-message in the batch.
#[serde(default)]
pub redelivered: bool,
}
/// Reminder timing: either relative (wait N seconds) or absolute (at unix
/// timestamp).
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -271,6 +293,15 @@ pub enum AgentRequest {
#[serde(default)]
wait_seconds: Option<u64>,
},
/// Pop up to `max` pending messages in one round-trip. No
/// long-poll — returns whatever's currently queued (possibly
/// zero) immediately. Same delivery + ack bookkeeping as `Recv`:
/// every popped row is marked `delivered_at = NOW`, tracked in
/// the broker's per-recipient `unacked_ids` list (so the next
/// `AckTurn` closes them out), and tagged `redelivered = true` if
/// it was resurfaced by `RequeueInflight`. Used by the harness to
/// drain a bursty inbox without N socket round-trips.
RecvBatch { max: u32 },
/// Non-mutating: how many pending messages are addressed to me?
/// Used by the harness to render a status line after each tool call.
Status,
@ -408,6 +439,10 @@ pub enum AgentResponse {
},
/// `Recv` found nothing pending.
Empty,
/// `RecvBatch` result. `messages` is in FIFO order and may be
/// empty (treated like `Empty` for `Recv`); never longer than the
/// `max` the caller passed.
Batch { messages: Vec<DeliveredMessage> },
/// `Status` result: how many pending messages are in this agent's inbox.
Status { unread: u64 },
/// `Recent` result: newest-first inbox rows.
@ -583,6 +618,10 @@ pub enum ManagerRequest {
#[serde(default)]
wait_seconds: Option<u64>,
},
/// Mirror of `AgentRequest::RecvBatch` on the manager surface —
/// pop up to `max` pending messages in one round-trip, no
/// long-poll. Same ack + redelivery bookkeeping.
RecvBatch { max: u32 },
/// Non-mutating: pending message count, used to render a status line
/// after each MCP tool call (mirrors `AgentRequest::Status`).
Status,
@ -734,6 +773,11 @@ pub enum ManagerResponse {
redelivered: bool,
},
Empty,
/// Mirror of `AgentResponse::Batch` on the manager surface.
/// `messages` is in FIFO order and may be empty.
Batch {
messages: Vec<DeliveredMessage>,
},
Status {
unread: u64,
},