mirror of
https://github.com/openai/codex.git
synced 2026-11-10 21:00:47 +00:00
Report completed sub-agent activity on parent turns (#40437)
## Why A Multi-Agent V2 child can finish after the parent turn that spawned it, so its successful completion needs to remain associated with that parent turn in notifications and history. ## What changed - Add `completed` to `SubAgentActivityKind` and emit the corresponding started and completed item events on the spawning parent turn when a child succeeds. - Persist completed activity as a canonical turn item in both legacy and paginated history, and expose it through app-server history and schemas. - Render completed activity as terminal in the TUI without counting it as a separate tool call or tool-runtime event. ## Testing - Cover successful completion in legacy and paginated history, late updates to completed parent turns, TUI rendering, analytics, and rollout tracing. - Verify that terminal child errors do not emit completed activity. GitOrigin-RevId: 6c71eb8b10e7327611c6af84f70e294e27d94d02
This commit is contained in:
@@ -1631,6 +1631,7 @@ mod tests {
|
||||
use codex_protocol::items::EnteredReviewModeItem as CoreEnteredReviewModeItem;
|
||||
use codex_protocol::items::ExitedReviewModeItem as CoreExitedReviewModeItem;
|
||||
use codex_protocol::items::HookPromptFragment as CoreHookPromptFragment;
|
||||
use codex_protocol::items::SubAgentActivityItem as CoreSubAgentActivityItem;
|
||||
use codex_protocol::items::TurnItem as CoreTurnItem;
|
||||
use codex_protocol::items::UserMessageItem as CoreUserMessageItem;
|
||||
use codex_protocol::items::build_hook_prompt_message;
|
||||
@@ -1654,6 +1655,7 @@ mod tests {
|
||||
use codex_protocol::protocol::McpToolCallEndEvent;
|
||||
use codex_protocol::protocol::PatchApplyBeginEvent;
|
||||
use codex_protocol::protocol::ReviewTarget;
|
||||
use codex_protocol::protocol::SubAgentActivityKind as CoreSubAgentActivityKind;
|
||||
use codex_protocol::protocol::ThreadRolledBackEvent;
|
||||
use codex_protocol::protocol::TurnAbortReason;
|
||||
use codex_protocol::protocol::TurnAbortedEvent;
|
||||
@@ -4480,6 +4482,57 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completed_sub_agent_activity_updates_completed_parent_turn() {
|
||||
let child_thread_id = ThreadId::new();
|
||||
let child_path = codex_protocol::AgentPath::root()
|
||||
.join("worker")
|
||||
.expect("worker path");
|
||||
let items = vec![
|
||||
RolloutItem::EventMsg(EventMsg::TurnStarted(TurnStartedEvent {
|
||||
turn_id: "turn-a".into(),
|
||||
trace_id: None,
|
||||
started_at: None,
|
||||
model_context_window: None,
|
||||
collaboration_mode_kind: Default::default(),
|
||||
})),
|
||||
RolloutItem::EventMsg(EventMsg::TurnComplete(TurnCompleteEvent {
|
||||
turn_id: "turn-a".into(),
|
||||
started_at: None,
|
||||
last_agent_message: None,
|
||||
error: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
})),
|
||||
RolloutItem::EventMsg(EventMsg::ItemCompleted(ItemCompletedEvent {
|
||||
thread_id: ThreadId::new(),
|
||||
turn_id: "turn-a".into(),
|
||||
item: CoreTurnItem::SubAgentActivity(CoreSubAgentActivityItem {
|
||||
id: "child-turn-completed".into(),
|
||||
kind: CoreSubAgentActivityKind::Completed,
|
||||
agent_thread_id: child_thread_id,
|
||||
agent_path: child_path,
|
||||
}),
|
||||
started_at_ms: None,
|
||||
completed_at_ms: 0,
|
||||
})),
|
||||
];
|
||||
|
||||
let turns = build_turns_from_rollout_items(&items);
|
||||
|
||||
assert_eq!(turns.len(), 1);
|
||||
assert_eq!(
|
||||
turns[0].items,
|
||||
vec![ThreadItem::SubAgentActivity {
|
||||
id: "child-turn-completed".into(),
|
||||
kind: crate::protocol::v2::SubAgentActivityKind::Completed,
|
||||
agent_thread_id: child_thread_id.to_string(),
|
||||
agent_path: "/root/worker".into(),
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignores_plain_user_response_items_in_rollout_replay() {
|
||||
let items = vec![
|
||||
|
||||
@@ -1180,6 +1180,7 @@ pub enum SubAgentActivityKind {
|
||||
Started,
|
||||
Interacted,
|
||||
Interrupted,
|
||||
Completed,
|
||||
}
|
||||
|
||||
impl From<CoreSubAgentActivityKind> for SubAgentActivityKind {
|
||||
@@ -1188,6 +1189,7 @@ impl From<CoreSubAgentActivityKind> for SubAgentActivityKind {
|
||||
CoreSubAgentActivityKind::Started => SubAgentActivityKind::Started,
|
||||
CoreSubAgentActivityKind::Interacted => SubAgentActivityKind::Interacted,
|
||||
CoreSubAgentActivityKind::Interrupted => SubAgentActivityKind::Interrupted,
|
||||
CoreSubAgentActivityKind::Completed => SubAgentActivityKind::Completed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3187,7 +3187,7 @@ fn core_turn_item_into_thread_item_converts_supported_variants() {
|
||||
|
||||
let sub_agent_activity_item = TurnItem::SubAgentActivity(SubAgentActivityItem {
|
||||
id: "activity-1".to_string(),
|
||||
kind: CoreSubAgentActivityKind::Interrupted,
|
||||
kind: CoreSubAgentActivityKind::Completed,
|
||||
agent_thread_id: receiver_thread_id,
|
||||
agent_path: codex_protocol::AgentPath::root()
|
||||
.join("worker")
|
||||
@@ -3198,7 +3198,7 @@ fn core_turn_item_into_thread_item_converts_supported_variants() {
|
||||
ThreadItem::from(sub_agent_activity_item),
|
||||
ThreadItem::SubAgentActivity {
|
||||
id: "activity-1".to_string(),
|
||||
kind: SubAgentActivityKind::Interrupted,
|
||||
kind: SubAgentActivityKind::Completed,
|
||||
agent_thread_id: receiver_thread_id.to_string(),
|
||||
agent_path: "/root/worker".to_string(),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user