mirror of
https://github.com/openai/codex.git
synced 2026-10-24 18:18:41 +00:00
## Summary - move regular-turn context diff/full-context persistence into `run_turn` so pre-turn compaction runs before incoming context updates are recorded - after successful pre-turn compaction, rely on a cleared `reference_context_item` to trigger full context reinjection on the follow-up regular turn (manual `/compact` keeps replacement history summary-only and also clears the baseline) - preserve `<model_switch>` when full context is reinjected, and inject it *before* the rest of the full-context items - scope `reference_context_item` and `previous_model` to regular user turns only so standalone tasks (`/compact`, shell, review, undo) cannot suppress future reinjection or `<model_switch>` behavior - make context-diff persistence + `reference_context_item` updates explicit in the regular-turn path, with clearer docs/comments around the invariant - stop persisting local `/compact` `RolloutItem::TurnContext` snapshots (only regular turns persist `TurnContextItem` now) - simplify resume/fork previous-model/reference-baseline hydration by looking up the last surviving turn context from rollout lifecycle events, including rollback and compaction-crossing handling - remove the legacy fallback that guessed from bare `TurnContext` rollouts without lifecycle events - update compaction/remote-compaction/model-visible snapshots and compact test assertions (including remote compaction mock response shape) ## Why We were persisting incoming context items before spawning the regular turn task, which let pre-turn compaction requests accidentally include incoming context diffs without the new user message. Fixing that exposed follow-on baseline issues around `/compact`, resume/fork, and standalone tasks that could cause duplicate context injection or suppress `<model_switch>` instructions. This PR re-centers the invariants around regular turns: - regular turns persist model-visible context diffs/full reinjection and update the `reference_context_item` - standalone tasks do not advance those regular-turn baselines - compaction clears the baseline when replacement history may have stripped the referenced context diffs ## Follow-ups (TODOs left in code) - `TODO(ccunningham)`: fix rollback/backtracking baseline handling more comprehensively - `TODO(ccunningham)`: include pending incoming context items in pre-turn compaction threshold estimation - `TODO(ccunningham)`: inject updated personality spec alongside `<model_switch>` so some model-switch paths can avoid forced full reinjection - `TODO(ccunningham)`: review task turn lifecycle (`TurnStarted`/`TurnComplete`) behavior and emit task-start context diffs for task types that should have them (excluding `/compact`) ## Validation - `just fmt` - CI should cover the updated compaction/resume/model-visible snapshot expectations and rollout-hydration behavior - I did **not** rerun the full local test suite after the latest resume-lookup / rollout-persistence simplifications
150 lines
4.6 KiB
Rust
150 lines
4.6 KiB
Rust
use crate::codex::TurnContext;
|
|
use crate::environment_context::EnvironmentContext;
|
|
use crate::shell::Shell;
|
|
use codex_execpolicy::Policy;
|
|
use codex_protocol::config_types::Personality;
|
|
use codex_protocol::models::DeveloperInstructions;
|
|
use codex_protocol::models::ResponseItem;
|
|
use codex_protocol::openai_models::ModelInfo;
|
|
use codex_protocol::protocol::TurnContextItem;
|
|
|
|
fn build_environment_update_item(
|
|
previous: Option<&TurnContextItem>,
|
|
next: &TurnContext,
|
|
shell: &Shell,
|
|
) -> Option<ResponseItem> {
|
|
let prev = previous?;
|
|
let prev_context = EnvironmentContext::from_turn_context_item(prev, shell);
|
|
let next_context = EnvironmentContext::from_turn_context(next, shell);
|
|
if prev_context.equals_except_shell(&next_context) {
|
|
return None;
|
|
}
|
|
|
|
Some(ResponseItem::from(
|
|
EnvironmentContext::diff_from_turn_context_item(prev, next, shell),
|
|
))
|
|
}
|
|
|
|
fn build_permissions_update_item(
|
|
previous: Option<&TurnContextItem>,
|
|
next: &TurnContext,
|
|
exec_policy: &Policy,
|
|
) -> Option<ResponseItem> {
|
|
let prev = previous?;
|
|
if prev.sandbox_policy == next.sandbox_policy && prev.approval_policy == next.approval_policy {
|
|
return None;
|
|
}
|
|
|
|
Some(
|
|
DeveloperInstructions::from_policy(
|
|
&next.sandbox_policy,
|
|
next.approval_policy,
|
|
exec_policy,
|
|
&next.cwd,
|
|
)
|
|
.into(),
|
|
)
|
|
}
|
|
|
|
fn build_collaboration_mode_update_item(
|
|
previous: Option<&TurnContextItem>,
|
|
next: &TurnContext,
|
|
) -> Option<ResponseItem> {
|
|
let prev = previous?;
|
|
if prev.collaboration_mode.as_ref() != Some(&next.collaboration_mode) {
|
|
// If the next mode has empty developer instructions, this returns None and we emit no
|
|
// update, so prior collaboration instructions remain in the prompt history.
|
|
Some(DeveloperInstructions::from_collaboration_mode(&next.collaboration_mode)?.into())
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn build_personality_update_item(
|
|
previous: Option<&TurnContextItem>,
|
|
next: &TurnContext,
|
|
personality_feature_enabled: bool,
|
|
) -> Option<ResponseItem> {
|
|
if !personality_feature_enabled {
|
|
return None;
|
|
}
|
|
let previous = previous?;
|
|
if next.model_info.slug != previous.model {
|
|
return None;
|
|
}
|
|
|
|
if let Some(personality) = next.personality
|
|
&& next.personality != previous.personality
|
|
{
|
|
let model_info = &next.model_info;
|
|
let personality_message = personality_message_for(model_info, personality);
|
|
personality_message
|
|
.map(|message| DeveloperInstructions::personality_spec_message(message).into())
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub(crate) fn personality_message_for(
|
|
model_info: &ModelInfo,
|
|
personality: Personality,
|
|
) -> Option<String> {
|
|
model_info
|
|
.model_messages
|
|
.as_ref()
|
|
.and_then(|spec| spec.get_personality_message(Some(personality)))
|
|
.filter(|message| !message.is_empty())
|
|
}
|
|
|
|
pub(crate) fn build_model_instructions_update_item(
|
|
previous_user_turn_model: Option<&str>,
|
|
next: &TurnContext,
|
|
) -> Option<ResponseItem> {
|
|
let previous_model = previous_user_turn_model?;
|
|
if previous_model == next.model_info.slug {
|
|
return None;
|
|
}
|
|
|
|
let model_instructions = next.model_info.get_model_instructions(next.personality);
|
|
if model_instructions.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
Some(DeveloperInstructions::model_switch_message(model_instructions).into())
|
|
}
|
|
|
|
pub(crate) fn build_settings_update_items(
|
|
previous: Option<&TurnContextItem>,
|
|
previous_user_turn_model: Option<&str>,
|
|
next: &TurnContext,
|
|
shell: &Shell,
|
|
exec_policy: &Policy,
|
|
personality_feature_enabled: bool,
|
|
) -> Vec<ResponseItem> {
|
|
let mut update_items = Vec::new();
|
|
|
|
// Keep model-switch instructions first so model-specific guidance is read before
|
|
// any other context diffs on this turn.
|
|
if let Some(model_instructions_item) =
|
|
build_model_instructions_update_item(previous_user_turn_model, next)
|
|
{
|
|
update_items.push(model_instructions_item);
|
|
}
|
|
if let Some(env_item) = build_environment_update_item(previous, next, shell) {
|
|
update_items.push(env_item);
|
|
}
|
|
if let Some(permissions_item) = build_permissions_update_item(previous, next, exec_policy) {
|
|
update_items.push(permissions_item);
|
|
}
|
|
if let Some(collaboration_mode_item) = build_collaboration_mode_update_item(previous, next) {
|
|
update_items.push(collaboration_mode_item);
|
|
}
|
|
if let Some(personality_item) =
|
|
build_personality_update_item(previous, next, personality_feature_enabled)
|
|
{
|
|
update_items.push(personality_item);
|
|
}
|
|
|
|
update_items
|
|
}
|