Skip to content

Commit ed5d64e

Browse files
charley-oaicodex
andcommitted
Persist guardian boundary without cached trunk
Co-authored-by: Codex <noreply@openai.com>
1 parent 432f049 commit ed5d64e

2 files changed

Lines changed: 48 additions & 29 deletions

File tree

codex-rs/core/src/guardian/review.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -260,9 +260,10 @@ pub(crate) async fn review_approval_request_with_cancel(
260260
/// it is pinned to a read-only sandbox with `approval_policy = never` and
261261
/// nonessential agent features disabled. When the cached trunk session is idle,
262262
/// later approvals append onto that same guardian conversation to preserve a
263-
/// stable prompt-cache key. That cached trunk also carries the parent-history
264-
/// checkpoint used to slice future guardian transcript evidence. If the trunk
265-
/// is already busy, the review runs in an ephemeral fork from the last
263+
/// stable prompt-cache key. The guardian session manager retains the
264+
/// parent-history checkpoint used to slice future guardian transcript
265+
/// evidence, and mirrors it onto the cached trunk while one exists. If the
266+
/// trunk is already busy, the review runs in an ephemeral fork from the last
266267
/// committed trunk rollout so parallel approvals do not block each other or
267268
/// mutate the cached thread. The trunk is recreated when the effective
268269
/// review-session config changes, and any future compaction must continue to

codex-rs/core/src/guardian/review_session.rs

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ pub(crate) struct GuardianReviewSessionManager {
8080
struct GuardianReviewSessionState {
8181
trunk: Option<Arc<GuardianReviewSession>>,
8282
ephemeral_reviews: Vec<Arc<GuardianReviewSession>>,
83+
// Parent-session history index captured after the latest terminal guardian
84+
// review. This remains authoritative even when no reusable trunk is
85+
// currently cached.
86+
parent_history_boundary: Option<usize>,
8387
}
8488

8589
struct GuardianReviewSession {
@@ -89,9 +93,9 @@ struct GuardianReviewSession {
8993
has_prior_review: AtomicBool,
9094
review_lock: Mutex<()>,
9195
last_committed_rollout_items: Mutex<Option<Vec<RolloutItem>>>,
92-
// Parent-session history index captured after the latest terminal guardian
93-
// review. Future guardian prompts use it to slice parent transcript
94-
// evidence without persisting extra rollout metadata.
96+
// Mirror of the manager checkpoint while this reusable trunk is alive.
97+
// Keeping it on the trunk preserves the boundary across config-driven trunk
98+
// replacement without persisting extra rollout metadata.
9599
parent_history_boundary: Mutex<Option<usize>>,
96100
}
97101

@@ -159,10 +163,6 @@ impl GuardianReviewSessionReuseKey {
159163
}
160164

161165
impl GuardianReviewSession {
162-
async fn parent_history_boundary(&self) -> Option<usize> {
163-
*self.parent_history_boundary.lock().await
164-
}
165-
166166
async fn set_parent_history_boundary(&self, boundary: Option<usize>) {
167167
*self.parent_history_boundary.lock().await = boundary;
168168
}
@@ -241,15 +241,15 @@ impl Drop for EphemeralReviewCleanup {
241241

242242
impl GuardianReviewSessionManager {
243243
pub(crate) async fn parent_history_boundary(&self) -> Option<usize> {
244-
let trunk = self.state.lock().await.trunk.clone();
245-
match trunk {
246-
Some(trunk) => trunk.parent_history_boundary().await,
247-
None => None,
248-
}
244+
self.state.lock().await.parent_history_boundary
249245
}
250246

251247
pub(crate) async fn set_parent_history_boundary(&self, boundary: Option<usize>) {
252-
let trunk = self.state.lock().await.trunk.clone();
248+
let trunk = {
249+
let mut state = self.state.lock().await;
250+
state.parent_history_boundary = boundary;
251+
state.trunk.clone()
252+
};
253253
if let Some(trunk) = trunk {
254254
trunk.set_parent_history_boundary(boundary).await;
255255
}
@@ -278,7 +278,6 @@ impl GuardianReviewSessionManager {
278278
let deadline = tokio::time::Instant::now() + GUARDIAN_REVIEW_TIMEOUT;
279279
let next_reuse_key = GuardianReviewSessionReuseKey::from_spawn_config(&params.spawn_config);
280280
let mut stale_trunk_to_shutdown = None;
281-
let mut spawned_replacement_trunk = false;
282281
let trunk_candidate = match run_before_review_deadline(
283282
deadline,
284283
params.external_cancel.as_ref(),
@@ -295,6 +294,7 @@ impl GuardianReviewSessionManager {
295294
}
296295

297296
if state.trunk.is_none() {
297+
let parent_history_boundary = state.parent_history_boundary;
298298
let spawn_cancel_token = CancellationToken::new();
299299
let review_session = match run_before_review_deadline_with_cancel(
300300
deadline,
@@ -306,6 +306,7 @@ impl GuardianReviewSessionManager {
306306
next_reuse_key.clone(),
307307
spawn_cancel_token.clone(),
308308
/*initial_history*/ None,
309+
parent_history_boundary,
309310
)),
310311
)
311312
.await
@@ -316,7 +317,6 @@ impl GuardianReviewSessionManager {
316317
}
317318
Err(outcome) => return outcome,
318319
};
319-
spawned_replacement_trunk = true;
320320
state.trunk = Some(Arc::clone(&review_session));
321321
}
322322

@@ -325,14 +325,6 @@ impl GuardianReviewSessionManager {
325325
Err(outcome) => return outcome,
326326
};
327327

328-
if spawned_replacement_trunk
329-
&& let (Some(stale_trunk), Some(trunk)) =
330-
(stale_trunk_to_shutdown.as_ref(), trunk_candidate.as_ref())
331-
{
332-
let boundary = stale_trunk.parent_history_boundary().await;
333-
trunk.set_parent_history_boundary(boundary).await;
334-
}
335-
336328
if let Some(review_session) = stale_trunk_to_shutdown {
337329
review_session.shutdown_in_background();
338330
}
@@ -386,14 +378,15 @@ impl GuardianReviewSessionManager {
386378
let reuse_key = GuardianReviewSessionReuseKey::from_spawn_config(
387379
codex.session.get_config().await.as_ref(),
388380
);
389-
self.state.lock().await.trunk = Some(Arc::new(GuardianReviewSession {
381+
let mut state = self.state.lock().await;
382+
state.trunk = Some(Arc::new(GuardianReviewSession {
390383
reuse_key,
391384
codex,
392385
cancel_token: CancellationToken::new(),
393386
has_prior_review: AtomicBool::new(false),
394387
review_lock: Mutex::new(()),
395388
last_committed_rollout_items: Mutex::new(None),
396-
parent_history_boundary: Mutex::new(None),
389+
parent_history_boundary: Mutex::new(state.parent_history_boundary),
397390
}));
398391
}
399392

@@ -473,6 +466,7 @@ impl GuardianReviewSessionManager {
473466
reuse_key,
474467
spawn_cancel_token.clone(),
475468
initial_history,
469+
/*parent_history_boundary*/ None,
476470
)),
477471
)
478472
.await
@@ -501,6 +495,7 @@ async fn spawn_guardian_review_session(
501495
reuse_key: GuardianReviewSessionReuseKey,
502496
cancel_token: CancellationToken,
503497
initial_history: Option<InitialHistory>,
498+
parent_history_boundary: Option<usize>,
504499
) -> anyhow::Result<GuardianReviewSession> {
505500
let has_prior_review = initial_history.is_some();
506501
let codex = run_codex_thread_interactive(
@@ -522,7 +517,7 @@ async fn spawn_guardian_review_session(
522517
has_prior_review: AtomicBool::new(has_prior_review),
523518
review_lock: Mutex::new(()),
524519
last_committed_rollout_items: Mutex::new(None),
525-
parent_history_boundary: Mutex::new(None),
520+
parent_history_boundary: Mutex::new(parent_history_boundary),
526521
})
527522
}
528523

@@ -911,4 +906,27 @@ mod tests {
911906
assert_eq!(outcome.unwrap(), 42);
912907
assert!(!cancel_token.is_cancelled());
913908
}
909+
910+
#[tokio::test(flavor = "current_thread")]
911+
async fn parent_history_boundary_persists_without_cached_trunk() {
912+
let manager = GuardianReviewSessionManager::default();
913+
914+
manager.set_parent_history_boundary(Some(7)).await;
915+
assert_eq!(manager.parent_history_boundary().await, Some(7));
916+
917+
let state = manager.state.lock().await;
918+
assert_eq!(state.parent_history_boundary, Some(7));
919+
assert!(state.trunk.is_none());
920+
}
921+
922+
#[tokio::test(flavor = "current_thread")]
923+
async fn clearing_parent_history_boundary_without_cached_trunk_updates_manager_state() {
924+
let manager = GuardianReviewSessionManager::default();
925+
926+
manager.set_parent_history_boundary(Some(7)).await;
927+
manager.set_parent_history_boundary(None).await;
928+
929+
assert_eq!(manager.parent_history_boundary().await, None);
930+
assert_eq!(manager.state.lock().await.parent_history_boundary, None);
931+
}
914932
}

0 commit comments

Comments
 (0)