Make MultiAgentV2 interruption markers assistant-authored (#19124)

## Why

`MultiAgentV2` follow-up messages are delivered to agents as
assistant-authored `InterAgentCommunication` envelopes. When
`followup_task` used `interrupt: true`, the interrupted-turn guidance
was still persisted as a contextual user message, so model-visible
history made a system-generated interruption boundary look
user-authored.

This keeps interruption guidance consistent with the rest of the v2
inter-agent message stream while preserving the legacy marker shape for
non-v2 sessions.

## What changed

- Make `interrupted_turn_history_marker` feature-aware.
- Record the interrupted-turn marker as an assistant `OutputText`
message when `Feature::MultiAgentV2` is enabled.
- Keep the existing user contextual fragment for non-v2 sessions.
- Apply the same feature-aware marker to interrupted fork snapshots.
- Add coverage for the live `followup_task` interrupt path and the
helper-level v2 marker shape.

## Testing

- `cargo test -p codex-core
multi_agent_v2_followup_task_interrupts_busy_child_without_losing_message
-- --nocapture`
- `cargo test -p codex-core
multi_agent_v2_interrupted_marker_uses_assistant_output_message --
--nocapture`
- `cargo test -p codex-core interrupted_fork_snapshot -- --nocapture`
This commit is contained in:
jif-oai
2026-04-24 13:39:26 +02:00
committed by GitHub
Unverified
parent 21463a5074
commit 120aa07d81
5 changed files with 136 additions and 21 deletions
+22 -5
View File
@@ -50,6 +50,7 @@ use codex_protocol::protocol::WarningEvent;
use codex_protocol::user_input::UserInput;
use codex_features::Feature;
use codex_protocol::models::ContentItem;
pub(crate) use compact::CompactTask;
pub(crate) use ghost_snapshot::GhostSnapshotTask;
pub(crate) use regular::RegularTask;
@@ -63,10 +64,26 @@ const GRACEFULL_INTERRUPTION_TIMEOUT_MS: u64 = 100;
/// Shared model-visible marker used by both the real interrupt path and
/// interrupted fork snapshots.
pub(crate) fn interrupted_turn_history_marker() -> ResponseItem {
ContextualUserFragment::into(crate::context::TurnAborted::new(
crate::context::TurnAborted::INTERRUPTED_GUIDANCE,
))
pub(crate) fn interrupted_turn_history_marker(multi_agent_v2_enabled: bool) -> ResponseItem {
let guidance = if multi_agent_v2_enabled {
crate::context::TurnAborted::INTERRUPTED_DEVELOPER_GUIDANCE
} else {
crate::context::TurnAborted::INTERRUPTED_GUIDANCE
};
let marker = crate::context::TurnAborted::new(guidance);
if multi_agent_v2_enabled {
ResponseItem::Message {
id: None,
role: "developer".to_string(),
content: vec![ContentItem::InputText {
text: marker.render(),
}],
end_turn: None,
phase: None,
}
} else {
ContextualUserFragment::into(marker)
}
}
fn emit_turn_network_proxy_metric(
@@ -675,7 +692,7 @@ impl Session {
if reason == TurnAbortReason::Interrupted {
self.cleanup_after_interrupt(&task.turn_context).await;
let marker = interrupted_turn_history_marker();
let marker = interrupted_turn_history_marker(self.enabled(Feature::MultiAgentV2));
self.record_into_history(std::slice::from_ref(&marker), task.turn_context.as_ref())
.await;
self.persist_rollout_items(&[RolloutItem::ResponseItem(marker)])