Retain the last selected step context for each turn (#41429)

## What changed

- Store the most recently captured execution `StepContext` in the active turn state.
- Keep speculative model-fallback captures from replacing that context, and update it only when the fallback is selected for remote compaction.
- Preserve the retained context when a turn completes or is interrupted.

## Testing

- Add coverage for completed and interrupted turns, successful primary compaction, fallback compaction, and interruption while a fallback is running.

GitOrigin-RevId: c7dee36ec8e88ba2f4fb4a9c0322e5c86a0caf0e
This commit is contained in:
Krish Chainani authored and copyberry committed 2026-08-28 22:26:23 +00:00
1 parent 2008d27e98
commit 0d22692962
7 files changed
+356 -36

No files matched your search

+48 -3
View File
@@ -3305,12 +3305,28 @@ impl Session {
Ok(world_state)
}
/// Captures one request-scoped view of dynamic state.
/// Retains the step captured for execution.
pub(crate) async fn set_last_known_step_context(&self, step_context: &Arc<StepContext>) {
let turn_state = {
let active_turn = self.active_turn.lock().await;
active_turn.as_ref().and_then(|active_turn| {
active_turn
.task
.as_ref()
.filter(|task| task.turn_context.sub_id == step_context.turn.sub_id)
.map(|_| Arc::clone(&active_turn.turn_state))
})
};
if let Some(turn_state) = turn_state {
turn_state.lock().await.last_known_step_context = Some(Arc::clone(step_context));
}
}
/// Captures one request-scoped view of dynamic state and retains it for the active turn.
///
/// This may refresh filesystem-derived state. Normal turns should call it only from
/// `run_turn` and pass the result down; standalone request or history boundaries may capture
/// their own step.
#[tracing::instrument(name = "step_context.capture", level = "info", skip_all)]
/// their own step. Use speculative capture for a step that may not execute.
pub(crate) async fn capture_step_context(
self: &Arc<Self>,
turn_context: Arc<TurnContext>,
@@ -3329,6 +3345,35 @@ impl Session {
turn_context: Arc<TurnContext>,
cancellation_token: &CancellationToken,
required_servers: &[String],
) -> CodexResult<Arc<StepContext>> {
let step_context = self
.capture_step_context_inner(turn_context, cancellation_token, required_servers)
.await?;
self.set_last_known_step_context(&step_context).await;
Ok(step_context)
}
/// Prepares a candidate step without replacing the active turn's retained context.
/// The caller must retain it explicitly if it is selected for execution.
async fn capture_speculative_step_context(
self: &Arc<Self>,
turn_context: Arc<TurnContext>,
cancellation_token: &CancellationToken,
) -> CodexResult<Arc<StepContext>> {
self.capture_step_context_inner(
turn_context,
cancellation_token,
/*required_servers*/ &[],
)
.await
}
#[tracing::instrument(name = "step_context.capture", level = "info", skip_all)]
async fn capture_step_context_inner(
self: &Arc<Self>,
turn_context: Arc<TurnContext>,
cancellation_token: &CancellationToken,
required_servers: &[String],
) -> CodexResult<Arc<StepContext>> {
// Capture once before asynchronous planning; all request consumers
// retain this immutable settings version even if the turn is updated.