[core] debounce current-time reminders by elapsed time (#29659)

## Summary
- rename `reminder_interval_model_requests` to
`reminder_interval_seconds`
- read the configured time provider before every model request and
inject a reminder only after the configured number of seconds has
elapsed
- preserve immediate first delivery and forced delivery after compaction
changes the context window

## Tests
- `just test -p codex-core current_time_reminder`
This commit is contained in:
rka-oai authored and GitHub committed 2026-06-23 10:13:27 -07:00
1 parent 02c326f646
commit 9fe689783d
9 files changed
+60 -38

No files matched your search

+28 -15
View File
@@ -1,3 +1,5 @@
use chrono::DateTime;
use chrono::Utc;
use codex_protocol::error::CodexErr;
use codex_protocol::error::Result as CodexResult;
@@ -7,18 +9,27 @@ use crate::context::ContextualUserFragment;
#[derive(Default)]
pub(crate) struct CurrentTimeReminderState {
model_requests_since_delivery: u64,
last_delivery_time: Option<DateTime<Utc>>,
last_window_id: Option<String>,
}
impl CurrentTimeReminderState {
fn take_reminder_due(&mut self, window_id: &str, interval: u64) -> bool {
self.model_requests_since_delivery = self.model_requests_since_delivery.saturating_add(1);
fn take_reminder_due(
&mut self,
window_id: &str,
current_time: DateTime<Utc>,
interval_seconds: u64,
) -> bool {
let reminder_is_due = self.last_window_id.as_deref() != Some(window_id)
|| self.model_requests_since_delivery >= interval;
|| self.last_delivery_time.is_none_or(|last_delivery_time| {
current_time
.signed_duration_since(last_delivery_time)
.num_seconds()
>= i64::try_from(interval_seconds).unwrap_or(i64::MAX)
});
if reminder_is_due {
self.model_requests_since_delivery = 0;
self.last_delivery_time = Some(current_time);
self.last_window_id = Some(window_id.to_string());
}
@@ -35,16 +46,6 @@ pub(super) async fn maybe_record_current_time_reminder(
return Ok(());
};
let reminder_is_due = {
let mut state = sess.state.lock().await;
state
.current_time_reminder
.take_reminder_due(window_id, config.reminder_interval_model_requests)
};
if !reminder_is_due {
return Ok(());
}
let current_time = sess
.services
.time_provider
@@ -52,6 +53,18 @@ pub(super) async fn maybe_record_current_time_reminder(
.await
.map_err(|err| CodexErr::Fatal(format!("failed to read current time: {err:#}")))?;
let reminder_is_due = {
let mut state = sess.state.lock().await;
state.current_time_reminder.take_reminder_due(
window_id,
current_time,
config.reminder_interval_seconds,
)
};
if !reminder_is_due {
return Ok(());
}
let response_item =
ContextualUserFragment::into(crate::context::CurrentTimeReminder::new(current_time));
sess.record_conversation_items(turn_context, std::slice::from_ref(&response_item))