mirror of
https://github.com/openai/codex.git
synced 2026-10-22 17:58:46 +00:00
Handle partial answers consistently across agent workflows (#51249)
## Why Partial answers contain answer fragments while a turn is still in progress. Treating them as final answers defers mailbox delivery and can count empty fragments as empty final responses; filtering them from forked histories and guardian transcripts loses answer content. ## What changed - Allow mailbox preemption after `partial_answer` messages and keep mailbox delivery open. - Preserve partial answers in forked agent histories and include them as protected assistant entries in guardian transcripts. - Exclude empty partial answers from empty-final-response accounting for goals. ## Testing Add regression coverage for partial-answer phases in live events, sampling continuations, follow-up requests, and forked child requests, plus mailbox preemption, goal accounting, and guardian transcript inclusion. GitOrigin-RevId: 208c8933e211b42b6c2d0bec6952527b8c9c120e
This commit is contained in:
@@ -89,7 +89,10 @@ fn keep_forked_rollout_item(item: &RolloutItem, preserve_context_baselines: bool
|
||||
RolloutItem::ResponseItem(envelope) => match &envelope.item {
|
||||
ResponseItem::Message { role, phase, .. } => match role.as_str() {
|
||||
"system" | "developer" | "user" => true,
|
||||
"assistant" => *phase == Some(MessagePhase::FinalAnswer),
|
||||
"assistant" => matches!(
|
||||
phase,
|
||||
Some(MessagePhase::PartialAnswer | MessagePhase::FinalAnswer)
|
||||
),
|
||||
_ => false,
|
||||
},
|
||||
ResponseItem::FunctionCallOutput { call_id: None, .. }
|
||||
|
||||
@@ -2449,6 +2449,7 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
|
||||
),
|
||||
},
|
||||
assistant_message("parent commentary", Some(MessagePhase::Commentary)),
|
||||
assistant_message("parent answer fragment", Some(MessagePhase::PartialAnswer)),
|
||||
assistant_message("parent final answer", Some(MessagePhase::FinalAnswer)),
|
||||
standalone_output,
|
||||
assistant_message("parent unknown phase", /*phase*/ None),
|
||||
@@ -2529,6 +2530,22 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
|
||||
};
|
||||
assert!(assistant_position(&parent_thread.session.clone_history().await).is_some());
|
||||
assert_eq!(assistant_position(&history), None);
|
||||
let expected_partial_answer = parent_thread
|
||||
.session
|
||||
.clone_history()
|
||||
.await
|
||||
.raw_items()
|
||||
.find(|item| {
|
||||
matches!(
|
||||
item,
|
||||
ResponseItem::Message {
|
||||
phase: Some(MessagePhase::PartialAnswer),
|
||||
..
|
||||
}
|
||||
)
|
||||
})
|
||||
.cloned()
|
||||
.expect("parent answer fragment should be recorded");
|
||||
let expected_final_answer = parent_thread
|
||||
.session
|
||||
.clone_history()
|
||||
@@ -2587,6 +2604,7 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
|
||||
let expected_history = [
|
||||
expected_parent_seed,
|
||||
expected_developer_message,
|
||||
expected_partial_answer,
|
||||
expected_final_answer,
|
||||
expected_standalone_output,
|
||||
ContextualUserFragment::into(MultiAgentRoleInstructions::Configured(
|
||||
|
||||
Reference in New Issue
Block a user