From 070d3d3b2d840c72b347ef70042ff50233254deb Mon Sep 17 00:00:00 2001 From: Rohit Arunachalam Date: Thu, 25 Jun 2026 04:13:43 -0700 Subject: [PATCH 1/4] Allow zero current time reminder intervals --- codex-rs/core/config.schema.json | 2 +- codex-rs/core/src/config/config_tests.rs | 24 ++---------------------- codex-rs/core/src/config/mod.rs | 6 ------ codex-rs/features/src/feature_configs.rs | 1 - 4 files changed, 3 insertions(+), 30 deletions(-) diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index 92a45d1978b5..bfdd179d781e 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -815,7 +815,7 @@ }, "reminder_interval_seconds": { "format": "uint64", - "minimum": 1.0, + "minimum": 0.0, "type": "integer" }, "sleep_tool": { diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index a178dfae04d3..17779117f64a 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -625,12 +625,12 @@ current_time_reminder = true r#" [features.current_time_reminder] enabled = true -reminder_interval_seconds = 4 +reminder_interval_seconds = 0 clock_source = "external" sleep_tool = true "#, CurrentTimeReminderConfig { - reminder_interval_seconds: 4, + reminder_interval_seconds: 0, clock_source: CurrentTimeSource::External, sleep_tool: true, }, @@ -643,26 +643,6 @@ sleep_tool = true Ok(()) } -#[tokio::test] -async fn load_config_rejects_zero_current_time_reminder_interval() -> std::io::Result<()> { - let error = load_current_time_reminder_config( - r#" -[features.current_time_reminder] -enabled = true -reminder_interval_seconds = 0 -"#, - ) - .await - .expect_err("zero reminder interval should be rejected"); - - assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput); - assert_eq!( - error.to_string(), - "features.current_time_reminder.reminder_interval_seconds must be positive" - ); - Ok(()) -} - async fn load_current_time_reminder_config(config_toml: &str) -> std::io::Result { let codex_home = tempdir()?; let config_toml = toml::from_str(config_toml).expect("TOML should deserialize"); diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 78188d92e2d7..58ae94906ebc 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -2685,12 +2685,6 @@ fn resolve_current_time_reminder_config( let reminder_interval_seconds = base .and_then(|config| config.reminder_interval_seconds) .unwrap_or(default.reminder_interval_seconds); - if reminder_interval_seconds == 0 { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - "features.current_time_reminder.reminder_interval_seconds must be positive", - )); - } Ok(Some(CurrentTimeReminderConfig { reminder_interval_seconds, diff --git a/codex-rs/features/src/feature_configs.rs b/codex-rs/features/src/feature_configs.rs index 2b638fce6e99..24036eee5826 100644 --- a/codex-rs/features/src/feature_configs.rs +++ b/codex-rs/features/src/feature_configs.rs @@ -147,7 +147,6 @@ pub struct CurrentTimeReminderConfigToml { #[serde(skip_serializing_if = "Option::is_none")] pub enabled: Option, #[serde(skip_serializing_if = "Option::is_none")] - #[schemars(range(min = 1))] pub reminder_interval_seconds: Option, #[serde(skip_serializing_if = "Option::is_none")] pub clock_source: Option, From 758b984640ad293371a2c91285a8684b750dd1d8 Mon Sep 17 00:00:00 2001 From: Rohit Arunachalam Date: Thu, 25 Jun 2026 11:13:16 -0700 Subject: [PATCH 2/4] Deliver zero-interval time reminders unconditionally --- codex-rs/core/src/session/time_reminder.rs | 1 + .../core/tests/suite/current_time_reminder.rs | 39 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/codex-rs/core/src/session/time_reminder.rs b/codex-rs/core/src/session/time_reminder.rs index 85831d29477a..8f612e8fcbc9 100644 --- a/codex-rs/core/src/session/time_reminder.rs +++ b/codex-rs/core/src/session/time_reminder.rs @@ -21,6 +21,7 @@ impl CurrentTimeReminderState { interval_seconds: u64, ) -> bool { let reminder_is_due = self.last_window_id.as_deref() != Some(window_id) + || interval_seconds == 0 || self.last_delivery_time.is_none_or(|last_delivery_time| { current_time .signed_duration_since(last_delivery_time) diff --git a/codex-rs/core/tests/suite/current_time_reminder.rs b/codex-rs/core/tests/suite/current_time_reminder.rs index 8ebf5d6b16f2..c5ebd0ed13a9 100644 --- a/codex-rs/core/tests/suite/current_time_reminder.rs +++ b/codex-rs/core/tests/suite/current_time_reminder.rs @@ -39,6 +39,7 @@ use pretty_assertions::assert_eq; use serde_json::json; const FIRST_REMINDER: &str = "It is 2026-06-17 17:34:15 UTC."; +const EARLIER_REMINDER: &str = "It is 2026-06-17 17:33:15 UTC."; const SECOND_REMINDER: &str = "It is 2026-06-17 17:35:15 UTC."; const THIRD_REMINDER: &str = "It is 2026-06-17 17:36:15 UTC."; const FIRST_TIME_UNIX_SECONDS: i64 = 1_781_717_655; @@ -162,6 +163,44 @@ async fn current_time_reminders_follow_time_interval_and_persist_in_history() -> Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn zero_current_time_reminder_interval_delivers_when_time_moves_backward() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + let responses = mount_sse_sequence( + &server, + vec![ + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), + ], + ) + .await; + let time_provider = Arc::new(TestTimeProvider::default()); + let test = test_codex() + .with_config(|config| { + enable_current_time_reminder(config, /*interval*/ 0, CurrentTimeSource::External) + }) + .with_external_time_provider(time_provider.clone()) + .build(&server) + .await?; + + test.submit_turn("first turn").await?; + time_provider + .current_time + .store(FIRST_TIME_UNIX_SECONDS - 60, Ordering::Relaxed); + test.submit_turn("second turn").await?; + + let requests = responses.requests(); + assert_eq!(requests.len(), 2); + assert_eq!(current_time_reminders(&requests[0]), vec![FIRST_REMINDER]); + assert_eq!( + current_time_reminders(&requests[1]), + vec![FIRST_REMINDER, EARLIER_REMINDER] + ); + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn system_time_source_adds_current_time_reminder() -> Result<()> { skip_if_no_network!(Ok(())); From 712a8b7f3e636fe8feea69923cac7638d6cf0b98 Mon Sep 17 00:00:00 2001 From: Rohit Arunachalam Date: Thu, 25 Jun 2026 04:20:39 -0700 Subject: [PATCH 3/4] Add current time reminder delivery mode config --- codex-rs/core/config.schema.json | 22 ++++++++++++++++++++++ codex-rs/core/src/config/config_tests.rs | 2 ++ codex-rs/core/src/config/mod.rs | 6 ++++++ codex-rs/core/src/session/config_lock.rs | 1 + codex-rs/features/src/feature_configs.rs | 13 +++++++++++++ codex-rs/features/src/lib.rs | 1 + 6 files changed, 45 insertions(+) diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index bfdd179d781e..907c71326b0a 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -810,6 +810,9 @@ "clock_source": { "$ref": "#/definitions/CurrentTimeSource" }, + "delivery_mode": { + "$ref": "#/definitions/CurrentTimeReminderDeliveryMode" + }, "enabled": { "type": "boolean" }, @@ -825,6 +828,25 @@ }, "type": "object" }, + "CurrentTimeReminderDeliveryMode": { + "description": "Which inference boundaries may receive current-time reminders.", + "oneOf": [ + { + "description": "Allow a reminder before any inference request once the interval is due.", + "enum": [ + "any_inference" + ], + "type": "string" + }, + { + "description": "Only allow a reminder after user input or tool output.", + "enum": [ + "after_user_or_tool_output" + ], + "type": "string" + } + ] + }, "CurrentTimeSource": { "enum": [ "system", diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 17779117f64a..237e67d6c041 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -627,11 +627,13 @@ current_time_reminder = true enabled = true reminder_interval_seconds = 0 clock_source = "external" +delivery_mode = "after_user_or_tool_output" sleep_tool = true "#, CurrentTimeReminderConfig { reminder_interval_seconds: 0, clock_source: CurrentTimeSource::External, + delivery_mode: CurrentTimeReminderDeliveryMode::AfterUserOrToolOutput, sleep_tool: true, }, ), diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 58ae94906ebc..897a31c8dbe5 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -59,6 +59,7 @@ use codex_exec_server::ExecutorFileSystem; use codex_exec_server::LOCAL_FS; use codex_features::CodeModeConfigToml; use codex_features::CurrentTimeReminderConfigToml; +use codex_features::CurrentTimeReminderDeliveryMode; use codex_features::CurrentTimeSource; use codex_features::Feature; use codex_features::FeatureConfigSource; @@ -1120,6 +1121,7 @@ pub struct RolloutBudgetConfig { pub struct CurrentTimeReminderConfig { pub reminder_interval_seconds: u64, pub clock_source: CurrentTimeSource, + pub delivery_mode: CurrentTimeReminderDeliveryMode, /// Whether to expose the input-interruptible `clock.sleep` tool. pub sleep_tool: bool, } @@ -1129,6 +1131,7 @@ impl Default for CurrentTimeReminderConfig { Self { reminder_interval_seconds: 1, clock_source: CurrentTimeSource::System, + delivery_mode: CurrentTimeReminderDeliveryMode::AnyInference, sleep_tool: false, } } @@ -2691,6 +2694,9 @@ fn resolve_current_time_reminder_config( clock_source: base .and_then(|config| config.clock_source) .unwrap_or(default.clock_source), + delivery_mode: base + .and_then(|config| config.delivery_mode) + .unwrap_or(default.delivery_mode), sleep_tool: base .and_then(|config| config.sleep_tool) .unwrap_or(default.sleep_tool), diff --git a/codex-rs/core/src/session/config_lock.rs b/codex-rs/core/src/session/config_lock.rs index b1adf9446664..606d9d2be0de 100644 --- a/codex-rs/core/src/session/config_lock.rs +++ b/codex-rs/core/src/session/config_lock.rs @@ -361,6 +361,7 @@ mod tests { enabled: Some(true), reminder_interval_seconds: Some(1), clock_source: Some(codex_features::CurrentTimeSource::System), + delivery_mode: Some(codex_features::CurrentTimeReminderDeliveryMode::AnyInference), sleep_tool: Some(false), })) ); diff --git a/codex-rs/features/src/feature_configs.rs b/codex-rs/features/src/feature_configs.rs index 24036eee5826..3a2ab50794c7 100644 --- a/codex-rs/features/src/feature_configs.rs +++ b/codex-rs/features/src/feature_configs.rs @@ -141,6 +141,17 @@ pub enum CurrentTimeSource { External, } +/// Which inference boundaries may receive current-time reminders. +#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum CurrentTimeReminderDeliveryMode { + /// Allow a reminder before any inference request once the interval is due. + #[default] + AnyInference, + /// Only allow a reminder after user input or tool output. + AfterUserOrToolOutput, +} + #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)] #[serde(deny_unknown_fields)] pub struct CurrentTimeReminderConfigToml { @@ -150,6 +161,8 @@ pub struct CurrentTimeReminderConfigToml { pub reminder_interval_seconds: Option, #[serde(skip_serializing_if = "Option::is_none")] pub clock_source: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub delivery_mode: Option, /// Expose the input-interruptible `clock.sleep` tool. #[serde(skip_serializing_if = "Option::is_none")] pub sleep_tool: Option, diff --git a/codex-rs/features/src/lib.rs b/codex-rs/features/src/lib.rs index 151e13f61575..3bce1a7d127f 100644 --- a/codex-rs/features/src/lib.rs +++ b/codex-rs/features/src/lib.rs @@ -18,6 +18,7 @@ mod feature_configs; mod legacy; pub use feature_configs::CodeModeConfigToml; pub use feature_configs::CurrentTimeReminderConfigToml; +pub use feature_configs::CurrentTimeReminderDeliveryMode; pub use feature_configs::CurrentTimeSource; pub use feature_configs::MultiAgentV2ConfigToml; pub use feature_configs::NetworkProxyConfigToml; From 06603fbe7ba6067ecde7428216347c75f30a4692 Mon Sep 17 00:00:00 2001 From: Rohit Arunachalam Date: Thu, 25 Jun 2026 04:35:47 -0700 Subject: [PATCH 4/4] Clarify current time reminder delivery mode --- codex-rs/core/config.schema.json | 2 +- codex-rs/features/src/feature_configs.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index 907c71326b0a..512c050a20c1 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -839,7 +839,7 @@ "type": "string" }, { - "description": "Only allow a reminder after user input or tool output.", + "description": "Allow reminders after user input or tool output; new context windows still force one.", "enum": [ "after_user_or_tool_output" ], diff --git a/codex-rs/features/src/feature_configs.rs b/codex-rs/features/src/feature_configs.rs index 3a2ab50794c7..9fc5df2d590a 100644 --- a/codex-rs/features/src/feature_configs.rs +++ b/codex-rs/features/src/feature_configs.rs @@ -148,7 +148,7 @@ pub enum CurrentTimeReminderDeliveryMode { /// Allow a reminder before any inference request once the interval is due. #[default] AnyInference, - /// Only allow a reminder after user input or tool output. + /// Allow reminders after user input or tool output; new context windows still force one. AfterUserOrToolOutput, }