mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Make automatic turn admission atomic with settings updates (#40648)
## Why Turn settings can change after an input request previews its overrides. Automatic idle work could therefore make a Plan-mode admission decision from stale settings or apply a sparse override built from an outdated collaboration mode. ## What changed - Evaluate automatic turn admission against both the current and proposed configuration under the settings publication lock. - Reject automatic work that would enter or leave Plan mode without committing settings or triggering runtime configuration effects. - Keep model and reasoning-effort overrides sparse until they are merged with the configuration being committed. - Continue allowing explicit user and recovery turns to make valid mode transitions. ## Testing Add coverage for concurrent settings changes, sparse model and effort updates, commit-time constraint validation, atomic Plan-mode rejection, and subsequent explicit user submission. GitOrigin-RevId: 234f06de116bdce7c3a3fb5cac3d15583304a253
This commit is contained in:
1 parent
1d64085e67
commit
ba6cf9c692
9 files changed
+756
-137
No files matched your search
@@ -758,18 +758,36 @@ impl Session {
|
||||
sub_id: String,
|
||||
updates: SessionSettingsUpdate,
|
||||
) -> CodexResult<(Arc<TurnContext>, ThreadSettingsSnapshot)> {
|
||||
let Some(turn) = self
|
||||
.new_turn_with_sub_id_if(sub_id, updates, |_, _| true)
|
||||
.await?
|
||||
else {
|
||||
unreachable!("unconditional turn construction must accept valid settings");
|
||||
};
|
||||
Ok(turn)
|
||||
}
|
||||
|
||||
/// Commits accepted settings atomically, then constructs the turn without holding
|
||||
/// the state lock. The caller owns admission policy.
|
||||
///
|
||||
/// `should_start` runs under the state lock against the current and validated
|
||||
/// proposed configurations. It must be fast and side-effect-free, and must not
|
||||
/// block, acquire other locks, or call back into `Session`.
|
||||
pub(super) async fn new_turn_with_sub_id_if(
|
||||
&self,
|
||||
sub_id: String,
|
||||
updates: SessionSettingsUpdate,
|
||||
should_start: impl FnOnce(&SessionConfiguration, &SessionConfiguration) -> bool + Send,
|
||||
) -> CodexResult<Option<(Arc<TurnContext>, ThreadSettingsSnapshot)>> {
|
||||
let final_output_json_schema = updates.final_output_json_schema.clone();
|
||||
let service_tier_for_turn = updates.service_tier_for_turn.clone();
|
||||
let update_result = self
|
||||
.update_settings(updates)
|
||||
.await
|
||||
.map_err(|err| CodexErr::InvalidRequest(err.to_string()));
|
||||
let commit = match update_result {
|
||||
Ok(commit) => commit,
|
||||
Err(err) => {
|
||||
let message = err.to_string();
|
||||
let commit = match self.update_settings_if(updates, should_start).await {
|
||||
Ok(Some(commit)) => commit,
|
||||
Ok(None) => return Ok(None),
|
||||
Err(error) => {
|
||||
let message = CodexErr::InvalidRequest(error.to_string()).to_string();
|
||||
self.send_event_raw(Event {
|
||||
id: sub_id.clone(),
|
||||
id: sub_id,
|
||||
msg: EventMsg::Error(ErrorEvent {
|
||||
message: message.clone(),
|
||||
codex_error_info: Some(CodexErrorInfo::BadRequest),
|
||||
@@ -787,9 +805,10 @@ impl Session {
|
||||
let turn_context = self
|
||||
.new_turn_from_configuration(sub_id, configuration, final_output_json_schema)
|
||||
.await;
|
||||
Ok((turn_context, commit.snapshot))
|
||||
Ok(Some((turn_context, commit.snapshot)))
|
||||
}
|
||||
|
||||
/// Builds a turn from the exact committed settings without starting a task.
|
||||
async fn new_turn_from_configuration(
|
||||
&self,
|
||||
sub_id: String,
|
||||
|
||||
Reference in new issue
Block a user