mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
Install full context in compaction replacement history (#51117)
## Why Manual, pre-turn, and post-turn compaction previously deferred rebuilding initial context until the next turn. Their saved replacement history could omit refreshed instructions and skill catalogs. ## What changed - Install initial context during local and remote compaction, before the last real user message or summary, keeping the compaction summary last. - Persist matching full world-state and turn-context baselines with every replacement instead of retaining only partial extension metadata. - Build replacement context and recompute token usage for the current model even when the previous model performs compaction. Reuse that captured context for eligible current-model retries. - Capture manual compaction context centrally and propagate preparation errors to the task runner. ## Testing Update assertions and snapshots for context ordering, refreshed instructions, resume and fork history, current-model checkpoint baselines, and cloud skill catalog retention across compaction and resume. GitOrigin-RevId: 8572a76c31c6884a3c580b3bbb350d49af876bd1
This commit is contained in:
@@ -58,23 +58,6 @@ pub use codex_prompts::SUMMARIZATION_PROMPT;
|
||||
pub use codex_prompts::SUMMARY_PREFIX;
|
||||
const COMPACT_USER_MESSAGE_MAX_TOKENS: usize = 20_000;
|
||||
|
||||
/// Controls whether compaction replacement history must include initial context.
|
||||
///
|
||||
/// Pre-turn/manual compaction variants use `DoNotInject`: they replace history with a summary and
|
||||
/// clear `reference_context_item`, so the next regular turn will fully reinject initial context
|
||||
/// after compaction.
|
||||
///
|
||||
/// Mid-turn compaction must use `BeforeLastUserMessage` because the model is trained to see the
|
||||
/// compaction summary as the last item in history after mid-turn compaction; we therefore inject
|
||||
/// initial context into the replacement history just above the last real user message.
|
||||
pub(crate) enum InitialContextInjection {
|
||||
BeforeLastUserMessage {
|
||||
world_state: Arc<WorldState>,
|
||||
step_context: Arc<StepContext>,
|
||||
},
|
||||
DoNotInject,
|
||||
}
|
||||
|
||||
/// Metadata for a new compaction checkpoint, kept separate from its replacement history.
|
||||
///
|
||||
/// `Session::replace_compacted_history` assigns missing item IDs before constructing the persisted
|
||||
@@ -89,32 +72,28 @@ pub(crate) struct CompactedHistoryMetadata {
|
||||
pub(crate) reviewer_compaction_hash: Option<String>,
|
||||
}
|
||||
|
||||
/// Render replacement context and its comparison baseline from the same captured step.
|
||||
/// Initial context is installed only after compaction returns, above the last real user message
|
||||
/// (or the summary when no user message remains), keeping the compaction summary last.
|
||||
pub(crate) async fn build_compaction_initial_context(
|
||||
sess: &Session,
|
||||
initial_context_injection: &InitialContextInjection,
|
||||
) -> (Vec<ResponseItemEnvelope>, Option<WorldStateSnapshot>) {
|
||||
// Return the rendered state with its items so history and its baseline stay identical.
|
||||
match initial_context_injection {
|
||||
InitialContextInjection::BeforeLastUserMessage {
|
||||
world_state,
|
||||
step_context,
|
||||
} => {
|
||||
let (items, snapshot) = sess
|
||||
.build_initial_context_with_world_state(step_context, world_state.as_ref())
|
||||
.await;
|
||||
(
|
||||
items.into_iter().map(ResponseItemEnvelope::new).collect(),
|
||||
Some(snapshot),
|
||||
)
|
||||
}
|
||||
InitialContextInjection::DoNotInject => (Vec::new(), None),
|
||||
}
|
||||
step_context: &StepContext,
|
||||
world_state: &WorldState,
|
||||
) -> (Vec<ResponseItemEnvelope>, WorldStateSnapshot) {
|
||||
let (items, snapshot) = sess
|
||||
.build_initial_context_with_world_state(step_context, world_state)
|
||||
.await;
|
||||
(
|
||||
items.into_iter().map(ResponseItemEnvelope::new).collect(),
|
||||
snapshot,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) async fn run_inline_auto_compact_task(
|
||||
sess: Arc<Session>,
|
||||
turn_context: Arc<TurnContext>,
|
||||
initial_context_injection: InitialContextInjection,
|
||||
replacement_step_context: Arc<StepContext>,
|
||||
world_state: Arc<WorldState>,
|
||||
reason: CompactionReason,
|
||||
phase: CompactionPhase,
|
||||
) -> CodexResult<()> {
|
||||
@@ -133,11 +112,15 @@ pub(crate) async fn run_inline_auto_compact_task(
|
||||
run_compact_task_inner(
|
||||
sess,
|
||||
turn_context,
|
||||
replacement_step_context,
|
||||
input,
|
||||
initial_context_injection,
|
||||
CompactionTrigger::Auto,
|
||||
reason,
|
||||
phase,
|
||||
world_state,
|
||||
CompactionTurnMetadata::new(
|
||||
CompactionTrigger::Auto,
|
||||
reason,
|
||||
CompactionImplementation::Responses,
|
||||
phase,
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
@@ -145,18 +128,22 @@ pub(crate) async fn run_inline_auto_compact_task(
|
||||
|
||||
pub(crate) async fn run_compact_task(
|
||||
sess: Arc<Session>,
|
||||
turn_context: Arc<TurnContext>,
|
||||
step_context: Arc<StepContext>,
|
||||
world_state: Arc<WorldState>,
|
||||
input: Vec<UserInput>,
|
||||
) -> CodexResult<()> {
|
||||
sess.emit_turn_started(&turn_context).await;
|
||||
run_compact_task_inner(
|
||||
sess.clone(),
|
||||
turn_context,
|
||||
Arc::clone(&step_context.turn),
|
||||
step_context,
|
||||
input,
|
||||
InitialContextInjection::DoNotInject,
|
||||
CompactionTrigger::Manual,
|
||||
CompactionReason::UserRequested,
|
||||
CompactionPhase::StandaloneTurn,
|
||||
world_state,
|
||||
CompactionTurnMetadata::new(
|
||||
CompactionTrigger::Manual,
|
||||
CompactionReason::UserRequested,
|
||||
CompactionImplementation::Responses,
|
||||
CompactionPhase::StandaloneTurn,
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
@@ -165,14 +152,14 @@ pub(crate) async fn run_compact_task(
|
||||
async fn run_compact_task_inner(
|
||||
sess: Arc<Session>,
|
||||
turn_context: Arc<TurnContext>,
|
||||
replacement_step_context: Arc<StepContext>,
|
||||
input: Vec<UserInput>,
|
||||
initial_context_injection: InitialContextInjection,
|
||||
trigger: CompactionTrigger,
|
||||
reason: CompactionReason,
|
||||
phase: CompactionPhase,
|
||||
world_state: Arc<WorldState>,
|
||||
compaction_metadata: CompactionTurnMetadata,
|
||||
) -> CodexResult<()> {
|
||||
let compaction_metadata =
|
||||
CompactionTurnMetadata::new(trigger, reason, CompactionImplementation::Responses, phase);
|
||||
let trigger = compaction_metadata.trigger();
|
||||
let reason = compaction_metadata.reason();
|
||||
let phase = compaction_metadata.phase();
|
||||
let attempt = CompactionAnalyticsAttempt::begin(
|
||||
sess.as_ref(),
|
||||
turn_context.as_ref(),
|
||||
@@ -201,8 +188,9 @@ async fn run_compact_task_inner(
|
||||
let result = run_compact_task_inner_impl(
|
||||
Arc::clone(&sess),
|
||||
Arc::clone(&turn_context),
|
||||
replacement_step_context,
|
||||
input,
|
||||
initial_context_injection,
|
||||
world_state,
|
||||
compaction_metadata,
|
||||
)
|
||||
.await;
|
||||
@@ -250,8 +238,9 @@ async fn run_compact_task_inner(
|
||||
async fn run_compact_task_inner_impl(
|
||||
sess: Arc<Session>,
|
||||
turn_context: Arc<TurnContext>,
|
||||
replacement_step_context: Arc<StepContext>,
|
||||
input: Vec<UserInput>,
|
||||
initial_context_injection: InitialContextInjection,
|
||||
world_state: Arc<WorldState>,
|
||||
compaction_metadata: CompactionTurnMetadata,
|
||||
) -> CodexResult<String> {
|
||||
let compaction_item = TurnItem::ContextCompaction(ContextCompactionItem::new());
|
||||
@@ -371,20 +360,13 @@ async fn run_compact_task_inner_impl(
|
||||
let (window_number, window_ids) = sess.advance_auto_compact_window().await;
|
||||
|
||||
let (initial_context, world_state_baseline) =
|
||||
build_compaction_initial_context(sess.as_ref(), &initial_context_injection).await;
|
||||
if !initial_context.is_empty() {
|
||||
new_history =
|
||||
insert_initial_context_before_last_real_user_or_summary(new_history, initial_context);
|
||||
}
|
||||
let reference_context_item = match initial_context_injection {
|
||||
InitialContextInjection::DoNotInject => None,
|
||||
InitialContextInjection::BeforeLastUserMessage { step_context, .. } => {
|
||||
Some(step_context.to_turn_context_item())
|
||||
}
|
||||
};
|
||||
build_compaction_initial_context(sess.as_ref(), &replacement_step_context, &world_state)
|
||||
.await;
|
||||
new_history =
|
||||
insert_initial_context_before_last_real_user_or_summary(new_history, initial_context);
|
||||
sess.replace_compacted_history(
|
||||
new_history,
|
||||
reference_context_item,
|
||||
replacement_step_context.to_turn_context_item(),
|
||||
world_state_baseline,
|
||||
CompactedHistoryMetadata {
|
||||
input_goal_ids,
|
||||
@@ -397,7 +379,8 @@ async fn run_compact_task_inner_impl(
|
||||
},
|
||||
)
|
||||
.await;
|
||||
sess.recompute_token_usage(&turn_context).await;
|
||||
sess.recompute_token_usage(&replacement_step_context.turn)
|
||||
.await;
|
||||
|
||||
sess.emit_turn_item_completed(&turn_context, compaction_item)
|
||||
.await;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::session::turn_context::TurnContext;
|
||||
use codex_analytics::CompactionImplementation;
|
||||
use codex_analytics::CompactionReason;
|
||||
use codex_otel::SessionTelemetry;
|
||||
@@ -6,13 +7,28 @@ use codex_protocol::error::CodexErrorDetails;
|
||||
use tracing::warn;
|
||||
|
||||
/// Returns whether a failed compaction attempt should use the current model.
|
||||
pub(crate) fn should_retry_with_current_model(error: &CodexErr) -> bool {
|
||||
!matches!(
|
||||
pub(crate) fn should_retry_with_current_model(
|
||||
error: &CodexErr,
|
||||
previous: &TurnContext,
|
||||
current: &TurnContext,
|
||||
) -> bool {
|
||||
if matches!(
|
||||
error.details(),
|
||||
CodexErrorDetails::TurnAborted
|
||||
| CodexErrorDetails::Interrupted
|
||||
| CodexErrorDetails::SessionBudgetExceeded
|
||||
)
|
||||
) || (previous.model_info().slug == current.model_info().slug
|
||||
&& previous.cyber_access_program == current.cyber_access_program)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
current.provider.info().is_openai()
|
||||
&& current
|
||||
.auth_manager
|
||||
.as_deref()
|
||||
.and_then(codex_login::AuthManager::auth_cached)
|
||||
.is_some_and(|auth| auth.uses_codex_backend() || auth.is_api_key_auth())
|
||||
}
|
||||
|
||||
pub(crate) fn record_model_fallback(
|
||||
|
||||
@@ -7,7 +7,6 @@ use crate::client_common::ResponseEvent;
|
||||
use crate::compact::CompactedHistoryMetadata;
|
||||
use crate::compact::CompactionAnalyticsAttempt;
|
||||
use crate::compact::CompactionAnalyticsDetails;
|
||||
use crate::compact::InitialContextInjection;
|
||||
use crate::compact::build_compaction_initial_context;
|
||||
use crate::compact::compaction_status_from_result;
|
||||
use crate::compact::insert_initial_context_before_last_real_user_or_summary;
|
||||
@@ -15,6 +14,7 @@ use crate::compact_model_fallback::record_model_fallback;
|
||||
use crate::compact_model_fallback::should_retry_with_current_model;
|
||||
use crate::compact_remote_history::HistoryItemGroup;
|
||||
use crate::compact_remote_history::history_item_groups;
|
||||
use crate::context::world_state::WorldState;
|
||||
use crate::context_manager::estimate_item_token_count;
|
||||
use crate::hook_runtime::PostCompactHookOutcome;
|
||||
use crate::hook_runtime::PreCompactHookOutcome;
|
||||
@@ -56,7 +56,6 @@ use codex_rollout_trace::InferenceTraceContext;
|
||||
use codex_utils_output_truncation::approx_token_count;
|
||||
use codex_utils_output_truncation::truncate_text;
|
||||
use futures::StreamExt;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
#[path = "compact_remote_v2_attempt.rs"]
|
||||
mod attempt;
|
||||
@@ -81,9 +80,9 @@ const MAX_REMOTE_COMPACTION_V2_STREAM_RETRIES: u64 = 2;
|
||||
pub(crate) async fn run_inline_remote_auto_compact_task(
|
||||
sess: Arc<Session>,
|
||||
step_context: Arc<StepContext>,
|
||||
fallback_step_context: Option<Arc<StepContext>>,
|
||||
replacement_step_context: Arc<StepContext>,
|
||||
client_session: &mut ModelClientSession,
|
||||
initial_context_injection: InitialContextInjection,
|
||||
world_state: Arc<WorldState>,
|
||||
reason: CompactionReason,
|
||||
phase: CompactionPhase,
|
||||
) -> CodexResult<()> {
|
||||
@@ -96,9 +95,9 @@ pub(crate) async fn run_inline_remote_auto_compact_task(
|
||||
run_remote_compact_task_inner(
|
||||
&sess,
|
||||
&step_context,
|
||||
fallback_step_context.as_ref(),
|
||||
&replacement_step_context,
|
||||
Some(client_session),
|
||||
initial_context_injection,
|
||||
world_state,
|
||||
compaction_metadata,
|
||||
)
|
||||
.await
|
||||
@@ -106,14 +105,9 @@ pub(crate) async fn run_inline_remote_auto_compact_task(
|
||||
|
||||
pub(crate) async fn run_remote_compact_task(
|
||||
sess: Arc<Session>,
|
||||
turn_context: Arc<TurnContext>,
|
||||
step_context: Arc<StepContext>,
|
||||
world_state: Arc<WorldState>,
|
||||
) -> CodexResult<()> {
|
||||
// Standalone compaction is its own request boundary, so it captures a fresh step.
|
||||
let step_context = sess
|
||||
.capture_step_context(Arc::clone(&turn_context), &CancellationToken::new())
|
||||
.await?;
|
||||
sess.emit_turn_started(&turn_context).await;
|
||||
|
||||
let compaction_metadata = CompactionTurnMetadata::new(
|
||||
CompactionTrigger::Manual,
|
||||
CompactionReason::UserRequested,
|
||||
@@ -123,9 +117,9 @@ pub(crate) async fn run_remote_compact_task(
|
||||
run_remote_compact_task_inner(
|
||||
&sess,
|
||||
&step_context,
|
||||
/*fallback_step_context*/ None,
|
||||
&step_context,
|
||||
/*client_session*/ None,
|
||||
InitialContextInjection::DoNotInject,
|
||||
world_state,
|
||||
compaction_metadata,
|
||||
)
|
||||
.await
|
||||
@@ -134,9 +128,9 @@ pub(crate) async fn run_remote_compact_task(
|
||||
async fn run_remote_compact_task_inner(
|
||||
sess: &Arc<Session>,
|
||||
step_context: &Arc<StepContext>,
|
||||
fallback_step_context: Option<&Arc<StepContext>>,
|
||||
replacement_step_context: &Arc<StepContext>,
|
||||
client_session: Option<&mut ModelClientSession>,
|
||||
initial_context_injection: InitialContextInjection,
|
||||
world_state: Arc<WorldState>,
|
||||
compaction_metadata: CompactionTurnMetadata,
|
||||
) -> CodexResult<()> {
|
||||
let turn_context = &step_context.turn;
|
||||
@@ -176,9 +170,9 @@ async fn run_remote_compact_task_inner(
|
||||
let result = run_remote_compact_task_inner_impl(
|
||||
sess,
|
||||
step_context,
|
||||
fallback_step_context,
|
||||
replacement_step_context,
|
||||
client_session,
|
||||
initial_context_injection,
|
||||
world_state,
|
||||
compaction_metadata,
|
||||
&mut analytics_details,
|
||||
)
|
||||
@@ -222,9 +216,9 @@ async fn run_remote_compact_task_inner(
|
||||
async fn run_remote_compact_task_inner_impl(
|
||||
sess: &Arc<Session>,
|
||||
step_context: &Arc<StepContext>,
|
||||
fallback_step_context: Option<&Arc<StepContext>>,
|
||||
replacement_step_context: &Arc<StepContext>,
|
||||
mut client_session: Option<&mut ModelClientSession>,
|
||||
initial_context_injection: InitialContextInjection,
|
||||
world_state: Arc<WorldState>,
|
||||
compaction_metadata: CompactionTurnMetadata,
|
||||
analytics_details: &mut CompactionAnalyticsDetails,
|
||||
) -> CodexResult<()> {
|
||||
@@ -253,15 +247,12 @@ async fn run_remote_compact_task_inner_impl(
|
||||
let (attempt, compaction_turn_context) = match attempt {
|
||||
Ok(attempt) => (attempt, turn_context),
|
||||
Err(error) => {
|
||||
let Some(fallback_step_context) = fallback_step_context else {
|
||||
return Err(error);
|
||||
};
|
||||
if !should_retry_with_current_model(&error) {
|
||||
let fallback_turn_context = &replacement_step_context.turn;
|
||||
if !should_retry_with_current_model(&error, turn_context, fallback_turn_context) {
|
||||
return Err(error);
|
||||
}
|
||||
sess.set_last_known_step_context(fallback_step_context)
|
||||
sess.set_last_known_step_context(replacement_step_context)
|
||||
.await;
|
||||
let fallback_turn_context = &fallback_step_context.turn;
|
||||
let fallback_compaction_trace =
|
||||
sess.services.rollout_thread_trace.compaction_trace_context(
|
||||
fallback_turn_context.sub_id.as_str(),
|
||||
@@ -271,7 +262,7 @@ async fn run_remote_compact_task_inner_impl(
|
||||
);
|
||||
let fallback_result = run_remote_compact_v2_attempt(
|
||||
sess,
|
||||
fallback_step_context,
|
||||
replacement_step_context,
|
||||
client_session,
|
||||
&fallback_compaction_trace,
|
||||
compaction_metadata,
|
||||
@@ -323,16 +314,11 @@ async fn run_remote_compact_task_inner_impl(
|
||||
analytics_details.retained_image_count = Some(retained_images);
|
||||
let (new_window_number, new_window_ids) = sess.advance_auto_compact_window().await;
|
||||
let (initial_context, world_state_baseline) =
|
||||
build_compaction_initial_context(sess.as_ref(), &initial_context_injection).await;
|
||||
build_compaction_initial_context(sess.as_ref(), replacement_step_context, &world_state)
|
||||
.await;
|
||||
let new_history =
|
||||
insert_initial_context_before_last_real_user_or_summary(compacted_history, initial_context);
|
||||
|
||||
let reference_context_item = match initial_context_injection {
|
||||
InitialContextInjection::DoNotInject => None,
|
||||
InitialContextInjection::BeforeLastUserMessage { step_context, .. } => {
|
||||
Some(step_context.to_turn_context_item())
|
||||
}
|
||||
};
|
||||
if let Some(trace_input_history) = trace_input_history.as_deref() {
|
||||
let replacement_history = new_history
|
||||
.iter()
|
||||
@@ -359,7 +345,7 @@ async fn run_remote_compact_task_inner_impl(
|
||||
};
|
||||
sess.replace_compacted_history(
|
||||
new_history,
|
||||
reference_context_item,
|
||||
replacement_step_context.to_turn_context_item(),
|
||||
world_state_baseline,
|
||||
CompactedHistoryMetadata {
|
||||
input_goal_ids,
|
||||
@@ -372,7 +358,8 @@ async fn run_remote_compact_task_inner_impl(
|
||||
},
|
||||
)
|
||||
.await;
|
||||
sess.recompute_token_usage(compaction_turn_context).await;
|
||||
sess.recompute_token_usage(&replacement_step_context.turn)
|
||||
.await;
|
||||
|
||||
sess.emit_turn_item_completed(compaction_turn_context, compaction_item)
|
||||
.await;
|
||||
|
||||
@@ -157,10 +157,15 @@ async fn local_compaction_respects_tool_metadata_state(
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
let step = session
|
||||
.capture_step_context(turn, &tokio_util::sync::CancellationToken::new())
|
||||
.await?;
|
||||
let world_state = Arc::new(session.build_world_state_for_step(&step).await?);
|
||||
// OpenAI identity keeps the client from removing passthrough for compatibility.
|
||||
run_compact_task(
|
||||
Arc::clone(&session),
|
||||
turn,
|
||||
step,
|
||||
world_state,
|
||||
vec![UserInput::Text {
|
||||
text: "Summarize the conversation.".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::compact::InitialContextInjection;
|
||||
use crate::context::world_state::WorldState;
|
||||
use crate::hook_runtime::PostCompactHookOutcome;
|
||||
use crate::hook_runtime::PreCompactHookOutcome;
|
||||
@@ -8,13 +7,11 @@ use crate::hook_runtime::run_post_compact_hooks;
|
||||
use crate::hook_runtime::run_pre_compact_hooks;
|
||||
use crate::session::session::Session;
|
||||
use crate::session::step_context::StepContext;
|
||||
use crate::session::turn_context::TurnContext;
|
||||
use codex_analytics::CompactionTrigger;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
use codex_protocol::items::ContextCompactionItem;
|
||||
use codex_protocol::items::TurnItem;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
/// Runs token-budget manual compaction as a normal compaction lifecycle.
|
||||
///
|
||||
@@ -23,15 +20,9 @@ use tokio_util::sync::CancellationToken;
|
||||
/// observe the same lifecycle as local or remote compaction.
|
||||
pub(crate) async fn run_manual_compact_task(
|
||||
sess: Arc<Session>,
|
||||
turn_context: Arc<TurnContext>,
|
||||
step_context: Arc<StepContext>,
|
||||
world_state: Arc<WorldState>,
|
||||
) -> CodexResult<()> {
|
||||
sess.emit_turn_started(&turn_context).await;
|
||||
|
||||
// Manual compaction runs outside run_turn, so it captures its own current step.
|
||||
let step_context = sess
|
||||
.capture_step_context(Arc::clone(&turn_context), &CancellationToken::new())
|
||||
.await?;
|
||||
let world_state = Arc::new(sess.build_world_state_for_step(&step_context).await?);
|
||||
run_compact_task_inner(&sess, &step_context, world_state, CompactionTrigger::Manual).await
|
||||
}
|
||||
|
||||
@@ -43,14 +34,8 @@ pub(crate) async fn run_manual_compact_task(
|
||||
pub(crate) async fn run_inline_auto_compact_task(
|
||||
sess: Arc<Session>,
|
||||
step_context: Arc<StepContext>,
|
||||
initial_context_injection: InitialContextInjection,
|
||||
world_state: Arc<WorldState>,
|
||||
) -> CodexResult<()> {
|
||||
let world_state = match initial_context_injection {
|
||||
InitialContextInjection::BeforeLastUserMessage { world_state, .. } => world_state,
|
||||
InitialContextInjection::DoNotInject => {
|
||||
Arc::new(sess.build_world_state_for_step(&step_context).await?)
|
||||
}
|
||||
};
|
||||
run_compact_task_inner(&sess, &step_context, world_state, CompactionTrigger::Auto).await
|
||||
}
|
||||
|
||||
|
||||
@@ -82,8 +82,8 @@ async fn run_review_preserves_evidence_during_parent_compaction() {
|
||||
parent
|
||||
.replace_compacted_history(
|
||||
vec![checkpoint.into()],
|
||||
/*reference_context_item*/ None,
|
||||
/*world_state_baseline*/ None,
|
||||
turn.to_turn_context_item(),
|
||||
crate::context::world_state::WorldStateSnapshot::default(),
|
||||
crate::compact::CompactedHistoryMetadata {
|
||||
input_goal_ids: Default::default(),
|
||||
message: String::new(),
|
||||
|
||||
@@ -2654,8 +2654,8 @@ async fn guardian_reuses_prompt_cache_key_and_appends_prior_reviews() -> anyhow:
|
||||
.into_iter()
|
||||
.map(codex_history::ResponseItemEnvelope::new)
|
||||
.collect(),
|
||||
/*reference_context_item*/ None,
|
||||
/*world_state_baseline*/ None,
|
||||
turn.to_turn_context_item(),
|
||||
crate::context::world_state::WorldStateSnapshot::default(),
|
||||
crate::compact::CompactedHistoryMetadata {
|
||||
input_goal_ids: Default::default(),
|
||||
message: String::new(),
|
||||
|
||||
@@ -3993,8 +3993,8 @@ impl Session {
|
||||
pub(crate) async fn replace_compacted_history(
|
||||
&self,
|
||||
mut items: Vec<ResponseItemEnvelope>,
|
||||
reference_context_item: Option<TurnContextItem>,
|
||||
world_state_baseline: Option<WorldStateSnapshot>,
|
||||
reference_context_item: TurnContextItem,
|
||||
world_state_baseline: WorldStateSnapshot,
|
||||
metadata: CompactedHistoryMetadata,
|
||||
) {
|
||||
for envelope in &mut items {
|
||||
@@ -4024,20 +4024,9 @@ impl Session {
|
||||
// Wait for accepted updates to finish persisting, then keep later updates from
|
||||
// overtaking the current settings snapshot while its checkpoint is written.
|
||||
let _settings_guard = thread_settings::acquire_persistence_lock(self).await;
|
||||
// A new history window needs a full checkpoint, even when it contains only
|
||||
// extension metadata and model-visible context will be rebuilt on the next turn.
|
||||
let mut world_state_item = None;
|
||||
let world_state_item = WorldStateItem::full(world_state_baseline.clone().into_object());
|
||||
let compacted_item = {
|
||||
let mut state = self.state.lock().await;
|
||||
let snapshot = world_state_baseline.or_else(|| {
|
||||
let previous = state.history.world_state_checkpoint()?;
|
||||
let mut retained = serde_json::Map::new();
|
||||
for contributor in self.services.extensions.context_contributors() {
|
||||
retained
|
||||
.extend(contributor.retain_world_state_after_compaction(&previous.state));
|
||||
}
|
||||
(!retained.is_empty()).then(|| WorldStateSnapshot::from(&retained))
|
||||
});
|
||||
// Goal edits are published outside the running task. Keep edits accepted after
|
||||
// the compaction input snapshot, in their original order, after its replacement.
|
||||
let replacement_goal_ids = crate::context::UserGoalUpdate::message_ids(
|
||||
@@ -4060,16 +4049,13 @@ impl Session {
|
||||
let replacement_history = items.clone();
|
||||
state.replace_annotated_history(
|
||||
items,
|
||||
reference_context_item.clone(),
|
||||
Some(reference_context_item.clone()),
|
||||
HistoryReplacement::Compaction {
|
||||
reviewer_compaction_hash: metadata.reviewer_compaction_hash,
|
||||
},
|
||||
);
|
||||
state.reasoning_effort_pin = ReasoningEffortPin::Compacted;
|
||||
if let Some(snapshot) = snapshot {
|
||||
world_state_item = Some(WorldStateItem::full(snapshot.clone().into_object()));
|
||||
state.history.set_world_state_baseline(snapshot);
|
||||
}
|
||||
state.history.set_world_state_baseline(world_state_baseline);
|
||||
CompactedItem {
|
||||
message: metadata.message,
|
||||
replacement_history: Some(replacement_history),
|
||||
@@ -4093,18 +4079,13 @@ impl Session {
|
||||
}
|
||||
};
|
||||
|
||||
let mut rollout_items = vec![RolloutItem::Compacted(compacted_item)];
|
||||
// Persist the baseline after the replacement history that established it.
|
||||
if let Some(world_state_item) = world_state_item {
|
||||
rollout_items.push(RolloutItem::WorldState(world_state_item));
|
||||
}
|
||||
if let Some(turn_context_item) = reference_context_item {
|
||||
rollout_items.push(RolloutItem::TurnContext(turn_context_item));
|
||||
}
|
||||
// The frozen turn context must not override current settings in persisted metadata.
|
||||
rollout_items.push(RolloutItem::EventMsg(
|
||||
thread_settings::applied_event(self).await,
|
||||
));
|
||||
let rollout_items = [
|
||||
RolloutItem::Compacted(compacted_item),
|
||||
RolloutItem::WorldState(world_state_item),
|
||||
RolloutItem::TurnContext(reference_context_item),
|
||||
// The frozen turn context must not override current settings in persisted metadata.
|
||||
RolloutItem::EventMsg(thread_settings::applied_event(self).await),
|
||||
];
|
||||
if self.persist_rollout_items(&rollout_items).await
|
||||
&& let Some(revision) = mcp_revision
|
||||
{
|
||||
@@ -4573,8 +4554,8 @@ impl Session {
|
||||
let turn_context_item = step_context.to_turn_context_item();
|
||||
self.replace_compacted_history(
|
||||
context_items,
|
||||
Some(turn_context_item),
|
||||
Some(world_state_snapshot),
|
||||
turn_context_item,
|
||||
world_state_snapshot,
|
||||
CompactedHistoryMetadata {
|
||||
input_goal_ids,
|
||||
message: String::new(),
|
||||
@@ -4605,8 +4586,8 @@ impl Session {
|
||||
/// `<model_switch>` developer message so model-specific instructions are not lost.
|
||||
///
|
||||
/// This is the normal runtime path that establishes a new `reference_context_item`.
|
||||
/// Mid-turn compaction is the other path that can re-establish that reference when it
|
||||
/// reinjects full initial context into replacement history. Live world-state changes may
|
||||
/// Compaction also establishes that reference when it installs full initial context
|
||||
/// into replacement history. Live world-state changes may
|
||||
/// independently advance their in-memory baseline within a turn.
|
||||
#[instrument(level = "trace", skip_all)]
|
||||
pub(crate) async fn record_context_updates_and_set_reference_context_item(
|
||||
|
||||
@@ -15,7 +15,6 @@ use super::*;
|
||||
mod config_refresh_tests;
|
||||
use crate::agents_md_manager::AgentsMdManager;
|
||||
use crate::agents_md_manager::SessionInstructions;
|
||||
use crate::compact::InitialContextInjection;
|
||||
use crate::config::ConfigBuilder;
|
||||
use crate::config::ConfigOverrides;
|
||||
use crate::config::RuntimeConfigRefresh;
|
||||
@@ -5854,7 +5853,7 @@ async fn session_configuration_apply_preserves_absolute_cwd_write_root_on_cwd_up
|
||||
|
||||
#[tokio::test]
|
||||
async fn settings_checkpoint_waits_for_accepted_settings_persistence() {
|
||||
let (mut session, _turn_context, rx) = make_session_and_context_with_auth_and_config_and_rx(
|
||||
let (mut session, turn_context, rx) = make_session_and_context_with_auth_and_config_and_rx(
|
||||
CodexAuth::from_api_key("Test API Key"),
|
||||
Vec::new(),
|
||||
|config| {
|
||||
@@ -5892,8 +5891,8 @@ async fn settings_checkpoint_waits_for_accepted_settings_persistence() {
|
||||
let mut checkpoint = Box::pin(tokio::task::unconstrained(
|
||||
session.replace_compacted_history(
|
||||
vec![ResponseItemEnvelope::new(user_message("compacted history"))],
|
||||
/*reference_context_item*/ None,
|
||||
/*world_state_baseline*/ None,
|
||||
turn_context.to_turn_context_item(),
|
||||
WorldStateSnapshot::default(),
|
||||
CompactedHistoryMetadata {
|
||||
input_goal_ids: Default::default(),
|
||||
message: "summary".to_string(),
|
||||
@@ -6026,8 +6025,8 @@ async fn mcp_attribution_checkpoints_cover_batch_prefixes_compaction_and_restore
|
||||
session
|
||||
.replace_compacted_history(
|
||||
vec![ResponseItemEnvelope::new(user_message("compacted history"))],
|
||||
/*reference_context_item*/ None,
|
||||
/*world_state_baseline*/ None,
|
||||
turn_context.to_turn_context_item(),
|
||||
WorldStateSnapshot::default(),
|
||||
CompactedHistoryMetadata {
|
||||
input_goal_ids: Default::default(),
|
||||
message: "summary".to_string(),
|
||||
@@ -6154,48 +6153,39 @@ async fn compaction_persists_resume_metadata_and_companion_records() {
|
||||
thread_settings: session.thread_settings_snapshot().await,
|
||||
};
|
||||
|
||||
let mut first_live_history = None;
|
||||
for with_baselines in [true, false] {
|
||||
let input_goal_ids =
|
||||
crate::context::UserGoalUpdate::message_ids(session.clone_history().await.raw_items());
|
||||
// The goal edit is accepted after the compaction input was captured.
|
||||
let accepted_goal = if with_baselines {
|
||||
session
|
||||
.record_user_goal_update(crate::context::UserGoalUpdate::Clear)
|
||||
.await
|
||||
.expect("record concurrent clear");
|
||||
session
|
||||
.clone_history()
|
||||
.await
|
||||
.annotated_items()
|
||||
.last()
|
||||
.cloned()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let (window_number, window_ids) = session.advance_auto_compact_window().await;
|
||||
session
|
||||
.replace_compacted_history(
|
||||
vec![ResponseItemEnvelope::new(user_message("compacted context"))],
|
||||
with_baselines.then_some(turn_context_baseline.clone()),
|
||||
with_baselines.then(|| world_state.render_full().0),
|
||||
CompactedHistoryMetadata {
|
||||
input_goal_ids,
|
||||
message: String::new(),
|
||||
window_number,
|
||||
window_ids,
|
||||
compaction_response_id: None,
|
||||
compaction_model_hash: None,
|
||||
reviewer_compaction_hash: None,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
if let Some(accepted_goal) = accepted_goal {
|
||||
let live_history = session.clone_history().await.annotated_items().to_vec();
|
||||
assert_eq!(&live_history[1..], &[accepted_goal]);
|
||||
first_live_history = Some(live_history);
|
||||
}
|
||||
}
|
||||
let input_goal_ids =
|
||||
crate::context::UserGoalUpdate::message_ids(session.clone_history().await.raw_items());
|
||||
// The goal edit is accepted after the compaction input was captured.
|
||||
session
|
||||
.record_user_goal_update(crate::context::UserGoalUpdate::Clear)
|
||||
.await
|
||||
.expect("record concurrent clear");
|
||||
let accepted_goal = session
|
||||
.clone_history()
|
||||
.await
|
||||
.annotated_items()
|
||||
.last()
|
||||
.cloned()
|
||||
.expect("accepted goal");
|
||||
let (window_number, window_ids) = session.advance_auto_compact_window().await;
|
||||
session
|
||||
.replace_compacted_history(
|
||||
vec![ResponseItemEnvelope::new(user_message("compacted context"))],
|
||||
turn_context_baseline.clone(),
|
||||
world_state.render_full().0,
|
||||
CompactedHistoryMetadata {
|
||||
input_goal_ids,
|
||||
message: String::new(),
|
||||
window_number,
|
||||
window_ids,
|
||||
compaction_response_id: None,
|
||||
compaction_model_hash: None,
|
||||
reviewer_compaction_hash: None,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
let live_history = session.clone_history().await.annotated_items().to_vec();
|
||||
assert_eq!(&live_history[1..], &[accepted_goal]);
|
||||
|
||||
session.flush_rollout().await.expect("flush checkpoints");
|
||||
let (items, _, _) = RolloutRecorder::load_rollout_items(&rollout_path)
|
||||
@@ -6206,26 +6196,22 @@ async fn compaction_persists_resume_metadata_and_companion_records() {
|
||||
.skip_while(|item| !matches!(item, RolloutItem::Compacted(_)))
|
||||
.collect::<Vec<_>>();
|
||||
let [
|
||||
RolloutItem::Compacted(first),
|
||||
RolloutItem::WorldState(first_world_state),
|
||||
RolloutItem::TurnContext(first_turn_context),
|
||||
RolloutItem::EventMsg(EventMsg::ThreadSettingsApplied(first_settings)),
|
||||
RolloutItem::Compacted(second),
|
||||
RolloutItem::EventMsg(EventMsg::ThreadSettingsApplied(second_settings)),
|
||||
RolloutItem::Compacted(compacted),
|
||||
RolloutItem::WorldState(saved_world_state),
|
||||
RolloutItem::TurnContext(saved_turn_context),
|
||||
RolloutItem::EventMsg(EventMsg::ThreadSettingsApplied(saved_settings)),
|
||||
] = compaction_items.as_slice()
|
||||
else {
|
||||
panic!("unexpected compaction records: {compaction_items:#?}");
|
||||
};
|
||||
assert_eq!(first.replacement_history, first_live_history);
|
||||
assert_eq!(first.resume_metadata.as_ref(), Some(&expected));
|
||||
assert_eq!(second.resume_metadata.as_ref(), Some(&expected));
|
||||
assert_eq!(compacted.replacement_history, Some(live_history));
|
||||
assert_eq!(compacted.resume_metadata.as_ref(), Some(&expected));
|
||||
assert_eq!(
|
||||
first_world_state,
|
||||
saved_world_state,
|
||||
&WorldStateItem::full(world_state.render_full().0.into_object())
|
||||
);
|
||||
assert_eq!(first_turn_context, &turn_context_baseline);
|
||||
assert_eq!(first_settings, &expected_settings);
|
||||
assert_eq!(second_settings, &expected_settings);
|
||||
assert_eq!(saved_turn_context, &turn_context_baseline);
|
||||
assert_eq!(saved_settings, &expected_settings);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -11312,12 +11298,8 @@ async fn build_initial_context_uses_retained_step_after_model_change() {
|
||||
.await
|
||||
.unwrap();
|
||||
let world_a = Arc::new(session.build_world_state_for_step(&step_a).await.unwrap());
|
||||
let retained = crate::compact::InitialContextInjection::BeforeLastUserMessage {
|
||||
world_state: Arc::clone(&world_a),
|
||||
step_context: Arc::clone(&step_a),
|
||||
};
|
||||
let (initial_a, _) =
|
||||
crate::compact::build_compaction_initial_context(&session, &retained).await;
|
||||
crate::compact::build_compaction_initial_context(&session, &step_a, &world_a).await;
|
||||
|
||||
let mut selected_b = step_a.settings.selected().clone();
|
||||
selected_b.collaboration_mode.settings.model = "model-b".to_string();
|
||||
@@ -11348,10 +11330,10 @@ async fn build_initial_context_uses_retained_step_after_model_change() {
|
||||
.0;
|
||||
let turn_contributions_b = session.build_turn_context_contribution_items(&step_b).await;
|
||||
let (restored_a, restored_snapshot) =
|
||||
crate::compact::build_compaction_initial_context(&session, &retained).await;
|
||||
crate::compact::build_compaction_initial_context(&session, &step_a, &world_a).await;
|
||||
|
||||
assert_eq!(restored_a, initial_a);
|
||||
assert_eq!(restored_snapshot, Some(world_a.render_full().0));
|
||||
assert_eq!(restored_snapshot, world_a.render_full().0);
|
||||
let initial_a = initial_a
|
||||
.into_iter()
|
||||
.map(ResponseItemEnvelope::into_item)
|
||||
@@ -11943,9 +11925,9 @@ async fn remote_compaction_v2_retains_only_the_selected_step(first_attempt: Firs
|
||||
crate::compact_remote_v2::run_inline_remote_auto_compact_task(
|
||||
Arc::clone(&session),
|
||||
Arc::clone(&primary),
|
||||
Some(Arc::clone(&fallback)),
|
||||
Arc::clone(&fallback),
|
||||
&mut client_session,
|
||||
InitialContextInjection::DoNotInject,
|
||||
Arc::new(session.build_world_state_for_step(&fallback).await.unwrap()),
|
||||
CompactionReason::ModelDownshift,
|
||||
CompactionPhase::PreTurn,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use super::*;
|
||||
use crate::compact::InitialContextInjection;
|
||||
use crate::config::Constrained;
|
||||
use crate::exec_policy::ExecPolicyManager;
|
||||
use crate::guardian::GUARDIAN_REVIEWER_NAME;
|
||||
@@ -1118,13 +1117,8 @@ async fn compaction_initial_context_preserves_separate_guardian_developer_messag
|
||||
.await
|
||||
.expect("world state should build"),
|
||||
);
|
||||
let initial_context_injection = InitialContextInjection::BeforeLastUserMessage {
|
||||
world_state,
|
||||
step_context,
|
||||
};
|
||||
|
||||
let (refreshed, _) =
|
||||
crate::compact::build_compaction_initial_context(&session, &initial_context_injection)
|
||||
crate::compact::build_compaction_initial_context(&session, &step_context, &world_state)
|
||||
.await;
|
||||
|
||||
let developer_messages = refreshed
|
||||
|
||||
@@ -6,12 +6,12 @@ use std::sync::atomic::Ordering;
|
||||
use crate::client::ModelClientSession;
|
||||
use crate::client_common::Prompt;
|
||||
use crate::client_common::ResponseEvent;
|
||||
use crate::compact::InitialContextInjection;
|
||||
use crate::compact::run_inline_auto_compact_task;
|
||||
use crate::compact_remote_v2::run_inline_remote_auto_compact_task as run_inline_remote_auto_compact_task_v2;
|
||||
use crate::connectors;
|
||||
use crate::context::ContextualUserFragment;
|
||||
use crate::context::UserVerificationNotice;
|
||||
use crate::context::world_state::WorldState;
|
||||
use crate::cyber_access_program;
|
||||
use crate::environment_selection::TurnEnvironmentSnapshot;
|
||||
use crate::feedback_tags;
|
||||
@@ -346,9 +346,9 @@ pub(crate) async fn run_turn(
|
||||
run_auto_compact(
|
||||
&sess,
|
||||
Arc::clone(&first_step_context),
|
||||
/*fallback_step_context*/ None,
|
||||
Arc::clone(&first_step_context),
|
||||
&mut client_session,
|
||||
InitialContextInjection::DoNotInject,
|
||||
Arc::clone(&world_state),
|
||||
CompactionReason::ContextLimit,
|
||||
CompactionPhase::PreTurn,
|
||||
)
|
||||
@@ -614,12 +614,9 @@ pub(crate) async fn run_turn(
|
||||
if let Err(err) = run_auto_compact(
|
||||
&sess,
|
||||
Arc::clone(&step_context),
|
||||
/*fallback_step_context*/ None,
|
||||
Arc::clone(&step_context),
|
||||
&mut client_session,
|
||||
InitialContextInjection::BeforeLastUserMessage {
|
||||
world_state: Arc::clone(&world_state),
|
||||
step_context: Arc::clone(&step_context),
|
||||
},
|
||||
Arc::clone(&world_state),
|
||||
CompactionReason::ContextLimit,
|
||||
CompactionPhase::MidTurn,
|
||||
)
|
||||
@@ -726,9 +723,9 @@ pub(crate) async fn run_turn(
|
||||
&& let Err(err) = run_auto_compact(
|
||||
&sess,
|
||||
Arc::clone(&step_context),
|
||||
/*fallback_step_context*/ None,
|
||||
Arc::clone(&step_context),
|
||||
&mut client_session,
|
||||
InitialContextInjection::DoNotInject,
|
||||
Arc::clone(&world_state),
|
||||
CompactionReason::ContextLimit,
|
||||
CompactionPhase::PostTurn,
|
||||
)
|
||||
@@ -777,12 +774,9 @@ pub(crate) async fn run_turn(
|
||||
run_auto_compact(
|
||||
&sess,
|
||||
Arc::clone(&step_context),
|
||||
/*fallback_step_context*/ None,
|
||||
Arc::clone(&step_context),
|
||||
&mut client_session,
|
||||
InitialContextInjection::BeforeLastUserMessage {
|
||||
world_state: Arc::clone(&world_state),
|
||||
step_context: Arc::clone(&step_context),
|
||||
},
|
||||
Arc::clone(&world_state),
|
||||
CompactionReason::ContextLimit,
|
||||
CompactionPhase::MidTurn,
|
||||
)
|
||||
@@ -1318,12 +1312,13 @@ async fn run_pre_sampling_compact(
|
||||
let step_context = sess
|
||||
.capture_step_context(Arc::clone(turn_context), cancellation_token)
|
||||
.await?;
|
||||
let world_state = Arc::new(sess.build_world_state_for_step(&step_context).await?);
|
||||
run_auto_compact(
|
||||
sess,
|
||||
Arc::clone(&step_context),
|
||||
step_context,
|
||||
/*fallback_step_context*/ None,
|
||||
client_session,
|
||||
InitialContextInjection::DoNotInject,
|
||||
world_state,
|
||||
CompactionReason::ContextLimit,
|
||||
CompactionPhase::PreTurn,
|
||||
)
|
||||
@@ -1340,33 +1335,6 @@ fn comp_hash_changed(previous: Option<&str>, current: Option<&str>) -> bool {
|
||||
.is_some_and(|(previous, current)| previous != current)
|
||||
}
|
||||
|
||||
/// Captures the current model's request-scoped state for retrying previous-model compaction.
|
||||
///
|
||||
/// Returns `None` when auth uses neither the Codex backend nor an API key, the provider is
|
||||
/// not OpenAI, or the previous and current model/program pairs are the same.
|
||||
async fn capture_current_model_fallback_step_context(
|
||||
sess: &Arc<Session>,
|
||||
turn_context: &Arc<TurnContext>,
|
||||
previous_turn_context: &TurnContext,
|
||||
cancellation_token: &CancellationToken,
|
||||
) -> CodexResult<Option<Arc<StepContext>>> {
|
||||
let supports_fallback = turn_context
|
||||
.auth_manager
|
||||
.as_deref()
|
||||
.and_then(codex_login::AuthManager::auth_cached)
|
||||
.is_some_and(|auth| auth.uses_codex_backend() || auth.is_api_key_auth());
|
||||
if !supports_fallback
|
||||
|| !turn_context.provider.info().is_openai()
|
||||
|| (previous_turn_context.model_info().slug == turn_context.model_info().slug
|
||||
&& previous_turn_context.cyber_access_program == turn_context.cyber_access_program)
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
sess.capture_speculative_step_context(Arc::clone(turn_context), cancellation_token)
|
||||
.await
|
||||
.map(Some)
|
||||
}
|
||||
|
||||
/// Runs pre-sampling compaction against the previous model when its compaction compatibility
|
||||
/// hash changed or when switching to a smaller context-window model.
|
||||
///
|
||||
@@ -1401,79 +1369,65 @@ async fn maybe_run_previous_model_inline_compact(
|
||||
);
|
||||
let previous_model_turn_context = Arc::new(previous_model_turn_context);
|
||||
|
||||
if should_compact_for_comp_hash_change {
|
||||
let step_context = sess
|
||||
.capture_step_context(Arc::clone(&previous_model_turn_context), cancellation_token)
|
||||
.await?;
|
||||
let fallback_step_context = capture_current_model_fallback_step_context(
|
||||
sess,
|
||||
turn_context,
|
||||
&previous_model_turn_context,
|
||||
cancellation_token,
|
||||
)
|
||||
.await?;
|
||||
run_auto_compact(
|
||||
sess,
|
||||
step_context,
|
||||
fallback_step_context,
|
||||
client_session,
|
||||
InitialContextInjection::DoNotInject,
|
||||
CompactionReason::CompHashChanged,
|
||||
CompactionPhase::PreTurn,
|
||||
)
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let Some(old_context_window) = previous_model_turn_context.model_context_window() else {
|
||||
return Ok(());
|
||||
};
|
||||
let Some(new_context_window) = turn_context.model_context_window() else {
|
||||
return Ok(());
|
||||
};
|
||||
let active_context_tokens = sess.get_total_token_usage().await;
|
||||
let previous_model_limit_reached = match turn_context
|
||||
.config
|
||||
.model_auto_compact_token_limit_scope
|
||||
{
|
||||
AutoCompactTokenLimitScope::Total => {
|
||||
let new_auto_compact_limit = turn_context
|
||||
.model_info()
|
||||
.auto_compact_token_limit()
|
||||
.unwrap_or(i64::MAX);
|
||||
active_context_tokens > new_auto_compact_limit
|
||||
|| active_context_tokens >= new_context_window
|
||||
let reason = if should_compact_for_comp_hash_change {
|
||||
CompactionReason::CompHashChanged
|
||||
} else {
|
||||
let Some(old_context_window) = previous_model_turn_context.model_context_window() else {
|
||||
return Ok(());
|
||||
};
|
||||
let Some(new_context_window) = turn_context.model_context_window() else {
|
||||
return Ok(());
|
||||
};
|
||||
let active_context_tokens = sess.get_total_token_usage().await;
|
||||
let previous_model_limit_reached =
|
||||
match turn_context.config.model_auto_compact_token_limit_scope {
|
||||
AutoCompactTokenLimitScope::Total => {
|
||||
let new_auto_compact_limit = turn_context
|
||||
.model_info()
|
||||
.auto_compact_token_limit()
|
||||
.unwrap_or(i64::MAX);
|
||||
active_context_tokens > new_auto_compact_limit
|
||||
|| active_context_tokens >= new_context_window
|
||||
}
|
||||
AutoCompactTokenLimitScope::BodyAfterPrefix => {
|
||||
active_context_tokens >= new_context_window
|
||||
}
|
||||
};
|
||||
if !previous_model_limit_reached
|
||||
|| previous_model_turn_context.model_info().slug == turn_context.model_info().slug
|
||||
|| old_context_window <= new_context_window
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
AutoCompactTokenLimitScope::BodyAfterPrefix => active_context_tokens >= new_context_window,
|
||||
CompactionReason::ModelDownshift
|
||||
};
|
||||
let should_run = previous_model_limit_reached
|
||||
&& previous_model_turn_context.model_info().slug != turn_context.model_info().slug
|
||||
&& old_context_window > new_context_window;
|
||||
if should_run {
|
||||
let step_context = sess
|
||||
.capture_step_context(Arc::clone(&previous_model_turn_context), cancellation_token)
|
||||
.await?;
|
||||
let fallback_step_context = capture_current_model_fallback_step_context(
|
||||
sess,
|
||||
turn_context,
|
||||
&previous_model_turn_context,
|
||||
cancellation_token,
|
||||
)
|
||||
|
||||
let step_context = sess
|
||||
.capture_step_context(previous_model_turn_context, cancellation_token)
|
||||
.await?;
|
||||
run_auto_compact(
|
||||
sess,
|
||||
step_context,
|
||||
fallback_step_context,
|
||||
client_session,
|
||||
InitialContextInjection::DoNotInject,
|
||||
CompactionReason::ModelDownshift,
|
||||
CompactionPhase::PreTurn,
|
||||
)
|
||||
// The previous model summarizes the old window; the current model owns its replacement.
|
||||
// Reuse the replacement step if the compaction request needs to fall back to that model.
|
||||
let replacement_step_context = sess
|
||||
.capture_speculative_step_context(Arc::clone(turn_context), cancellation_token)
|
||||
.await?;
|
||||
}
|
||||
let world_state = Arc::new(
|
||||
sess.build_world_state_for_step(&replacement_step_context)
|
||||
.await?,
|
||||
);
|
||||
run_auto_compact(
|
||||
sess,
|
||||
step_context,
|
||||
replacement_step_context,
|
||||
client_session,
|
||||
world_state,
|
||||
reason,
|
||||
CompactionPhase::PreTurn,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `world_state` belongs to `replacement_step_context`, which may differ from the compactor.
|
||||
#[instrument(
|
||||
level = "trace",
|
||||
skip_all,
|
||||
@@ -1482,9 +1436,9 @@ async fn maybe_run_previous_model_inline_compact(
|
||||
async fn run_auto_compact(
|
||||
sess: &Arc<Session>,
|
||||
step_context: Arc<StepContext>,
|
||||
fallback_step_context: Option<Arc<StepContext>>,
|
||||
replacement_step_context: Arc<StepContext>,
|
||||
client_session: &mut ModelClientSession,
|
||||
initial_context_injection: InitialContextInjection,
|
||||
world_state: Arc<WorldState>,
|
||||
reason: CompactionReason,
|
||||
phase: CompactionPhase,
|
||||
) -> CodexResult<()> {
|
||||
@@ -1501,8 +1455,8 @@ async fn run_auto_compact(
|
||||
// instead of consuming a pending `new_context` tool request.
|
||||
crate::compact_token_budget::run_inline_auto_compact_task(
|
||||
Arc::clone(sess),
|
||||
step_context,
|
||||
initial_context_injection,
|
||||
replacement_step_context,
|
||||
world_state,
|
||||
)
|
||||
.await?;
|
||||
return Ok(());
|
||||
@@ -1518,9 +1472,9 @@ async fn run_auto_compact(
|
||||
run_inline_remote_auto_compact_task_v2(
|
||||
Arc::clone(sess),
|
||||
step_context,
|
||||
fallback_step_context,
|
||||
replacement_step_context,
|
||||
client_session,
|
||||
initial_context_injection,
|
||||
world_state,
|
||||
reason,
|
||||
phase,
|
||||
)
|
||||
@@ -1535,7 +1489,8 @@ async fn run_auto_compact(
|
||||
run_inline_auto_compact_task(
|
||||
Arc::clone(sess),
|
||||
Arc::clone(turn_context),
|
||||
initial_context_injection,
|
||||
replacement_step_context,
|
||||
world_state,
|
||||
reason,
|
||||
phase,
|
||||
)
|
||||
|
||||
@@ -31,7 +31,7 @@ impl SessionTask for CompactTask {
|
||||
session: Arc<Session>,
|
||||
ctx: Arc<TurnContext>,
|
||||
_input: Vec<TurnInput>,
|
||||
_cancellation_token: CancellationToken,
|
||||
cancellation_token: CancellationToken,
|
||||
) -> SessionTaskResult {
|
||||
let _profile_guard = ctx.turn_timing_state.begin_compaction();
|
||||
let _compaction_span = tracing::trace_span!(
|
||||
@@ -40,8 +40,19 @@ impl SessionTask for CompactTask {
|
||||
conversation.id = %session.thread_id,
|
||||
turn.id = %ctx.sub_id,
|
||||
);
|
||||
// Preparation errors must reach the task runner, which reports them to the client.
|
||||
session.emit_turn_started(&ctx).await;
|
||||
let step_context = session
|
||||
.capture_step_context(Arc::clone(&ctx), &cancellation_token)
|
||||
.await?;
|
||||
let world_state = Arc::new(session.build_world_state_for_step(&step_context).await?);
|
||||
if ctx.config.features.enabled(Feature::TokenBudget) {
|
||||
crate::compact_token_budget::run_manual_compact_task(session, ctx).await?;
|
||||
crate::compact_token_budget::run_manual_compact_task(
|
||||
session,
|
||||
step_context,
|
||||
world_state,
|
||||
)
|
||||
.await?;
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
@@ -52,8 +63,12 @@ impl SessionTask for CompactTask {
|
||||
"remote_v2",
|
||||
/*manual*/ true,
|
||||
);
|
||||
crate::compact_remote_v2::run_remote_compact_task(session.clone(), Arc::clone(&ctx))
|
||||
.await
|
||||
crate::compact_remote_v2::run_remote_compact_task(
|
||||
session.clone(),
|
||||
step_context,
|
||||
world_state,
|
||||
)
|
||||
.await
|
||||
}
|
||||
RemoteCompactionSupport::Unsupported => {
|
||||
emit_compact_metric(
|
||||
@@ -71,7 +86,8 @@ impl SessionTask for CompactTask {
|
||||
// Compaction prompt is synthesized; no UI element ranges to preserve.
|
||||
text_elements: Vec::new(),
|
||||
}];
|
||||
crate::compact::run_compact_task(session.clone(), Arc::clone(&ctx), input).await
|
||||
crate::compact::run_compact_task(session.clone(), step_context, world_state, input)
|
||||
.await
|
||||
}
|
||||
};
|
||||
if let Err(err) = result {
|
||||
|
||||
@@ -735,7 +735,7 @@ async fn summarize_context_three_requests_and_instructions(
|
||||
// Verify rollout contains user-turn TurnContext entries and a Compacted entry.
|
||||
println!("rollout path: {}", rollout_path.display());
|
||||
let text = std::fs::read_to_string(&rollout_path).expect("failed to read rollout file");
|
||||
let mut regular_turn_context_count = 0usize;
|
||||
let mut turn_context_count = 0usize;
|
||||
let mut saw_compacted_summary = false;
|
||||
for line in text.lines() {
|
||||
let trimmed = line.trim();
|
||||
@@ -747,7 +747,7 @@ async fn summarize_context_three_requests_and_instructions(
|
||||
};
|
||||
match entry.item {
|
||||
RolloutItem::TurnContext(_) => {
|
||||
regular_turn_context_count += 1;
|
||||
turn_context_count += 1;
|
||||
}
|
||||
RolloutItem::Compacted(ci) if ci.message == expected_summary_message => {
|
||||
let summary_item = ci
|
||||
@@ -780,8 +780,8 @@ async fn summarize_context_three_requests_and_instructions(
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
regular_turn_context_count, 2,
|
||||
"rollout should contain one TurnContext entry per real user turn"
|
||||
turn_context_count, 3,
|
||||
"rollout should contain two user-turn contexts and the compaction baseline"
|
||||
);
|
||||
assert!(
|
||||
saw_compacted_summary,
|
||||
@@ -4463,15 +4463,10 @@ async fn manual_compact_twice_preserves_latest_user_messages() {
|
||||
final_request_last_user_text, final_user_message,
|
||||
"final turn request should end with the submitted user message"
|
||||
);
|
||||
let history_before_seeded_prefix = final_request_before_last_user
|
||||
.strip_suffix(initial_seeded_user_prefix)
|
||||
.expect("final request should end with the seeded user prefix from the first request");
|
||||
let expected_history = vec![
|
||||
first_user_message.to_string(),
|
||||
second_user_message.to_string(),
|
||||
expected_second_summary,
|
||||
];
|
||||
assert_eq!(history_before_seeded_prefix, expected_history.as_slice());
|
||||
let mut expected_history = vec![first_user_message.to_string()];
|
||||
expected_history.extend_from_slice(initial_seeded_user_prefix);
|
||||
expected_history.extend([second_user_message.to_string(), expected_second_summary]);
|
||||
assert_eq!(final_request_before_last_user, expected_history.as_slice());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
@@ -5825,8 +5820,8 @@ async fn remote_v2_compaction_refreshes_instructions_and_preserves_them_on_cold_
|
||||
let replacement_history = replacement_history_from_rollout(&rollout_path)?;
|
||||
assert_eq!(
|
||||
instruction_fragments_in_items(&replacement_history),
|
||||
Vec::<String>::new(),
|
||||
"remote-v2 replacement history currently omits the global-instruction fragment"
|
||||
vec![new_fragment.clone()],
|
||||
"remote-v2 replacement history includes refreshed global instructions"
|
||||
);
|
||||
assert_eq!(
|
||||
test.codex.instruction_sources().await,
|
||||
|
||||
@@ -17,7 +17,6 @@ enum History {
|
||||
Fork,
|
||||
Rollback,
|
||||
IncompleteRollback,
|
||||
Checkpoint,
|
||||
ApiKeyResume,
|
||||
ApiKeyEnabledResume,
|
||||
ApiKeyCustomProviderResume,
|
||||
@@ -71,7 +70,6 @@ async fn submit_pair(
|
||||
#[test_case(Compaction::RemoteHash, History::Fork, Some(CyberAccessProgram::DaybreakBlue); "fork")]
|
||||
#[test_case(Compaction::RemoteHash, History::Rollback, Some(CyberAccessProgram::DaybreakBlue); "rollback")]
|
||||
#[test_case(Compaction::RemoteHash, History::IncompleteRollback, Some(CyberAccessProgram::DaybreakBlue); "incomplete rollback")]
|
||||
#[test_case(Compaction::RemoteHash, History::Checkpoint, Some(CyberAccessProgram::DaybreakBlue); "checkpoint")]
|
||||
#[test_case(Compaction::RemoteHash, History::Resume, None; "missing program")]
|
||||
#[test_case(Compaction::RemoteHash, History::Resume, Some(CyberAccessProgram::Standard); "standard")]
|
||||
#[test_case(Compaction::LocalHash, History::Live, None; "local missing program")]
|
||||
@@ -165,30 +163,6 @@ async fn model_switch_program_pair(
|
||||
discarded_model
|
||||
);
|
||||
}
|
||||
if matches!(history, History::Checkpoint) {
|
||||
let compact = mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
json!({"type": "response.output_item.done", "item": {
|
||||
"type": "compaction", "encrypted_content": "CHECKPOINT"
|
||||
}}),
|
||||
ev_completed("checkpoint"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
initial.codex.submit(Op::Compact).await?;
|
||||
wait_for_event(&initial.codex, |event| {
|
||||
matches!(event, EventMsg::TurnComplete(_))
|
||||
})
|
||||
.await;
|
||||
assert_eq!(
|
||||
compact
|
||||
.single_request()
|
||||
.inputs_of_type("compaction_trigger")
|
||||
.len(),
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
let resumed;
|
||||
let thread = match history {
|
||||
@@ -210,7 +184,6 @@ async fn model_switch_program_pair(
|
||||
History::Resume
|
||||
| History::Rollback
|
||||
| History::IncompleteRollback
|
||||
| History::Checkpoint
|
||||
| History::ApiKeyResume
|
||||
| History::ApiKeyEnabledResume
|
||||
| History::ApiKeyCustomProviderResume => {
|
||||
@@ -368,6 +341,39 @@ async fn model_switch_program_pair(
|
||||
if !local {
|
||||
assert_eq!(requests[0].inputs_of_type("compaction_trigger").len(), 1);
|
||||
}
|
||||
|
||||
// Even when the previous model summarizes, the replacement belongs to the current model.
|
||||
let rollout = fs::read_to_string(thread.rollout_path().expect("rollout"))?
|
||||
.lines()
|
||||
.map(codex_rollout::parse_rollout_line)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let checkpoint = rollout
|
||||
.iter()
|
||||
.rposition(|line| matches!(&line.item, RolloutItem::Compacted(_)))
|
||||
.expect("model-switch compaction");
|
||||
let replacement = &rollout[checkpoint + 1..];
|
||||
let world_state = replacement
|
||||
.iter()
|
||||
.find_map(|line| match &line.item {
|
||||
RolloutItem::WorldState(state) => Some(state),
|
||||
_ => None,
|
||||
})
|
||||
.expect("replacement world state");
|
||||
let turn_context = replacement
|
||||
.iter()
|
||||
.find_map(|line| match &line.item {
|
||||
RolloutItem::TurnContext(context) => Some(context),
|
||||
_ => None,
|
||||
})
|
||||
.expect("replacement turn context");
|
||||
assert_eq!(
|
||||
(
|
||||
world_state.full,
|
||||
world_state.state.get("model"),
|
||||
turn_context.model.as_str(),
|
||||
),
|
||||
(true, Some(&json!(next_model)), next_model),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -277,18 +277,16 @@ async fn compact_resume_and_fork_preserve_model_history_view() {
|
||||
let summary_after_compact = extract_summary_user_text(&requests[2], SUMMARY_TEXT);
|
||||
let summary_after_resume = extract_summary_user_text(&requests[3], SUMMARY_TEXT);
|
||||
let summary_after_fork = extract_summary_user_text(&requests[4], SUMMARY_TEXT);
|
||||
let mut expected_after_compact_user_texts =
|
||||
vec!["hello world".to_string(), summary_after_compact];
|
||||
expected_after_compact_user_texts.extend_from_slice(seeded_user_prefix);
|
||||
let mut expected_after_compact_user_texts = seeded_user_prefix.to_vec();
|
||||
expected_after_compact_user_texts.extend(["hello world".to_string(), summary_after_compact]);
|
||||
expected_after_compact_user_texts.push("AFTER_COMPACT".to_string());
|
||||
assert_eq!(
|
||||
json_message_input_texts(&requests[2], "user"),
|
||||
expected_after_compact_user_texts
|
||||
);
|
||||
|
||||
let mut expected_after_resume_user_texts =
|
||||
vec!["hello world".to_string(), summary_after_resume];
|
||||
expected_after_resume_user_texts.extend_from_slice(seeded_user_prefix);
|
||||
let mut expected_after_resume_user_texts = seeded_user_prefix.to_vec();
|
||||
expected_after_resume_user_texts.extend(["hello world".to_string(), summary_after_resume]);
|
||||
expected_after_resume_user_texts.push("AFTER_COMPACT".to_string());
|
||||
let after_resume_user_texts = json_message_input_texts(&requests[3], "user");
|
||||
let (after_resume_last, after_resume_prefix) = after_resume_user_texts
|
||||
@@ -317,9 +315,8 @@ async fn compact_resume_and_fork_preserve_model_history_view() {
|
||||
}
|
||||
|
||||
let after_fork_user_texts = json_message_input_texts(&requests[4], "user");
|
||||
let mut expected_after_fork_history_prefix =
|
||||
vec!["hello world".to_string(), summary_after_fork];
|
||||
expected_after_fork_history_prefix.extend_from_slice(seeded_user_prefix);
|
||||
let mut expected_after_fork_history_prefix = seeded_user_prefix.to_vec();
|
||||
expected_after_fork_history_prefix.extend(["hello world".to_string(), summary_after_fork]);
|
||||
expected_after_fork_history_prefix.push("AFTER_COMPACT".to_string());
|
||||
let (after_fork_last, after_fork_prefix) = after_fork_user_texts
|
||||
.split_last()
|
||||
@@ -450,15 +447,19 @@ async fn compact_resume_after_second_compaction_preserves_history() -> Result<()
|
||||
"hello world".to_string(),
|
||||
"AFTER_COMPACT".to_string(),
|
||||
"AFTER_RESUME".to_string(),
|
||||
"AFTER_FORK".to_string(),
|
||||
summary_after_second_compact.clone(),
|
||||
];
|
||||
expected_after_second_compact_user_texts.extend_from_slice(seeded_user_prefix);
|
||||
expected_after_second_compact_user_texts.push("AFTER_COMPACT_2".to_string());
|
||||
let mut expected_fork_local_user_texts =
|
||||
vec!["AFTER_FORK".to_string(), summary_after_second_compact];
|
||||
expected_fork_local_user_texts.extend_from_slice(seeded_user_prefix);
|
||||
expected_fork_local_user_texts.push("AFTER_COMPACT_2".to_string());
|
||||
expected_after_second_compact_user_texts.extend([
|
||||
"AFTER_FORK".to_string(),
|
||||
summary_after_second_compact.clone(),
|
||||
"AFTER_COMPACT_2".to_string(),
|
||||
]);
|
||||
let mut expected_fork_local_user_texts = seeded_user_prefix.to_vec();
|
||||
expected_fork_local_user_texts.extend([
|
||||
"AFTER_FORK".to_string(),
|
||||
summary_after_second_compact,
|
||||
"AFTER_COMPACT_2".to_string(),
|
||||
]);
|
||||
let final_user_texts = json_message_input_texts(&requests[requests.len() - 1], "user");
|
||||
let (final_last, final_prefix) = final_user_texts
|
||||
.split_last()
|
||||
|
||||
@@ -3236,17 +3236,8 @@ async fn production_turn_keeps_rebalanced_catalogs_stable_after_compaction_and_r
|
||||
_ => None,
|
||||
})
|
||||
.expect("compaction retains an allocation checkpoint");
|
||||
let allocation = checkpoint.state["cloud_skills"]["allocation"]
|
||||
.as_object()
|
||||
.expect("retained allocation");
|
||||
assert_eq!(
|
||||
serde_json::to_value(checkpoint)?,
|
||||
json!({
|
||||
"full": true,
|
||||
"state": { "cloud_skills": { "allocation": allocation } },
|
||||
}),
|
||||
"compaction retains only allocation metadata, not rendered catalogs",
|
||||
);
|
||||
assert!(checkpoint.full);
|
||||
assert!(checkpoint.state["cloud_skills"]["allocation"].is_object());
|
||||
let resumed = test
|
||||
.thread_manager
|
||||
.start_thread(StartThreadOptions {
|
||||
@@ -3337,7 +3328,7 @@ async fn production_turn_keeps_rebalanced_catalogs_stable_after_compaction_and_r
|
||||
.flat_map(|text| text.lines())
|
||||
.filter(|line| line.starts_with("- exec-"))
|
||||
.collect::<Vec<_>>(),
|
||||
if matches!(index, 0 | 4) {
|
||||
if index == 0 {
|
||||
Vec::new()
|
||||
} else {
|
||||
ready_executor_lines.clone()
|
||||
@@ -3358,7 +3349,7 @@ async fn production_turn_keeps_rebalanced_catalogs_stable_after_compaction_and_r
|
||||
"cloud_skills_across_executor_readiness"
|
||||
},
|
||||
context_snapshot::format_request_history_snapshot(
|
||||
"Cloud skills rebalance once to retain every executor skill. Post-turn compaction and resume restore the same cloud allocation before the executor reconnects; both catalogs are fully reinjected once into the new history.",
|
||||
"Cloud skills rebalance once to retain every executor skill. Post-turn compaction installs both catalogs and their allocation before resume; the saved catalogs remain visible while the executor reconnects.",
|
||||
&requests,
|
||||
&ContextSnapshotOptions::default()
|
||||
.rewrite_known_segments()
|
||||
|
||||
+20
-20
@@ -26,31 +26,31 @@ Scenario: Manual /compact with prior user history compacts existing history and
|
||||
04:message/user:
|
||||
<SUMMARIZATION_PROMPT>
|
||||
|
||||
## Window 3 (after request 2: input diverged at item 00, settings changed (parallel_tool_calls, tools))
|
||||
## Window 3 (after request 2: input diverged at item 03, settings changed (parallel_tool_calls, tools))
|
||||
-- request 3 (turn) --
|
||||
00:message/user:
|
||||
first manual turn
|
||||
00:message/developer:
|
||||
<PERMISSIONS_INSTRUCTIONS>
|
||||
01:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
02:message/user:
|
||||
first manual turn
|
||||
03:message/user:
|
||||
<COMPACTION_SUMMARY>
|
||||
FIRST_MANUAL_SUMMARY
|
||||
02:message/developer:
|
||||
<PERMISSIONS_INSTRUCTIONS>
|
||||
03:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
04:message/user:
|
||||
second manual turn
|
||||
|
||||
## Window 4 (after request 3: settings changed (parallel_tool_calls, tools))
|
||||
-- request 4 (compaction) --
|
||||
00:message/user:
|
||||
first manual turn
|
||||
00:message/developer:
|
||||
<PERMISSIONS_INSTRUCTIONS>
|
||||
01:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
02:message/user:
|
||||
first manual turn
|
||||
03:message/user:
|
||||
<COMPACTION_SUMMARY>
|
||||
FIRST_MANUAL_SUMMARY
|
||||
02:message/developer:
|
||||
<PERMISSIONS_INSTRUCTIONS>
|
||||
03:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
04:message/user:
|
||||
second manual turn
|
||||
05:message/assistant:
|
||||
@@ -58,18 +58,18 @@ Scenario: Manual /compact with prior user history compacts existing history and
|
||||
06:message/user:
|
||||
<SUMMARIZATION_PROMPT>
|
||||
|
||||
## Window 5 (after request 4: input diverged at item 01, settings changed (parallel_tool_calls, tools))
|
||||
## Window 5 (after request 4: input diverged at item 00, settings changed (parallel_tool_calls, tools))
|
||||
-- request 5 (turn) --
|
||||
00:message/user:
|
||||
first manual turn
|
||||
01:message/user:
|
||||
second manual turn
|
||||
01:message/developer:
|
||||
<PERMISSIONS_INSTRUCTIONS>
|
||||
02:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
03:message/user:
|
||||
second manual turn
|
||||
04:message/user:
|
||||
<COMPACTION_SUMMARY>
|
||||
SECOND_MANUAL_SUMMARY
|
||||
03:message/developer:
|
||||
<PERMISSIONS_INSTRUCTIONS>
|
||||
04:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
05:message/user:
|
||||
post compact follow-up
|
||||
|
||||
+5
-5
@@ -11,12 +11,12 @@ Scenario: Manual /compact with no prior user turn currently still issues a compa
|
||||
|
||||
## Window 2 (after request 1: input diverged at item 00, settings changed (parallel_tool_calls, tools))
|
||||
-- request 2 (turn; Local Post-Compaction History Layout) --
|
||||
00:message/user:
|
||||
00:message/developer:
|
||||
<PERMISSIONS_INSTRUCTIONS>
|
||||
01:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
02:message/user:
|
||||
<COMPACTION_SUMMARY>
|
||||
MANUAL_EMPTY_SUMMARY
|
||||
01:message/developer:
|
||||
<PERMISSIONS_INSTRUCTIONS>
|
||||
02:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
03:message/user:
|
||||
AFTER_MANUAL_EMPTY_COMPACT
|
||||
|
||||
+7
-7
@@ -28,15 +28,15 @@ Scenario: Pre-sampling compaction on model switch to a smaller context window: c
|
||||
|
||||
## Window 3 (after request 2: input diverged at item 00, settings changed (model, parallel_tool_calls, tools))
|
||||
-- request 3 (turn; Post-Compaction Follow-up Request (Next Model)) --
|
||||
00:message/user:
|
||||
before switch
|
||||
01:message/user:
|
||||
<COMPACTION_SUMMARY>
|
||||
PRE_SAMPLING_SUMMARY
|
||||
02:message/developer[2]:
|
||||
00:message/developer[2]:
|
||||
[01] <MODEL_SWITCH>
|
||||
[02] <PERMISSIONS_INSTRUCTIONS>
|
||||
03:message/user:
|
||||
01:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
02:message/user:
|
||||
before switch
|
||||
03:message/user:
|
||||
<COMPACTION_SUMMARY>
|
||||
PRE_SAMPLING_SUMMARY
|
||||
04:message/user:
|
||||
after switch
|
||||
|
||||
+6
-6
@@ -39,15 +39,15 @@ Scenario: Pre-turn auto-compaction uses the prior context without the incoming u
|
||||
-- request 4 (turn) --
|
||||
00:message/user:
|
||||
USER_ONE
|
||||
01:message/user:
|
||||
USER_TWO
|
||||
01:message/developer:
|
||||
<PERMISSIONS_INSTRUCTIONS>
|
||||
02:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
03:message/user:
|
||||
USER_TWO
|
||||
04:message/user:
|
||||
<COMPACTION_SUMMARY>
|
||||
PRE_TURN_SUMMARY
|
||||
03:message/developer:
|
||||
<PERMISSIONS_INSTRUCTIONS>
|
||||
04:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
05:message/user[2]:
|
||||
[01] <input_image:detail,image_url>
|
||||
[02] USER_THREE
|
||||
|
||||
+7
-7
@@ -28,15 +28,15 @@ Scenario: Pre-turn compaction during model switch (without pre-sampling model-sw
|
||||
|
||||
## Window 3 (after request 2: input diverged at item 00, settings changed (parallel_tool_calls, tools))
|
||||
-- request 3 (turn; Local Post-Compaction History Layout) --
|
||||
00:message/user:
|
||||
BEFORE_SWITCH_USER
|
||||
01:message/user:
|
||||
<COMPACTION_SUMMARY>
|
||||
PRETURN_SWITCH_SUMMARY
|
||||
02:message/developer[2]:
|
||||
00:message/developer[2]:
|
||||
[01] <MODEL_SWITCH>
|
||||
[02] <PERMISSIONS_INSTRUCTIONS>
|
||||
03:message/user:
|
||||
01:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
02:message/user:
|
||||
BEFORE_SWITCH_USER
|
||||
03:message/user:
|
||||
<COMPACTION_SUMMARY>
|
||||
PRETURN_SWITCH_SUMMARY
|
||||
04:message/user:
|
||||
AFTER_SWITCH_USER
|
||||
|
||||
+7
-7
@@ -222,10 +222,7 @@ Settings: same as window 1
|
||||
|
||||
02:message/user:
|
||||
Plan a team kickoff for Friday using $outline and $calendar:agenda.
|
||||
03:message/user:
|
||||
Summarize the kickoff notes with $notes:summarize.
|
||||
04:compaction:encrypted=true; chars=26; hash=ec5e2724e6f5f641
|
||||
05:message/developer[3]:
|
||||
03:message/developer[3]:
|
||||
[01] <skills_instructions>
|
||||
## Skills
|
||||
A skill is a set of local instructions to follow that is stored in a `SKILL.md` file. Below is the list ...ed into an absolute path using the skill roots table. [hash=ca27c3957f42502b]
|
||||
@@ -271,7 +268,7 @@ Settings: same as window 1
|
||||
|
||||
Never use the `request_user_input` tool for permission requests or permission-related escalations.
|
||||
</collaboration_mode>
|
||||
06:message/developer:
|
||||
04:message/developer:
|
||||
<multi_agent_role>You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals.
|
||||
|
||||
At the start of your turn, you are the active agent.
|
||||
@@ -289,9 +286,9 @@ Settings: same as window 1
|
||||
There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
|
||||
|
||||
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...ne"` or a positive integer string.</multi_agent_role> [hash=c7f52e90688299ce]
|
||||
07:message/developer:
|
||||
05:message/developer:
|
||||
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
|
||||
08:message/user[2]:
|
||||
06:message/user[2]:
|
||||
[01] # AGENTS.md instructions for <AGENTS_DIRECTORY>
|
||||
|
||||
<INSTRUCTIONS>
|
||||
@@ -304,6 +301,9 @@ Settings: same as window 1
|
||||
<timezone><HOST_TIMEZONE></timezone>
|
||||
<filesystem><workspace_roots><root><WORKSPACE_ROOT 1></root></workspace_roots><permission_profile type...ntry></file_system></permission_profile></filesystem> [hash=911722cc43b4c3b8]
|
||||
</environment_context>
|
||||
07:message/user:
|
||||
Summarize the kickoff notes with $notes:summarize.
|
||||
08:compaction:encrypted=true; chars=26; hash=ec5e2724e6f5f641
|
||||
09:message/user[2]:
|
||||
[01] Check the final kickoff brief and attached sketch with $final-check and $calendar:agenda.
|
||||
[02] <input_image:image_url>
|
||||
|
||||
+58
-9
@@ -108,9 +108,58 @@ Settings:
|
||||
## Window 2 (after request 2: input diverged at item 00)
|
||||
Settings: unavailable (items only)
|
||||
-- request 3 (items; Stored replacement before next turn) --
|
||||
00:message/user:
|
||||
00:message/developer[2]:
|
||||
[01] <permissions instructions>
|
||||
Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `danger-full-access`...ll commands are permitted. Network access is enabled. [hash=c134061cebb94a1b]
|
||||
Approval policy is currently never. Do not provide the `sandbox_permissions` for any reason, commands will be rejected.
|
||||
</permissions instructions>
|
||||
[02] <collaboration_mode># Collaboration Mode: Default
|
||||
|
||||
You are now in Default mode. Any previous instructions for other modes (e.g. Plan mode) are no longer active.
|
||||
|
||||
Your active mode changes only when new developer instructions with a different `<collaboration_mode>...<...by themselves. Known mode names are Default and Plan. [hash=e75f6b12fc52d812]
|
||||
|
||||
## request_user_input availability
|
||||
|
||||
Use the `request_user_input` tool only when it is listed in the available tools for this turn.
|
||||
|
||||
Use the `request_user_input` tool only for optional questions where the answer would materially improve the quality of the work.
|
||||
|
||||
If `request_user_input` returns no answers, continue with best judgment instead of asking again or treating the turn as blocked.
|
||||
|
||||
Never use the `request_user_input` tool for permission requests or permission-related escalations.
|
||||
</collaboration_mode>
|
||||
01:message/developer:
|
||||
<multi_agent_role>You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals.
|
||||
|
||||
At the start of your turn, you are the active agent.
|
||||
You can spawn sub-agents to handle subtasks, and those sub-agents can spawn their own sub-agents.
|
||||
All agents in the team, including the agents that you can assign tasks to, are equally intelligent and capable, and have access to the same set of tools.
|
||||
|
||||
You can use `spawn_agent` to create a new agent, `followup_task` to give an existing agent a new task an...message to a running agent without triggering a turn. [hash=7d40ff8b58977202]
|
||||
`send_message` calls may be read by a human, so ensure they are legible. Always put proper spaces between words and/or numbers.
|
||||
<OMITTED 17 LINES; ~246 TOKENS; hash=780031e2df907944>
|
||||
- All agents use the same current working directory.
|
||||
- As a result, edits made by one agent are immediately visible to all other agents.
|
||||
|
||||
When calling `wait_agent`, prefer longer waits (minutes) to avoid busy polling.
|
||||
|
||||
There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
|
||||
|
||||
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...ne"` or a positive integer string.</multi_agent_role> [hash=c7f52e90688299ce]
|
||||
02:message/developer:
|
||||
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
|
||||
03:message/user:
|
||||
<environment_context>
|
||||
<cwd><CWD></cwd>
|
||||
<shell><HOST_SHELL></shell>
|
||||
<current_date><CURRENT_DATE></current_date>
|
||||
<timezone><HOST_TIMEZONE></timezone>
|
||||
<filesystem><workspace_roots><root><CWD></root></workspace_roots><permission_profile type="disabled"><...e="unrestricted" /></permission_profile></filesystem> [hash=d40b2f45cb028888]
|
||||
</environment_context>
|
||||
04:message/user:
|
||||
Plan the release.
|
||||
01:compaction:encrypted=true; chars=31; hash=8255caf138edede8
|
||||
05:compaction:encrypted=true; chars=31; hash=8255caf138edede8
|
||||
|
||||
## Window 3 (after request 3: input diverged at item 00)
|
||||
Settings: same as window 1
|
||||
@@ -148,10 +197,7 @@ Settings: same as window 1
|
||||
- Relevance: Determine what a plugin can help with from explicit user mention or from the plugin-associa..., MCP tools, and apps exposed elsewhere in this turn. [hash=620459a4e7b590f0]
|
||||
- Missing/blocked: If the user requests a plugin that does not have relevant callable capabilities for t..., say so briefly and continue with the best fallback. [hash=746b0469a8f6cb3e]
|
||||
|
||||
02:message/user:
|
||||
Plan the release.
|
||||
03:compaction:encrypted=true; chars=31; hash=8255caf138edede8
|
||||
04:message/developer[2]:
|
||||
02:message/developer[2]:
|
||||
[01] <permissions instructions>
|
||||
Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `danger-full-access`...ll commands are permitted. Network access is enabled. [hash=c134061cebb94a1b]
|
||||
Approval policy is currently never. Do not provide the `sandbox_permissions` for any reason, commands will be rejected.
|
||||
@@ -172,7 +218,7 @@ Settings: same as window 1
|
||||
|
||||
Never use the `request_user_input` tool for permission requests or permission-related escalations.
|
||||
</collaboration_mode>
|
||||
05:message/developer:
|
||||
03:message/developer:
|
||||
<multi_agent_role>You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals.
|
||||
|
||||
At the start of your turn, you are the active agent.
|
||||
@@ -190,9 +236,9 @@ Settings: same as window 1
|
||||
There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
|
||||
|
||||
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...ne"` or a positive integer string.</multi_agent_role> [hash=c7f52e90688299ce]
|
||||
06:message/developer:
|
||||
04:message/developer:
|
||||
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
|
||||
07:message/user:
|
||||
05:message/user:
|
||||
<environment_context>
|
||||
<cwd><CWD></cwd>
|
||||
<shell><HOST_SHELL></shell>
|
||||
@@ -200,5 +246,8 @@ Settings: same as window 1
|
||||
<timezone><HOST_TIMEZONE></timezone>
|
||||
<filesystem><workspace_roots><root><CWD></root></workspace_roots><permission_profile type="disabled"><...e="unrestricted" /></permission_profile></filesystem> [hash=d40b2f45cb028888]
|
||||
</environment_context>
|
||||
06:message/user:
|
||||
Plan the release.
|
||||
07:compaction:encrypted=true; chars=31; hash=8255caf138edede8
|
||||
08:message/user:
|
||||
Continue with the first step.
|
||||
|
||||
+21
-21
@@ -59,14 +59,14 @@ Settings: relative to window 1
|
||||
<MODEL_INSTRUCTIONS>
|
||||
02:message/user:
|
||||
Keep the working tree unchanged.
|
||||
03:message/user:
|
||||
Confirm where publishing is allowed.
|
||||
04:compaction:encrypted=true; chars=39; hash=4010be12538dbedf
|
||||
05:message/developer[2]:
|
||||
03:message/developer[2]:
|
||||
[01] <MODEL_SWITCH>
|
||||
[02] <PERMISSIONS_INSTRUCTIONS>
|
||||
06:message/user:
|
||||
04:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
05:message/user:
|
||||
Confirm where publishing is allowed.
|
||||
06:compaction:encrypted=true; chars=39; hash=4010be12538dbedf
|
||||
07:message/user:
|
||||
Continue with the new model.
|
||||
-- request 5 (turn) --
|
||||
@@ -196,14 +196,14 @@ Settings: same as window 2
|
||||
<MODEL_INSTRUCTIONS>
|
||||
02:message/user:
|
||||
Keep the working tree unchanged.
|
||||
03:message/user:
|
||||
Confirm where publishing is allowed.
|
||||
04:compaction:encrypted=true; chars=39; hash=4010be12538dbedf
|
||||
05:message/developer[2]:
|
||||
03:message/developer[2]:
|
||||
[01] <MODEL_SWITCH>
|
||||
[02] <PERMISSIONS_INSTRUCTIONS>
|
||||
06:message/user:
|
||||
04:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
05:message/user:
|
||||
Confirm where publishing is allowed.
|
||||
06:compaction:encrypted=true; chars=39; hash=4010be12538dbedf
|
||||
07:message/user:
|
||||
Continue with the new model.
|
||||
08:message/user:
|
||||
@@ -213,7 +213,7 @@ Settings: same as window 2
|
||||
-- request 8 (compaction) --
|
||||
11:compaction_trigger
|
||||
|
||||
## Window 5 (after request 8: input diverged at item 04)
|
||||
## Window 5 (after request 8: input diverged at item 03)
|
||||
Settings: same as window 2
|
||||
-- request 9 (turn) --
|
||||
00:additional_tools/developer (1; hash=1ea3c73f7e3c8b33):
|
||||
@@ -229,13 +229,13 @@ Settings: same as window 2
|
||||
Confirm where publishing is allowed.
|
||||
04:message/user:
|
||||
Continue with the new model.
|
||||
05:message/user:
|
||||
Run another check after compaction.
|
||||
06:compaction:encrypted=true; chars=33; hash=5c07b4383c427607
|
||||
07:message/developer:
|
||||
05:message/developer:
|
||||
<PERMISSIONS_INSTRUCTIONS>
|
||||
08:message/user:
|
||||
06:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
07:message/user:
|
||||
Run another check after compaction.
|
||||
08:compaction:encrypted=true; chars=33; hash=5c07b4383c427607
|
||||
09:message/user:
|
||||
Run another check after compaction.
|
||||
|
||||
@@ -353,13 +353,13 @@ Settings: same as window 2
|
||||
Confirm where publishing is allowed.
|
||||
04:message/user:
|
||||
Continue with the new model.
|
||||
05:message/user:
|
||||
Run another check after compaction.
|
||||
06:compaction:encrypted=true; chars=33; hash=5c07b4383c427607
|
||||
07:message/developer:
|
||||
05:message/developer:
|
||||
<PERMISSIONS_INSTRUCTIONS>
|
||||
08:message/user:
|
||||
06:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
07:message/user:
|
||||
Run another check after compaction.
|
||||
08:compaction:encrypted=true; chars=33; hash=5c07b4383c427607
|
||||
09:message/user:
|
||||
Run another check after compaction.
|
||||
10:function_call/exec_command:{"cmd":"exit 0","sandbox_permissions":"require_escalated"}
|
||||
|
||||
+18
-13
@@ -1,8 +1,8 @@
|
||||
---
|
||||
source: core/tests/suite/skills_extension.rs
|
||||
expression: "context_snapshot::format_request_history_snapshot(\"Cloud skills rebalance once to retain every executor skill. Post-turn compaction and resume restore the same cloud allocation before the executor reconnects; both catalogs are fully reinjected once into the new history.\",\n&requests,\n&ContextSnapshotOptions::default().rewrite_known_segments().include_request_settings(),)"
|
||||
expression: "context_snapshot::format_request_history_snapshot(\"Cloud skills rebalance once to retain every executor skill. Post-turn compaction installs both catalogs and their allocation before resume; the saved catalogs remain visible while the executor reconnects.\",\n&requests,\n&ContextSnapshotOptions::default().rewrite_known_segments().include_request_settings(),)"
|
||||
---
|
||||
Scenario: Cloud skills rebalance once to retain every executor skill. Post-turn compaction and resume restore the same cloud allocation before the executor reconnects; both catalogs are fully reinjected once into the new history.
|
||||
Scenario: Cloud skills rebalance once to retain every executor skill. Post-turn compaction installs both catalogs and their allocation before resume; the saved catalogs remain visible while the executor reconnects.
|
||||
|
||||
## Window 1
|
||||
Settings:
|
||||
@@ -102,12 +102,7 @@ Settings: same as window 1
|
||||
-- request 5 (turn) --
|
||||
00:message/user:
|
||||
Inspect the available skills.
|
||||
01:message/user:
|
||||
Inspect the available skills again.
|
||||
02:message/user:
|
||||
<COMPACTION_SUMMARY>
|
||||
The available skills were inspected.
|
||||
03:message/developer[3]:
|
||||
01:message/developer[4]:
|
||||
[01] <PERMISSIONS_INSTRUCTIONS>
|
||||
[02] <environments_instructions>
|
||||
## Execution environments
|
||||
@@ -118,14 +113,24 @@ Settings: same as window 1
|
||||
Wait only when the current task needs that environment. Continue using tools that are already available for unrelated work.
|
||||
</environments_instructions>
|
||||
[03] <SKILLS_INSTRUCTIONS>
|
||||
04:message/user:
|
||||
[04] <SKILLS_INSTRUCTIONS>
|
||||
02:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
03:message/user:
|
||||
Inspect the available skills again.
|
||||
04:message/user:
|
||||
<COMPACTION_SUMMARY>
|
||||
The available skills were inspected.
|
||||
05:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
06:message/developer:
|
||||
<SKILLS_INSTRUCTIONS>
|
||||
07:message/user:
|
||||
Inspect the skills after resuming.
|
||||
-- request 6 (turn) --
|
||||
06:function_call/wait_for_environment:{"environment_id":"skills-executor"}
|
||||
07:function_call_output:{"environment_id":"skills-executor","status":"ready"}
|
||||
08:message/user:
|
||||
08:function_call/wait_for_environment:{"environment_id":"skills-executor"}
|
||||
09:function_call_output:{"environment_id":"skills-executor","status":"ready"}
|
||||
10:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
09:message/developer:
|
||||
11:message/developer:
|
||||
<SKILLS_INSTRUCTIONS>
|
||||
|
||||
+31
-21
@@ -1,8 +1,8 @@
|
||||
---
|
||||
source: core/tests/suite/skills_extension.rs
|
||||
expression: "context_snapshot::format_request_history_snapshot(\"Cloud skills rebalance once to retain every executor skill. Post-turn compaction and resume restore the same cloud allocation before the executor reconnects; both catalogs are fully reinjected once into the new history.\",\n&requests,\n&ContextSnapshotOptions::default().rewrite_known_segments().include_request_settings(),)"
|
||||
expression: "context_snapshot::format_request_history_snapshot(\"Cloud skills rebalance once to retain every executor skill. Post-turn compaction installs both catalogs and their allocation before resume; the saved catalogs remain visible while the executor reconnects.\",\n&requests,\n&ContextSnapshotOptions::default().rewrite_known_segments().include_request_settings(),)"
|
||||
---
|
||||
Scenario: Cloud skills rebalance once to retain every executor skill. Post-turn compaction and resume restore the same cloud allocation before the executor reconnects; both catalogs are fully reinjected once into the new history.
|
||||
Scenario: Cloud skills rebalance once to retain every executor skill. Post-turn compaction installs both catalogs and their allocation before resume; the saved catalogs remain visible while the executor reconnects.
|
||||
|
||||
## Window 1
|
||||
Settings:
|
||||
@@ -122,12 +122,7 @@ Settings: same as window 1
|
||||
-- request 5 (turn) --
|
||||
00:message/user:
|
||||
Inspect the available skills.
|
||||
01:message/user:
|
||||
Inspect the available skills again.
|
||||
02:message/user:
|
||||
<COMPACTION_SUMMARY>
|
||||
The available skills were inspected.
|
||||
03:message/developer[3]:
|
||||
01:message/developer[4]:
|
||||
[01] <PERMISSIONS_INSTRUCTIONS>
|
||||
[02] <environments_instructions>
|
||||
## Execution environments
|
||||
@@ -138,9 +133,19 @@ Settings: same as window 1
|
||||
Wait only when the current task needs that environment. Continue using tools that are already available for unrelated work.
|
||||
</environments_instructions>
|
||||
[03] <SKILLS_INSTRUCTIONS>
|
||||
04:message/user:
|
||||
[04] <SKILLS_INSTRUCTIONS>
|
||||
02:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
03:message/user:
|
||||
Inspect the available skills again.
|
||||
04:message/user:
|
||||
<COMPACTION_SUMMARY>
|
||||
The available skills were inspected.
|
||||
05:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
06:message/developer:
|
||||
<SKILLS_INSTRUCTIONS>
|
||||
07:message/user:
|
||||
Inspect the skills after resuming.
|
||||
|
||||
## Window 5 (after request 5)
|
||||
@@ -148,12 +153,7 @@ Settings: same as window 2
|
||||
-- request 6 (turn) --
|
||||
00:message/user:
|
||||
Inspect the available skills.
|
||||
01:message/user:
|
||||
Inspect the available skills again.
|
||||
02:message/user:
|
||||
<COMPACTION_SUMMARY>
|
||||
The available skills were inspected.
|
||||
03:message/developer[3]:
|
||||
01:message/developer[4]:
|
||||
[01] <PERMISSIONS_INSTRUCTIONS>
|
||||
[02] <environments_instructions>
|
||||
## Execution environments
|
||||
@@ -164,13 +164,23 @@ Settings: same as window 2
|
||||
Wait only when the current task needs that environment. Continue using tools that are already available for unrelated work.
|
||||
</environments_instructions>
|
||||
[03] <SKILLS_INSTRUCTIONS>
|
||||
[04] <SKILLS_INSTRUCTIONS>
|
||||
02:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
03:message/user:
|
||||
Inspect the available skills again.
|
||||
04:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
<COMPACTION_SUMMARY>
|
||||
The available skills were inspected.
|
||||
05:message/user:
|
||||
Inspect the skills after resuming.
|
||||
06:function_call/wait_for_environment:{"environment_id":"skills-executor"}
|
||||
07:function_call_output:{"environment_id":"skills-executor","status":"ready"}
|
||||
08:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
09:message/developer:
|
||||
06:message/developer:
|
||||
<SKILLS_INSTRUCTIONS>
|
||||
07:message/user:
|
||||
Inspect the skills after resuming.
|
||||
08:function_call/wait_for_environment:{"environment_id":"skills-executor"}
|
||||
09:function_call_output:{"environment_id":"skills-executor","status":"ready"}
|
||||
10:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
11:message/developer:
|
||||
<SKILLS_INSTRUCTIONS>
|
||||
|
||||
@@ -142,18 +142,6 @@ pub trait ContextContributor: Send + Sync {
|
||||
Vec::new()
|
||||
})
|
||||
}
|
||||
|
||||
/// Retains bounded extension metadata when compaction discards rendered context.
|
||||
///
|
||||
/// Return only this contributor's section IDs, without rendered text or availability
|
||||
/// state. Core persists these partial sections so the next step can rebuild full
|
||||
/// context without losing decisions that are independent of model-visible history.
|
||||
fn retain_world_state_after_compaction(
|
||||
&self,
|
||||
_previous_world_state: &serde_json::Map<String, serde_json::Value>,
|
||||
) -> serde_json::Map<String, serde_json::Value> {
|
||||
serde_json::Map::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Contributor for host-owned thread lifecycle gates.
|
||||
|
||||
@@ -71,7 +71,6 @@ use crate::tools::SkillAnalytics;
|
||||
use crate::tools::SkillToolAuthority;
|
||||
use crate::tools::skill_tools;
|
||||
use crate::warnings::bounded_warnings;
|
||||
use crate::world_state::CLOUD_SKILLS_WORLD_STATE_ID;
|
||||
use crate::world_state_catalogs::CatalogContext;
|
||||
use crate::world_state_catalogs::CatalogStatus;
|
||||
|
||||
@@ -265,25 +264,6 @@ where
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
fn retain_world_state_after_compaction(
|
||||
&self,
|
||||
previous_world_state: &serde_json::Map<String, serde_json::Value>,
|
||||
) -> serde_json::Map<String, serde_json::Value> {
|
||||
let mut retained = serde_json::Map::new();
|
||||
if let Some(allocation) = previous_world_state
|
||||
.get(CLOUD_SKILLS_WORLD_STATE_ID)
|
||||
.and_then(|section| section.get("allocation"))
|
||||
{
|
||||
// Allocation survives lost history; the catalog fingerprint and budget still
|
||||
// determine whether it can be reused after fresh discovery and policy resolution.
|
||||
retained.insert(
|
||||
CLOUD_SKILLS_WORLD_STATE_ID.to_string(),
|
||||
serde_json::json!({ "allocation": allocation }),
|
||||
);
|
||||
}
|
||||
retained
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> ToolContributor for SkillsExtension<C>
|
||||
|
||||
Reference in New Issue
Block a user