broker: lease-style delivery — ack_turn + requeue_inflight close the no-drop loop
This commit is contained in:
parent
69a3ca7469
commit
690cb5ab5b
8 changed files with 684 additions and 35 deletions
|
|
@ -121,6 +121,10 @@ async fn serve(
|
|||
turn_lock: TurnLock,
|
||||
) -> Result<()> {
|
||||
tracing::info!(socket = %socket.display(), "hive-m1nd serve");
|
||||
// Same boot-time recovery as hive-ag3nt — see that loop for the
|
||||
// rationale. Manager-flavour socket so we requeue only manager
|
||||
// inflight rows.
|
||||
requeue_inflight(socket).await;
|
||||
loop {
|
||||
let recv: Result<ManagerResponse> =
|
||||
// Explicit long-poll: see hive-ag3nt's serve loop for the
|
||||
|
|
@ -134,7 +138,12 @@ async fn serve(
|
|||
)
|
||||
.await;
|
||||
match recv {
|
||||
Ok(ManagerResponse::Message { from, body }) => {
|
||||
Ok(ManagerResponse::Message {
|
||||
from,
|
||||
body,
|
||||
id: _,
|
||||
redelivered,
|
||||
}) => {
|
||||
if from == SYSTEM_SENDER {
|
||||
// Helper events (ApprovalResolved / Spawned / Rebuilt /
|
||||
// Killed / Destroyed) — these are FYI for the manager;
|
||||
|
|
@ -154,14 +163,14 @@ async fn serve(
|
|||
// prompt body so claude sees it. Sender stays "system"
|
||||
// so the wake prompt can label it as such.
|
||||
}
|
||||
tracing::info!(%from, %body, "manager inbox");
|
||||
tracing::info!(%from, %body, %redelivered, "manager inbox");
|
||||
let unread = inbox_unread(socket).await;
|
||||
bus.emit(LiveEvent::TurnStart {
|
||||
from: from.clone(),
|
||||
body: body.clone(),
|
||||
unread,
|
||||
});
|
||||
let prompt = format_wake_prompt(&from, &body, unread);
|
||||
let prompt = format_wake_prompt(&from, &body, unread, redelivered);
|
||||
bus.set_state(TurnState::Thinking);
|
||||
let started_at = now_unix();
|
||||
let started_instant = std::time::Instant::now();
|
||||
|
|
@ -172,6 +181,12 @@ async fn serve(
|
|||
};
|
||||
turn::emit_turn_end(&bus, &outcome);
|
||||
bus.set_state(TurnState::Idle);
|
||||
// Ack only on a clean turn-end; Failed leaves the
|
||||
// popped ids in-flight for the next boot's requeue.
|
||||
// Mirrors hive-ag3nt; see that loop for full rationale.
|
||||
if matches!(outcome, turn::TurnOutcome::Ok) {
|
||||
ack_turn(socket).await;
|
||||
}
|
||||
if let Some(s) = &stats {
|
||||
let ended_at = now_unix();
|
||||
let duration_ms =
|
||||
|
|
@ -228,8 +243,15 @@ async fn serve(
|
|||
/// Per-turn user prompt. The role/tools/etc. is in the system prompt
|
||||
/// (`prompts/manager.md` → `claude --system-prompt-file`); this is just
|
||||
/// the wake signal. `unread` is the inbox depth after this message was
|
||||
/// popped.
|
||||
fn format_wake_prompt(from: &str, body: &str, unread: u64) -> String {
|
||||
/// popped. `redelivered` adds a "may already be handled" banner above
|
||||
/// the wake body when the broker resurfaced this row (see hive-ag3nt's
|
||||
/// `format_wake_prompt` for the full story).
|
||||
fn format_wake_prompt(from: &str, body: &str, unread: u64, redelivered: bool) -> String {
|
||||
let banner = if redelivered {
|
||||
hive_ag3nt::mcp::REDELIVERY_HINT
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let pending = if unread == 0 {
|
||||
String::new()
|
||||
} else {
|
||||
|
|
@ -237,7 +259,39 @@ fn format_wake_prompt(from: &str, body: &str, unread: u64) -> String {
|
|||
"\n\n({unread} more message(s) pending in your inbox — drain via `mcp__hyperhive__recv` if relevant.)"
|
||||
)
|
||||
};
|
||||
format!("Incoming message from `{from}`:\n---\n{body}\n---{pending}")
|
||||
format!("{banner}Incoming message from `{from}`:\n---\n{body}\n---{pending}")
|
||||
}
|
||||
|
||||
/// Best-effort: tell the broker every message popped during the turn
|
||||
/// is now handled. Mirror of `hive-ag3nt::ack_turn` on the manager
|
||||
/// surface.
|
||||
async fn ack_turn(socket: &Path) {
|
||||
match client::request::<_, ManagerResponse>(socket, &ManagerRequest::AckTurn).await {
|
||||
Ok(ManagerResponse::Ok) => {}
|
||||
Ok(ManagerResponse::Err { message }) => {
|
||||
tracing::warn!(%message, "ack_turn rejected by broker");
|
||||
}
|
||||
Ok(other) => {
|
||||
tracing::warn!(?other, "ack_turn unexpected response");
|
||||
}
|
||||
Err(e) => tracing::warn!(error = ?e, "ack_turn transport error"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Boot-time recovery: ask the broker to resurface any inflight (popped
|
||||
/// but not acked) messages so the next `Recv` re-delivers them with
|
||||
/// the redelivery banner. Mirror of `hive-ag3nt::requeue_inflight`.
|
||||
async fn requeue_inflight(socket: &Path) {
|
||||
match client::request::<_, ManagerResponse>(socket, &ManagerRequest::RequeueInflight).await {
|
||||
Ok(ManagerResponse::Ok) => {}
|
||||
Ok(ManagerResponse::Err { message }) => {
|
||||
tracing::warn!(%message, "requeue_inflight rejected by broker");
|
||||
}
|
||||
Ok(other) => {
|
||||
tracing::warn!(?other, "requeue_inflight unexpected response");
|
||||
}
|
||||
Err(e) => tracing::warn!(error = ?e, "requeue_inflight transport error"),
|
||||
}
|
||||
}
|
||||
|
||||
async fn inbox_unread(socket: &Path) -> u64 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue