Route message-board notifications through agent mailboxes (#59139)

## Why

Board notifications were skipped when recipients had no active turn accepting mailbox delivery. Queue them through the ordinary agent-message path so idle recipients can receive them in a later turn.

## What changed

- Submit board notices as `Op::InterAgentCommunication` with `trigger_turn` disabled, preserving normal mailbox scheduling without reopening finalized answers.
- Hold a residency guard through dispatch of submitted agent messages to coordinate delivery with agent eviction.
- Update board tool descriptions to describe the host's notification queueing and wake policy.

## Testing

Update local and HTTP board scenarios to cover queued idle notices, delivery after a finalized answer, and mailbox delivery before a pending tool call. Extend residency coverage to verify that submission waits for the eviction gate and queued messages survive eviction.

GitOrigin-RevId: a77137fc40932f5903852e14742483844e07c9bc
This commit is contained in:
jif
2026-11-03 12:33:26 +00:00
committed by copyberry
parent 3b5483307d
commit f05aa41da6
16 changed files with 159 additions and 110 deletions
+1 -33
View File
@@ -7,7 +7,6 @@ use codex_diagnostics::GaugeGuard;
use codex_history::ResponseItemEnvelope;
use codex_protocol::ThreadId;
use codex_protocol::models::ResponseItem;
use codex_protocol::protocol::InterAgentCommunication;
use codex_protocol::turn_input::TurnStartOptions;
use codex_protocol::user_input::UserInput;
use serde::Deserialize;
@@ -202,38 +201,6 @@ impl InputQueue {
(activity_rx, pending_activity)
}
#[expect(
clippy::await_holding_invalid_type,
reason = "active turn checks and turn state updates must remain atomic"
)]
pub(crate) async fn deliver_mailbox_communication_to_current_turn(
&self,
active_turn: &Mutex<Option<ActiveTurn>>,
communication: InterAgentCommunication,
expected_turn_id: Option<&str>,
) -> bool {
let active = active_turn.lock().await;
let Some(active_turn) = active.as_ref().filter(|turn| {
turn.task.as_ref().is_some_and(|task| {
expected_turn_id.is_none_or(|id| {
task.turn_context.sub_id == id && !task.cancellation_token.is_cancelled()
})
})
}) else {
return false;
};
let mut turn_state = active_turn.turn_state.lock().await;
if !turn_state.accepts_mailbox_delivery_for_current_turn() {
return false;
}
turn_state
.pending_input
.items
.push(TurnInput::InterAgentCommunication(communication.into()));
self.activity_tx.send_replace(InputQueueActivity::Mailbox);
true
}
pub(crate) async fn enqueue_mailbox_communication(
&self,
communication: crate::PendingAgentMessage,
@@ -556,6 +523,7 @@ mod tests {
use super::*;
use codex_history::CodexHarnessMetadata;
use codex_protocol::AgentPath;
use codex_protocol::protocol::InterAgentCommunication;
use codex_protocol::user_input::UserInput;
use pretty_assertions::assert_eq;