Add per-turn cyber access program selection (#40687)

## What changed

- Add the experimental `turn/start.cyberAccessProgram` option with `standard`,
  `daybreakBlue`, and `daybreakRed` values.
- Forward the selection as `access_programs.cyber` on Responses, WebSocket,
  and remote-compaction requests made with ChatGPT authentication, while
  omitting it for API-key and custom-provider requests.
- Preserve the per-turn selection across recovery, compaction, and child-agent
  turns without making it a persistent thread setting.

## Testing

- Cover app-server forwarding, authentication boundaries, WebSocket reuse,
  turn recovery, compaction, and child-agent inheritance.

GitOrigin-RevId: d2eb468f365b2214c5099bc21741cf30f8bd2eb5
This commit is contained in:
faizan-oai authored and copyberry committed 2026-08-25 19:54:52 +00:00
1 parent a6e63f9f32
commit 94967e03e5
72 files changed
+1323 -273

No files matched your search

+54 -42
View File
@@ -30,6 +30,7 @@ use crate::hook_runtime::run_turn_interrupt_hooks;
use crate::session::TurnInput;
use crate::session::session::Session;
use crate::session::turn::run_hooks_and_record_inputs;
use crate::session::turn_context::NewTurnContextOptions;
use crate::session::turn_context::TurnContext;
use crate::state::ActiveTurn;
use crate::state::RunningTask;
@@ -70,11 +71,6 @@ static ACTIVE_TURNS: Gauge = Gauge::new("core.turns.active");
pub(crate) type SessionTaskResult = CodexResult<Option<String>>;
pub(crate) enum MailboxParentProvenance {
Ignore,
Attribute,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum InterruptedTurnHistoryMarker {
Disabled,
@@ -278,8 +274,7 @@ impl Session {
) {
self.abort_all_tasks(TurnAbortReason::Replaced).await;
self.clear_connector_selection().await;
self.start_task(turn_context, input, task, MailboxParentProvenance::Ignore)
.await;
self.start_task(turn_context, input, task).await;
}
pub(crate) async fn start_task<T: SessionTask>(
@@ -287,7 +282,6 @@ impl Session {
turn_context: Arc<TurnContext>,
input: Vec<TurnInput>,
task: T,
mailbox_parent_provenance: MailboxParentProvenance,
) {
let task: Arc<dyn AnySessionTask> = Arc::new(task);
let task_kind = task.kind();
@@ -311,33 +305,14 @@ impl Session {
.await
.clear_turn(&turn_context.sub_id);
let (pending_items, parent_turn_id, root_turn_id) =
self.input_queue.get_pending_input(&self.active_turn).await;
if let MailboxParentProvenance::Attribute = mailbox_parent_provenance {
if let Some(id) = parent_turn_id {
if let Some(initiating_agent_path) = pending_items.iter().find_map(|item| {
let TurnInput::InterAgentCommunication(communication) = item else {
return None;
};
communication
.trigger_turn
.then(|| communication.author.clone())
}) {
turn_context
.turn_metadata_state
.set_initiating_agent_path(initiating_agent_path);
}
turn_context.turn_metadata_state.set_parent_turn_id(id);
}
if let Some(id) = root_turn_id {
turn_context.turn_metadata_state.set_root_turn_id(id);
}
} else if pending_items.iter().any(|item| {
// Reserved turn input already has its context; only newly arriving mail can change lineage.
let (pending_items, start_options) = self.input_queue.drain_mailbox_input_items().await;
if pending_items.iter().any(|item| {
matches!(
item,
TurnInput::InterAgentCommunication(communication) if communication.trigger_turn
)
}) && turn_context.turn_metadata_state.root_turn_id() != root_turn_id
}) && turn_context.turn_metadata_state.root_turn_id() != start_options.root_turn_id
{
turn_context.turn_metadata_state.mark_root_turn_ambiguous();
}
@@ -477,24 +452,61 @@ impl Session {
return;
}
{
let turn_state = {
let mut active_turn = self.active_turn.lock().await;
if active_turn.is_some() {
return;
}
*active_turn = Some(ActiveTurn::default());
}
let active_turn = active_turn.get_or_insert_with(ActiveTurn::default);
Arc::clone(&active_turn.turn_state)
};
let turn_context = self.new_default_turn_with_sub_id(sub_id).await;
let (input, mut start_options) =
self.input_queue.get_pending_input(&self.active_turn).await;
if !input.iter().any(
|item| matches!(item, TurnInput::InterAgentCommunication(mail) if mail.trigger_turn),
) {
// Queue-only mail wakes durable sleep without selecting a new task's settings.
start_options.cyber_access_program = self
.reference_context_item()
.await
.and_then(|context| context.cyber_access_program);
}
let turn_context = self
.new_turn_with_default_settings(
sub_id,
NewTurnContextOptions {
final_output_json_schema: start_options.final_output_json_schema,
cyber_access_program: start_options.cyber_access_program,
},
)
.await;
if let Some(id) = start_options.parent_turn_id {
if let Some(initiating_agent_path) = input.iter().find_map(|item| {
let TurnInput::InterAgentCommunication(communication) = item else {
return None;
};
communication
.trigger_turn
.then(|| communication.author.clone())
}) {
turn_context
.turn_metadata_state
.set_initiating_agent_path(initiating_agent_path);
}
turn_context.turn_metadata_state.set_parent_turn_id(id);
}
if let Some(id) = start_options.root_turn_id {
turn_context.turn_metadata_state.set_root_turn_id(id);
}
self.maybe_emit_model_warnings_for_turn(turn_context.as_ref())
.await;
self.start_task(
turn_context,
Vec::new(),
RegularTask::new(),
MailboxParentProvenance::Attribute,
)
.await;
// Task completion must still save this mail if pre-turn compaction fails.
self.input_queue
.extend_pending_input_for_turn_state(turn_state.as_ref(), input)
.await;
self.start_task(turn_context, Vec::new(), RegularTask::new())
.await;
}
pub async fn abort_all_tasks(self: &Arc<Self>, reason: TurnAbortReason) {