recv: fold batch drain into recv(max) — one tool, uniform list response

This commit is contained in:
damocles 2026-05-19 01:07:30 +02:00
parent 77b89bf2c6
commit 5d27ae3048
8 changed files with 271 additions and 417 deletions

View file

@ -174,10 +174,12 @@ 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.
/// One delivered message in a `Recv` response. The unified
/// `Recv { max }` always returns a `Vec<DeliveredMessage>` — single
/// pop = a one-element vec, batch = up to `max` elements, idle =
/// empty. Each row carries the broker's id + redelivered flag so the
/// harness can drive `AckTurn` and surface the "may already be
/// handled" hint per-row.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeliveredMessage {
pub from: String,
@ -286,22 +288,25 @@ pub enum CancelLooseEndKind {
pub enum AgentRequest {
/// Send a message to another agent.
Send { to: String, body: String },
/// Pop one pending message from this agent's inbox. Long-polls
/// up to `wait_seconds` (capped at 60s server-side, default 30s
/// when None) before returning `Empty`.
/// Pop pending messages from this agent's inbox. Always returns
/// a list (`Messages { messages }`) — empty when nothing's
/// pending. `max` caps the batch size (default 1 = single-message
/// behaviour, server-side cap 32). `wait_seconds` long-polls for
/// the first message; once one arrives (or one is already
/// pending), the call drains up to `max` in total before
/// returning. Same delivery + ack bookkeeping per row as before:
/// `delivered_at = NOW`, tracked on the per-recipient
/// `unacked_ids` list (the next `AckTurn` closes them out), and
/// each row carries `redelivered = true` if `RequeueInflight`
/// resurfaced it.
Recv {
#[serde(default)]
wait_seconds: Option<u64>,
/// Maximum number of messages to pop. None = 1 (single).
/// Server-side cap is 32; values above clamp silently.
#[serde(default)]
max: Option<u32>,
},
/// 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,
@ -421,28 +426,13 @@ pub enum AgentResponse {
Ok,
/// Either `Send` failed or `Recv` errored.
Err { message: String },
/// `Recv` produced a message. `id` is the broker's row id — opaque
/// to claude (the MCP surface strips it before handing the body
/// to the model) but tracked by the harness so the broker's
/// in-memory unacked list can be drained on `AckTurn`. When
/// `redelivered = true` this row was popped earlier, never
/// acked (turn crash / OOM / restart), and resurfaced by
/// `RequeueInflight` — the harness prepends a "may already be
/// handled" hint to the wake prompt so claude can DTRT.
Message {
from: String,
body: String,
#[serde(default)]
id: i64,
#[serde(default)]
redelivered: bool,
},
/// `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> },
/// `Recv` result: zero or more messages, FIFO-ordered, never
/// longer than the `max` the caller passed. Empty vec = nothing
/// pending (the "(empty)" path for the formatter). Per-row `id` +
/// `redelivered` carry the broker's row id (opaque to claude;
/// tracked by the harness for `AckTurn`) and the "previously
/// popped, not acked" flag — see `DeliveredMessage` for details.
Messages { messages: Vec<DeliveredMessage> },
/// `Status` result: how many pending messages are in this agent's inbox.
Status { unread: u64 },
/// `Recent` result: newest-first inbox rows.
@ -612,16 +602,16 @@ pub enum ManagerRequest {
to: String,
body: String,
},
/// Same shape as `AgentRequest::Recv` — caller-tunable long-poll
/// duration, capped at 60s server-side, default 30s when None.
/// Same shape as `AgentRequest::Recv` — caller-tunable
/// `wait_seconds` (capped at 60s server-side, default 30s when
/// None) for first-message long-poll, plus `max` (default 1, cap
/// 32) to drain up to N popped rows in one round-trip.
Recv {
#[serde(default)]
wait_seconds: Option<u64>,
#[serde(default)]
max: Option<u32>,
},
/// 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,
@ -759,23 +749,11 @@ pub enum ManagerResponse {
Err {
message: String,
},
/// Same delivery shape as `AgentResponse::Message` — `id` +
/// `redelivered` carry the broker's row id and the
/// "previously popped, not acked" flag through the manager
/// surface so the manager harness drives the same
/// requeue-with-hint flow as a sub-agent.
Message {
from: String,
body: String,
#[serde(default)]
id: i64,
#[serde(default)]
redelivered: bool,
},
Empty,
/// Mirror of `AgentResponse::Batch` on the manager surface.
/// `messages` is in FIFO order and may be empty.
Batch {
/// Mirror of `AgentResponse::Messages` on the manager surface.
/// Always-list shape: 0..=max popped rows, FIFO-ordered. Carries
/// per-row `id` + `redelivered` so the manager harness drives the
/// same ack + requeue-with-hint flow as a sub-agent.
Messages {
messages: Vec<DeliveredMessage>,
},
Status {