inline model instructions in initial context

This commit is contained in:
pakrym-oai
2026-06-20 23:29:49 -07:00
parent aaf737fa59
commit f6f0b8efca
67 changed files with 654 additions and 257 deletions
+6 -6
View File
@@ -26,8 +26,8 @@ pub const WS_REQUEST_HEADER_TRACESTATE_CLIENT_METADATA_KEY: &str = "ws_request_h
pub struct CompactionInput<'a> {
pub model: &'a str,
pub input: &'a [ResponseItem],
#[serde(skip_serializing_if = "str::is_empty")]
pub instructions: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
pub instructions: Option<&'a str>,
pub tools: Vec<Value>,
pub parallel_tool_calls: bool,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -182,8 +182,8 @@ impl From<VerbosityConfig> for OpenAiVerbosity {
#[derive(Debug, Serialize, Clone, PartialEq)]
pub struct ResponsesApiRequest {
pub model: String,
#[serde(skip_serializing_if = "String::is_empty")]
pub instructions: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub instructions: Option<String>,
pub input: Vec<ResponseItem>,
pub tools: Vec<serde_json::Value>,
pub tool_choice: String,
@@ -228,8 +228,8 @@ impl From<&ResponsesApiRequest> for ResponseCreateWsRequest {
#[derive(Debug, Serialize)]
pub struct ResponseCreateWsRequest {
pub model: String,
#[serde(skip_serializing_if = "String::is_empty")]
pub instructions: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub instructions: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub previous_response_id: Option<String>,
pub input: Vec<ResponseItem>,
@@ -806,7 +806,7 @@ mod tests {
fn direct_serialization_preserves_websocket_request_payload() {
let request = ResponsesWsRequest::ResponseCreate(ResponseCreateWsRequest {
model: "gpt-test".to_string(),
instructions: "Use the available tools.".to_string(),
instructions: Some("Use the available tools.".to_string()),
previous_response_id: Some("resp-1".to_string()),
input: vec![ResponseItem::Message {
id: Some("msg-1".to_string()),
+3 -3
View File
@@ -307,7 +307,7 @@ async fn responses_client_stream_request_preserves_item_ids() -> Result<()> {
let client = ResponsesClient::new(transport, provider("openai"), Arc::new(NoAuth));
let request = ResponsesApiRequest {
model: "gpt-test".into(),
instructions: "Say hi".into(),
instructions: Some("Say hi".into()),
input: vec![ResponseItem::Message {
id: Some("msg_1".into()),
role: "user".into(),
@@ -399,7 +399,7 @@ async fn streaming_client_retries_on_transport_error() -> Result<()> {
let request = ResponsesApiRequest {
model: "gpt-test".into(),
instructions: "Say hi".into(),
instructions: Some("Say hi".into()),
input: Vec::new(),
tools: Vec::new(),
tool_choice: "auto".into(),
@@ -512,7 +512,7 @@ async fn azure_store_sends_ids_and_headers() -> Result<()> {
let request = ResponsesApiRequest {
model: "gpt-test".into(),
instructions: "Say hi".into(),
instructions: Some("Say hi".into()),
input: vec![ResponseItem::Message {
id: Some("msg_1".into()),
role: "user".into(),
+6 -3
View File
@@ -535,7 +535,7 @@ impl ModelClient {
let payload = ApiCompactionInput {
model: &model,
input: &input,
instructions: &instructions,
instructions: instructions.as_deref(),
tools,
parallel_tool_calls,
reasoning,
@@ -778,7 +778,10 @@ impl ModelClient {
service_tier: Option<String>,
responses_metadata: &CodexResponsesMetadata,
) -> Result<ResponsesApiRequest> {
let instructions = &prompt.base_instructions.text;
let instructions = prompt
.base_instructions
.as_ref()
.map(|instructions| instructions.text.clone());
let mut input = prompt.get_formatted_input_for_request(model_info.use_responses_lite);
if !self.state.provider.info().is_openai() {
input.iter_mut().for_each(ResponseItem::clear_metadata);
@@ -810,7 +813,7 @@ impl ModelClient {
let service_tier = model_info.service_tier_for_request(service_tier);
let request = ResponsesApiRequest {
model: model_info.slug.clone(),
instructions: instructions.clone(),
instructions,
input,
tools,
tool_choice: "auto".to_string(),
+3 -2
View File
@@ -26,7 +26,8 @@ pub struct Prompt {
/// Whether parallel tool calls are permitted for this prompt.
pub(crate) parallel_tool_calls: bool,
pub base_instructions: BaseInstructions,
/// Base instructions to send through the top-level Responses API field.
pub base_instructions: Option<BaseInstructions>,
/// Optional the output schema for the model's response.
pub output_schema: Option<Value>,
@@ -41,7 +42,7 @@ impl Default for Prompt {
input: Vec::new(),
tools: Vec::new(),
parallel_tool_calls: false,
base_instructions: BaseInstructions::default(),
base_instructions: None,
output_schema: None,
output_schema_strict: true,
}
+4 -4
View File
@@ -108,7 +108,7 @@ fn serializes_text_verbosity_when_set() {
let tools: Vec<serde_json::Value> = vec![];
let req = ResponsesApiRequest {
model: "gpt-5.4".to_string(),
instructions: "i".to_string(),
instructions: Some("i".to_string()),
input,
tools,
tool_choice: "auto".to_string(),
@@ -155,7 +155,7 @@ fn serializes_text_schema_with_strict_format() {
let req = ResponsesApiRequest {
model: "gpt-5.4".to_string(),
instructions: "i".to_string(),
instructions: Some("i".to_string()),
input,
tools,
tool_choice: "auto".to_string(),
@@ -216,7 +216,7 @@ fn omits_text_when_not_set() {
let tools: Vec<serde_json::Value> = vec![];
let req = ResponsesApiRequest {
model: "gpt-5.4".to_string(),
instructions: "i".to_string(),
instructions: Some("i".to_string()),
input,
tools,
tool_choice: "auto".to_string(),
@@ -239,7 +239,7 @@ fn omits_text_when_not_set() {
fn serializes_flex_service_tier_when_set() {
let req = ResponsesApiRequest {
model: "gpt-5.4".to_string(),
instructions: "i".to_string(),
instructions: Some("i".to_string()),
input: vec![],
tools: vec![],
tool_choice: "auto".to_string(),
+56
View File
@@ -1,6 +1,7 @@
use super::AuthRequestTelemetryContext;
use super::ModelClient;
use super::PendingUnauthorizedRetry;
use super::ReasoningSummaryConfig;
use super::UnauthorizedRecoveryExecution;
use super::X_CODEX_INSTALLATION_ID_HEADER;
use super::X_CODEX_PARENT_THREAD_ID_HEADER;
@@ -10,6 +11,7 @@ use super::X_OPENAI_SUBAGENT_HEADER;
use crate::AttestationContext;
use crate::AttestationProvider;
use crate::GenerateAttestationFuture;
use crate::client_common::Prompt;
use crate::responses_metadata::CodexResponsesMetadata;
use crate::test_support::TestCodexResponsesRequestKind;
use crate::test_support::responses_metadata as test_responses_metadata;
@@ -25,6 +27,7 @@ use codex_model_provider_info::WireApi;
use codex_model_provider_info::create_oss_provider_with_base_url;
use codex_otel::SessionTelemetry;
use codex_protocol::ThreadId;
use codex_protocol::models::BaseInstructions;
use codex_protocol::models::ContentItem;
use codex_protocol::models::ResponseItem;
use codex_protocol::openai_models::ModelInfo;
@@ -146,6 +149,59 @@ fn test_session_telemetry() -> SessionTelemetry {
)
}
#[tokio::test]
async fn response_request_uses_top_level_instructions_only_for_legacy_prompts() {
let client = test_model_client(SessionSource::Exec);
let client_setup = client
.current_client_setup()
.await
.expect("resolve client setup");
let model_info = test_model_info();
let responses_metadata = test_responses_metadata_for_client(
&client,
Some("turn-1"),
format!("{}:0", client.state.thread_id),
/*parent_thread_id*/ None,
TestCodexResponsesRequestKind::Turn,
);
let mut prompt = Prompt {
base_instructions: Some(BaseInstructions {
text: "legacy instructions".to_string(),
}),
..Default::default()
};
let legacy_request = client
.build_responses_request(
&client_setup.api_provider,
&prompt,
&model_info,
/*effort*/ None,
ReasoningSummaryConfig::Auto,
/*service_tier*/ None,
&responses_metadata,
)
.expect("build legacy request");
assert_eq!(
legacy_request.instructions,
Some("legacy instructions".to_string())
);
prompt.base_instructions = None;
let inline_request = client
.build_responses_request(
&client_setup.api_provider,
&prompt,
&model_info,
/*effort*/ None,
ReasoningSummaryConfig::Auto,
/*service_tier*/ None,
&responses_metadata,
)
.expect("build inline request");
assert_eq!(inline_request.instructions, None);
}
#[derive(Default)]
struct TagCollectorVisitor {
tags: BTreeMap<String, String>,
+2 -1
View File
@@ -238,7 +238,8 @@ async fn run_compact_task_inner_impl(
let turn_input_len = turn_input.len();
let prompt = Prompt {
input: turn_input,
base_instructions: sess.get_base_instructions().await,
base_instructions: (!turn_context.inline_instructions)
.then_some(sess.get_base_instructions().await),
..Default::default()
};
let attempt_result = drain_to_completed(
+4 -4
View File
@@ -227,7 +227,7 @@ async fn run_remote_compact_task_inner_impl(
input: prompt_input,
tools: tool_router.model_visible_specs(),
parallel_tool_calls: turn_context.model_info.supports_parallel_tool_calls,
base_instructions,
base_instructions: (!turn_context.inline_instructions).then_some(base_instructions),
output_schema: None,
output_schema_strict: true,
};
@@ -379,8 +379,8 @@ pub(crate) fn trim_function_call_history_to_fit_context_window(
let item_count = history.raw_items().len();
for index in (0..item_count).rev() {
let Some(estimated_tokens_before) =
history.estimate_token_count_with_base_instructions(base_instructions)
let Some(estimated_tokens_before) = history
.estimate_token_count_with_resolved_base_instructions(turn_context, base_instructions)
else {
break;
};
@@ -398,7 +398,7 @@ pub(crate) fn trim_function_call_history_to_fit_context_window(
items[index] = rewritten_item;
history.replace(items);
let estimated_tokens_after = history
.estimate_token_count_with_base_instructions(base_instructions)
.estimate_token_count_with_resolved_base_instructions(turn_context, base_instructions)
.unwrap_or_default();
rewritten_outputs += 1;
estimated_deleted_tokens = estimated_deleted_tokens
+5 -2
View File
@@ -239,7 +239,7 @@ async fn run_remote_compact_task_inner_impl(
input,
tools: tool_router.model_visible_specs(),
parallel_tool_calls: turn_context.model_info.supports_parallel_tool_calls,
base_instructions,
base_instructions: (!turn_context.inline_instructions).then_some(base_instructions),
output_schema: None,
output_schema_strict: true,
};
@@ -252,7 +252,10 @@ async fn run_remote_compact_task_inner_impl(
);
let trace_attempt = compaction_trace.start_attempt(&serde_json::json!({
"model": turn_context.model_info.slug.as_str(),
"instructions": prompt.base_instructions.text.as_str(),
"instructions": prompt
.base_instructions
.as_ref()
.map(|instructions| instructions.text.as_str()),
"input": &prompt.input,
"parallel_tool_calls": prompt.parallel_tool_calls,
}));
@@ -190,6 +190,7 @@ fn turn_context_item_filesystem_uses_workspace_roots_instead_of_cwd() {
let repo_private = repo.join("private");
let item = TurnContextItem {
turn_id: None,
inline_instructions: false,
cwd: test_abs_path("/not-the-workspace"),
workspace_roots: Some(vec![repo.clone(), other_repo.clone()]),
current_date: None,
+2
View File
@@ -16,6 +16,7 @@ mod internal_model_context;
mod legacy_apply_patch_exec_command_warning;
mod legacy_model_mismatch_warning;
mod legacy_unified_exec_process_limit_warning;
mod model_instructions;
mod model_switch_instructions;
mod multi_agent_mode_instructions;
mod network_rule_saved;
@@ -59,6 +60,7 @@ pub use internal_model_context::InvalidInternalContextSource;
pub(crate) use legacy_apply_patch_exec_command_warning::LegacyApplyPatchExecCommandWarning;
pub(crate) use legacy_model_mismatch_warning::LegacyModelMismatchWarning;
pub(crate) use legacy_unified_exec_process_limit_warning::LegacyUnifiedExecProcessLimitWarning;
pub(crate) use model_instructions::ModelInstructions;
pub(crate) use model_switch_instructions::ModelSwitchInstructions;
pub(crate) use multi_agent_mode_instructions::MultiAgentModeInstructions;
pub(crate) use network_rule_saved::NetworkRuleSaved;
@@ -0,0 +1,32 @@
use super::ContextualUserFragment;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ModelInstructions {
instructions: String,
}
impl ModelInstructions {
pub(crate) fn new(instructions: impl Into<String>) -> Self {
Self {
instructions: instructions.into(),
}
}
}
impl ContextualUserFragment for ModelInstructions {
fn role(&self) -> &'static str {
"developer"
}
fn markers(&self) -> (&'static str, &'static str) {
Self::type_markers()
}
fn type_markers() -> (&'static str, &'static str) {
("", "")
}
fn body(&self) -> String {
self.instructions.clone()
}
}
+19 -2
View File
@@ -135,7 +135,19 @@ impl ContextManager {
let base_instructions = BaseInstructions {
text: model_info.get_model_instructions(personality),
};
self.estimate_token_count_with_base_instructions(&base_instructions)
self.estimate_token_count_with_resolved_base_instructions(turn_context, &base_instructions)
}
pub(crate) fn estimate_token_count_with_resolved_base_instructions(
&self,
turn_context: &TurnContext,
base_instructions: &BaseInstructions,
) -> Option<i64> {
if turn_context.inline_instructions {
self.estimate_token_count_without_base_instructions()
} else {
self.estimate_token_count_with_base_instructions(base_instructions)
}
}
pub(crate) fn estimate_token_count_with_base_instructions(
@@ -145,13 +157,18 @@ impl ContextManager {
let base_tokens =
i64::try_from(approx_token_count(&base_instructions.text)).unwrap_or(i64::MAX);
self.estimate_token_count_without_base_instructions()
.map(|items_tokens| base_tokens.saturating_add(items_tokens))
}
fn estimate_token_count_without_base_instructions(&self) -> Option<i64> {
let items_tokens = self
.items
.iter()
.map(estimate_item_token_count)
.fold(0i64, i64::saturating_add);
Some(base_tokens.saturating_add(items_tokens))
Some(items_tokens)
}
pub(crate) fn remove_first_item(&mut self) {
@@ -127,6 +127,7 @@ fn developer_msg_with_fragments(texts: &[&str]) -> ResponseItem {
fn reference_context_item() -> TurnContextItem {
TurnContextItem {
turn_id: Some("reference-turn".to_string()),
inline_instructions: false,
cwd: AbsolutePathBuf::try_from(
std::env::current_dir()
.expect("current directory")
+10 -3
View File
@@ -27,6 +27,7 @@ use crate::context::AvailablePluginsInstructions;
use crate::context::AvailableSkillsInstructions;
use crate::context::CollaborationModeInstructions;
use crate::context::ContextualUserFragment;
use crate::context::ModelInstructions;
use crate::context::MultiAgentModeInstructions;
use crate::context::NetworkRuleSaved;
use crate::context::PermissionsInstructions;
@@ -1403,7 +1404,10 @@ impl Session {
) {
let history = self.clone_history().await;
let base_instructions = self.get_base_instructions().await;
history.estimate_token_count_with_base_instructions(&base_instructions)
history.estimate_token_count_with_resolved_base_instructions(
turn_context,
&base_instructions,
)
} else {
None
};
@@ -3034,6 +3038,9 @@ impl Session {
state.auto_compact_window_ids(),
)
};
if turn_context.inline_instructions {
developer_sections.push(ModelInstructions::new(base_instructions.clone()).render());
}
if let Some(model_switch_message) =
crate::context_manager::updates::build_model_instructions_update_item(
previous_turn_settings.as_ref(),
@@ -3503,8 +3510,8 @@ impl Session {
pub(crate) async fn recompute_token_usage(&self, turn_context: &TurnContext) {
let history = self.clone_history().await;
let base_instructions = self.get_base_instructions().await;
let Some(estimated_total_tokens) =
history.estimate_token_count_with_base_instructions(&base_instructions)
let Some(estimated_total_tokens) = history
.estimate_token_count_with_resolved_base_instructions(turn_context, &base_instructions)
else {
return;
};
+1
View File
@@ -105,6 +105,7 @@ pub(super) async fn spawn_review_thread(
sub_id: review_turn_id.clone(),
trace_id: current_span_trace_id(),
realtime_active: parent_turn_context.realtime_active,
inline_instructions: parent_turn_context.inline_instructions,
config: per_turn_config,
auth_manager: auth_manager_for_context,
model_info: model_info.clone(),
@@ -87,6 +87,7 @@ async fn record_initial_history_resumed_bare_turn_context_does_not_hydrate_previ
let previous_model = "previous-rollout-model";
let previous_context_item = TurnContextItem {
turn_id: Some(turn_context.sub_id.clone()),
inline_instructions: false,
#[allow(deprecated)]
cwd: turn_context.cwd.clone(),
workspace_roots: None,
@@ -128,6 +129,7 @@ async fn record_initial_history_resumed_hydrates_previous_turn_settings_from_lif
let previous_model = "previous-rollout-model";
let mut previous_context_item = TurnContextItem {
turn_id: Some(turn_context.sub_id.clone()),
inline_instructions: false,
#[allow(deprecated)]
cwd: turn_context.cwd.clone(),
workspace_roots: None,
@@ -1002,6 +1004,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
let previous_model = "previous-rollout-model";
let previous_context_item = TurnContextItem {
turn_id: Some(turn_context.sub_id.clone()),
inline_instructions: false,
#[allow(deprecated)]
cwd: turn_context.cwd.clone(),
workspace_roots: None,
@@ -1088,6 +1091,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
.expect("serialize seeded reference context item"),
serde_json::to_value(Some(TurnContextItem {
turn_id: Some(turn_context.sub_id.clone()),
inline_instructions: false,
#[allow(deprecated)]
cwd: turn_context.cwd.clone(),
workspace_roots: None,
@@ -1119,6 +1123,7 @@ async fn record_initial_history_resumed_aborted_turn_without_id_clears_active_tu
let previous_model = "previous-rollout-model";
let previous_context_item = TurnContextItem {
turn_id: Some(turn_context.sub_id.clone()),
inline_instructions: false,
#[allow(deprecated)]
cwd: turn_context.cwd.clone(),
workspace_roots: None,
@@ -1245,6 +1250,7 @@ async fn record_initial_history_resumed_unmatched_abort_preserves_active_turn_fo
let unmatched_abort_turn_id = "other-turn".to_string();
let current_context_item = TurnContextItem {
turn_id: Some(current_turn_id.clone()),
inline_instructions: false,
#[allow(deprecated)]
cwd: turn_context.cwd.clone(),
workspace_roots: None,
@@ -1366,6 +1372,7 @@ async fn record_initial_history_resumed_trailing_incomplete_turn_compaction_clea
let previous_model = "previous-rollout-model";
let previous_context_item = TurnContextItem {
turn_id: Some(turn_context.sub_id.clone()),
inline_instructions: false,
#[allow(deprecated)]
cwd: turn_context.cwd.clone(),
workspace_roots: None,
@@ -1533,6 +1540,7 @@ async fn record_initial_history_resumed_replaced_incomplete_compacted_turn_clear
let previous_model = "previous-rollout-model";
let previous_context_item = TurnContextItem {
turn_id: Some(turn_context.sub_id.clone()),
inline_instructions: false,
#[allow(deprecated)]
cwd: turn_context.cwd.clone(),
workspace_roots: None,
+53 -1
View File
@@ -1894,6 +1894,56 @@ async fn record_initial_history_new_defers_initial_context_until_first_turn() {
assert_eq!(session.previous_turn_settings().await, None);
}
#[tokio::test]
async fn build_initial_context_inlines_base_instructions_for_new_turn_contexts() {
let (session, mut turn_context) = make_session_and_context().await;
let base_instructions = "INLINE_BASE_INSTRUCTIONS_MARKER".to_string();
session
.state
.lock()
.await
.session_configuration
.base_instructions = base_instructions.clone();
assert!(turn_context.inline_instructions);
let initial_context = session.build_initial_context(&turn_context).await;
assert!(
developer_input_texts(&initial_context)
.iter()
.any(|text| text.contains(&base_instructions))
);
turn_context.inline_instructions = false;
let legacy_initial_context = session.build_initial_context(&turn_context).await;
assert!(
developer_input_texts(&legacy_initial_context)
.iter()
.all(|text| !text.contains(&base_instructions))
);
}
#[tokio::test]
async fn new_turn_context_inherits_inline_instructions_from_reference_context() {
let (session, turn_context) = make_session_and_context().await;
assert!(turn_context.inline_instructions);
let mut legacy_context_item = turn_context.to_turn_context_item();
legacy_context_item.inline_instructions = false;
session
.state
.lock()
.await
.set_reference_context_item(Some(legacy_context_item));
let resumed_turn_context = session.new_default_turn().await;
assert!(!resumed_turn_context.inline_instructions);
assert!(
!resumed_turn_context
.to_turn_context_item()
.inline_instructions
);
}
fn session_meta_item(
thread_id: ThreadId,
multi_agent_version: Option<MultiAgentVersion>,
@@ -2021,7 +2071,8 @@ async fn record_initial_history_seeds_token_info_from_rollout() {
#[tokio::test]
async fn recompute_token_usage_uses_session_base_instructions() {
let (session, turn_context) = make_session_and_context().await;
let (session, mut turn_context) = make_session_and_context().await;
turn_context.inline_instructions = false;
let override_instructions = "SESSION_OVERRIDE_INSTRUCTIONS_ONLY".repeat(120);
{
@@ -2726,6 +2777,7 @@ async fn record_initial_history_forked_hydrates_previous_turn_settings() {
let previous_model = "forked-rollout-model";
let previous_context_item = TurnContextItem {
turn_id: Some(turn_context.sub_id.clone()),
inline_instructions: false,
#[allow(deprecated)]
cwd: turn_context.cwd.clone(),
workspace_roots: None,
+1 -1
View File
@@ -1071,7 +1071,7 @@ pub(crate) fn build_prompt(
input,
tools: router.model_visible_specs(),
parallel_tool_calls: turn_context.model_info.supports_parallel_tool_calls,
base_instructions,
base_instructions: (!turn_context.inline_instructions).then_some(base_instructions),
output_schema: turn_context.final_output_json_schema.clone(),
output_schema_strict: !crate::guardian::is_guardian_reviewer_source(
&turn_context.session_source,
@@ -104,6 +104,7 @@ pub struct TurnContext {
pub(crate) sub_id: String,
pub(crate) trace_id: Option<String>,
pub(crate) realtime_active: bool,
pub(crate) inline_instructions: bool,
pub config: Arc<Config>,
pub(crate) auth_manager: Option<Arc<AuthManager>>,
pub(crate) model_info: ModelInfo,
@@ -257,6 +258,7 @@ impl TurnContext {
sub_id: self.sub_id.clone(),
trace_id: self.trace_id.clone(),
realtime_active: self.realtime_active,
inline_instructions: self.inline_instructions,
config: Arc::new(config),
auth_manager: self.auth_manager.clone(),
model_info: model_info.clone(),
@@ -370,6 +372,7 @@ impl TurnContext {
let cwd = self.cwd.clone();
TurnContextItem {
turn_id: Some(self.sub_id.clone()),
inline_instructions: self.inline_instructions,
cwd,
workspace_roots: (!workspace_roots.is_empty()).then_some(workspace_roots),
current_date: self.current_date.clone(),
@@ -551,6 +554,7 @@ impl Session {
sub_id,
trace_id: current_span_trace_id(),
realtime_active: false,
inline_instructions: true,
config: per_turn_config,
auth_manager: auth_manager_for_context,
model_info,
@@ -782,6 +786,10 @@ impl Session {
sub_id,
skills_snapshot,
);
turn_context.inline_instructions = self
.reference_context_item()
.await
.is_none_or(|item| item.inline_instructions);
turn_context.realtime_active = self.conversation.running_state().await.is_some();
if let Some(final_schema) = final_output_json_schema {
+38 -5
View File
@@ -58,8 +58,34 @@ pub fn format_request_input_snapshot(
request: &ResponsesRequest,
options: &ContextSnapshotOptions,
) -> String {
let items = request.input();
format_response_items_snapshot(items.as_slice(), options)
let mut items = request.input();
let has_top_level_instructions = request
.body_json()
.get("instructions")
.and_then(Value::as_str)
.is_some();
// Inline model instructions can be large and vary by model. Keep their position visible while
// normalizing the text to a stable, compact placeholder.
if !has_top_level_instructions
&& let Some(text) = items
.iter_mut()
.find(|item| {
item.get("type").and_then(Value::as_str) == Some("message")
&& item.get("role").and_then(Value::as_str) == Some("developer")
})
.and_then(|item| item.get_mut("content"))
.and_then(Value::as_array_mut)
.and_then(|content| content.first_mut())
.and_then(|entry| entry.get_mut("text"))
{
*text = Value::String("<MODEL_INSTRUCTIONS>".to_string());
}
let input = format_response_items_snapshot(items.as_slice(), options);
if has_top_level_instructions {
format!("instructions:<MODEL_INSTRUCTIONS>\n{input}")
} else {
input
}
}
pub fn format_response_items_snapshot(items: &[Value], options: &ContextSnapshotOptions) -> String {
@@ -422,7 +448,7 @@ fn canonicalize_snapshot_text(text: &str) -> String {
{
return format!("<COMPACTION_SUMMARY>\n{summary}");
}
normalize_dynamic_snapshot_paths(text)
normalize_dynamic_snapshot_text(text)
}
fn is_capability_instruction_text(text: &str) -> bool {
@@ -431,14 +457,21 @@ fn is_capability_instruction_text(text: &str) -> bool {
|| text.starts_with(PLUGINS_INSTRUCTIONS_OPEN_TAG)
}
fn normalize_dynamic_snapshot_paths(text: &str) -> String {
fn normalize_dynamic_snapshot_text(text: &str) -> String {
static SYSTEM_SKILL_PATH_RE: OnceLock<Regex> = OnceLock::new();
static WALL_TIME_RE: OnceLock<Regex> = OnceLock::new();
let system_skill_path_re = SYSTEM_SKILL_PATH_RE.get_or_init(|| {
Regex::new(r"/[^)\n]*/skills/\.system/([^/\n]+)/SKILL\.md")
.expect("system skill path regex should compile")
});
system_skill_path_re
let text = system_skill_path_re
.replace_all(text, "<SYSTEM_SKILLS_ROOT>/$1/SKILL.md")
.into_owned();
let wall_time_re = WALL_TIME_RE.get_or_init(|| {
Regex::new(r"Wall time: [0-9.]+ seconds").expect("wall time regex should compile")
});
wall_time_re
.replace_all(&text, "Wall time: <DURATION>")
.into_owned()
}
+3 -3
View File
@@ -201,9 +201,9 @@ async fn remote_model_override_uses_catalog_model_for_strict_auto_review() -> Re
.into_iter()
.find(|request| {
request.body_contains_text("auto-review-model-override.txt")
&& request
.instructions_text()
.starts_with("You are judging one planned coding-agent action.")
&& request.message_input_texts("developer").iter().any(|text| {
text.starts_with("You are judging one planned coding-agent action.")
})
})
.expect("expected Guardian request for apply_patch");
assert_eq!(
+3 -13
View File
@@ -344,14 +344,9 @@ async fn exec_cli_applies_model_instructions_file() {
assert!(output.status.success());
// Inspect the captured request and verify our custom base instructions were
// included in the `instructions` field.
// included in developer input.
let request = resp_mock.single_request();
let body = request.body_json();
let instructions = body
.get("instructions")
.and_then(|v| v.as_str())
.unwrap_or_default()
.to_string();
let instructions = request.message_input_texts("developer").join("\n\n");
assert!(
instructions.contains(marker),
"instructions did not contain custom marker; got: {instructions}"
@@ -414,12 +409,7 @@ async fn exec_cli_profile_applies_model_instructions_file() {
assert!(output.status.success());
let request = resp_mock.single_request();
let body = request.body_json();
let instructions = body
.get("instructions")
.and_then(|v| v.as_str())
.unwrap_or_default()
.to_string();
let instructions = request.message_input_texts("developer").join("\n\n");
assert!(
instructions.contains(marker),
"instructions did not contain profile marker; got: {instructions}"
+19 -24
View File
@@ -1199,7 +1199,7 @@ async fn send_provider_auth_request(server: &MockServer, auth: ModelProviderAuth
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn includes_base_instructions_override_in_request() {
async fn inlines_base_instructions_override_in_request() {
skip_if_no_network!();
// Mock server
let server = MockServer::start().await;
@@ -1238,12 +1238,14 @@ async fn includes_base_instructions_override_in_request() {
let request = resp_mock.single_request();
let request_body = request.body_json();
let developer_messages = request.message_input_texts("developer");
assert!(request_body.get("instructions").is_none());
assert!(
request_body["instructions"]
.as_str()
.unwrap()
.contains("test instructions")
developer_messages
.iter()
.any(|text| text.contains("test instructions")),
"expected base instructions in developer input, got {developer_messages:?}"
);
}
@@ -1464,16 +1466,13 @@ async fn includes_user_instructions_message_in_request() {
let request = resp_mock.single_request();
let request_body = request.body_json();
assert!(
!request_body["instructions"]
.as_str()
.unwrap()
.contains("be nice")
);
assert!(request_body.get("instructions").is_none());
assert_message_role(&request_body["input"][0], "developer");
let permissions_text = request_body["input"][0]["content"][0]["text"]
.as_str()
.expect("invalid permissions message content");
let developer_texts = request.message_input_texts("developer");
let permissions_text = developer_texts
.iter()
.find(|text| text.contains("`sandbox_mode`"))
.expect("permissions message content");
assert!(
permissions_text.contains("`sandbox_mode`"),
"expected permissions message to mention sandbox_mode, got {permissions_text:?}"
@@ -2650,17 +2649,13 @@ async fn includes_developer_instructions_message_in_request() {
let request = resp_mock.single_request();
let request_body = request.body_json();
let permissions_text = request_body["input"][0]["content"][0]["text"]
.as_str()
.expect("invalid permissions message content");
assert!(
!request_body["instructions"]
.as_str()
.unwrap()
.contains("be nice")
);
assert!(request_body.get("instructions").is_none());
assert_message_role(&request_body["input"][0], "developer");
let developer_texts = request.message_input_texts("developer");
let permissions_text = developer_texts
.iter()
.find(|text| text.contains("`sandbox_mode`"))
.expect("permissions message content");
assert!(
permissions_text.contains("`sandbox_mode`"),
"expected permissions message to mention sandbox_mode, got {permissions_text:?}"
@@ -2081,9 +2081,9 @@ fn prompt_with_input(input: Vec<ResponseItem>) -> Prompt {
fn prompt_with_input_and_instructions(input: Vec<ResponseItem>, instructions: &str) -> Prompt {
let mut prompt = prompt_with_input(input);
prompt.base_instructions = BaseInstructions {
prompt.base_instructions = Some(BaseInstructions {
text: instructions.to_string(),
};
});
prompt
}
+24 -25
View File
@@ -547,11 +547,13 @@ async fn summarize_context_three_requests_and_instructions() {
let body3 = requests[2].body_json();
// Manual compact should keep the baseline developer instructions.
let instr1 = body1.get("instructions").and_then(|v| v.as_str()).unwrap();
let instr2 = body2.get("instructions").and_then(|v| v.as_str()).unwrap();
assert_eq!(
instr1, instr2,
"manual compact should keep the standard developer instructions"
assert!(body1.get("instructions").is_none());
assert!(body2.get("instructions").is_none());
let developer_texts1 = requests[0].message_input_texts("developer");
let developer_texts2 = requests[1].message_input_texts("developer");
assert!(
developer_texts2.starts_with(&developer_texts1),
"manual compact should keep the standard developer instructions: {developer_texts2:?}"
);
// The summarization request should include the injected user input marker.
@@ -1206,18 +1208,20 @@ async fn multiple_auto_compact_per_task_runs_after_token_limit_hit() {
return None;
}
let text = value
.get("content")
.and_then(|content| content.as_array())
.and_then(|content| content.first())
.and_then(|item| item.get("text"))
.and_then(|text| text.as_str());
// Ignore cached prefix messages (project docs + permissions) since they are not
// relevant to compaction behavior and can change as bundled prompts evolve.
let role = value.get("role").and_then(|role| role.as_str());
if role == Some("developer")
&& text.is_some_and(|text| text.contains("`sandbox_mode`"))
&& value
.get("content")
.and_then(|content| content.as_array())
.is_some_and(|content| {
content.iter().any(|item| {
item.get("text")
.and_then(|text| text.as_str())
.is_some_and(|text| text.contains("`sandbox_mode`"))
})
})
{
return None;
}
@@ -1713,18 +1717,13 @@ async fn auto_compact_runs_after_token_limit_hit() {
let body_first = requests[0].body_json();
let body_auto = requests[auto_compact_index].body_json();
let body_follow_up = requests[follow_up_index].body_json();
let instructions = body_auto
.get("instructions")
.and_then(|v| v.as_str())
.unwrap_or_default();
let baseline_instructions = body_first
.get("instructions")
.and_then(|v| v.as_str())
.unwrap_or_default()
.to_string();
assert_eq!(
instructions, baseline_instructions,
"auto compact should keep the standard developer instructions",
assert!(body_first.get("instructions").is_none());
assert!(body_auto.get("instructions").is_none());
let baseline_developer_texts = requests[0].message_input_texts("developer");
let auto_developer_texts = requests[auto_compact_index].message_input_texts("developer");
assert!(
auto_developer_texts.starts_with(&baseline_developer_texts),
"auto compact should keep the standard developer instructions: {auto_developer_texts:?}",
);
let input_auto = body_auto.get("input").and_then(|v| v.as_array()).unwrap();
+33 -9
View File
@@ -61,8 +61,12 @@ fn estimate_compact_input_tokens(request: &responses::ResponsesRequest) -> i64 {
}
fn estimate_compact_payload_tokens(request: &responses::ResponsesRequest) -> i64 {
estimate_compact_input_tokens(request)
.saturating_add(approx_token_count(&request.instructions_text()))
let body = request.body_json();
let instructions = body
.get("instructions")
.and_then(Value::as_str)
.unwrap_or_default();
estimate_compact_input_tokens(request).saturating_add(approx_token_count(instructions))
}
fn assert_tools_payload_does_not_defer(body: &Value) {
@@ -1986,7 +1990,17 @@ async fn remote_compact_trim_estimate_uses_session_base_instructions() -> Result
}),
)
.await?;
let baseline_codex = baseline_harness.test().codex.clone();
let baseline_test = baseline_harness.test();
let baseline_base_instructions = baseline_test
.thread_manager
.get_models_manager()
.get_model_info(
&baseline_test.session_configured.model,
&baseline_test.config.to_models_manager_config(),
)
.await
.base_instructions;
let baseline_codex = baseline_test.codex.clone();
responses::mount_sse_sequence(
baseline_harness.server(),
@@ -2071,12 +2085,13 @@ async fn remote_compact_trim_estimate_uses_session_base_instructions() -> Result
let override_base_instructions = format!(
"{}\nREMOTE_BASE_INSTRUCTIONS_OVERRIDE {}",
baseline_compact_request.instructions_text(),
baseline_base_instructions,
"x".repeat(8_000)
);
let override_context_window = baseline_payload_tokens.saturating_add(500);
let pretrim_override_estimate =
baseline_input_tokens.saturating_add(approx_token_count(&override_base_instructions));
let pretrim_override_estimate = baseline_input_tokens
.saturating_sub(approx_token_count(&baseline_base_instructions))
.saturating_add(approx_token_count(&override_base_instructions));
assert!(
pretrim_override_estimate > override_context_window,
"expected override instructions to push pre-trim estimate past the context window"
@@ -2165,9 +2180,18 @@ async fn remote_compact_trim_estimate_uses_session_base_instructions() -> Result
.await;
let override_compact_request = override_compact_mock.single_request();
assert_eq!(
override_compact_request.instructions_text(),
override_base_instructions
assert!(
override_compact_request
.body_json()
.get("instructions")
.is_none()
);
let override_developer_texts = override_compact_request.message_input_texts("developer");
assert!(
override_developer_texts
.iter()
.any(|text| text.contains(&override_base_instructions)),
"expected override instructions in developer input, got {override_developer_texts:?}"
);
assert!(
override_compact_request.has_function_call(override_retained_call_id),
@@ -317,8 +317,9 @@ async fn apps_default_prompt_with_auto_review_routes_actual_mcp_approval_to_guar
.into_iter()
.find(|request| {
request
.instructions_text()
.starts_with("You are judging one planned coding-agent action.")
.message_input_texts("developer")
.iter()
.any(|text| text.starts_with("You are judging one planned coding-agent action."))
})
.expect("expected a Guardian request for the app MCP approval");
assert!(guardian_request.body_contains_text("calendar_create_event"));
@@ -6,9 +6,12 @@ use anyhow::Result;
use codex_config::types::Personality;
use codex_features::Feature;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::ResponseItem;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::Op;
use codex_protocol::protocol::RolloutItem;
use codex_protocol::protocol::RolloutLine;
use codex_protocol::user_input::UserInput;
use core_test_support::PathBufExt;
use core_test_support::context_snapshot;
@@ -374,6 +377,40 @@ async fn snapshot_model_visible_layout_resume_with_personality_change() -> Resul
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
let initial_request = initial_mock.single_request();
// Rewrite the seed turn into the legacy representation: no persisted compatibility flag and
// no model instructions in history. The resumed request should therefore use the top-level
// `instructions` field.
let mut removed_inline_instructions = false;
let mut found_turn_context = false;
let legacy_rollout = fs::read_to_string(&rollout_path)?
.lines()
.map(|line| -> Result<String> {
let mut line: RolloutLine = serde_json::from_str(line)?;
match &mut line.item {
RolloutItem::TurnContext(turn_context) => {
turn_context.inline_instructions = false;
found_turn_context = true;
}
RolloutItem::ResponseItem(ResponseItem::Message { role, content, .. })
if role == "developer" && !removed_inline_instructions =>
{
assert!(!content.is_empty(), "developer message should have content");
content.remove(0);
removed_inline_instructions = true;
}
_ => {}
}
Ok(serde_json::to_string(&line)?)
})
.collect::<Result<Vec<_>>>()?
.join("\n");
assert!(found_turn_context, "rollout should contain a turn context");
assert!(
removed_inline_instructions,
"rollout should contain inline model instructions"
);
fs::write(&rollout_path, format!("{legacy_rollout}\n"))?;
let resumed_mock = mount_sse_once(
&server,
sse(vec![
@@ -437,7 +474,7 @@ async fn snapshot_model_visible_layout_resume_with_personality_change() -> Resul
insta::assert_snapshot!(
"model_visible_layout_resume_with_personality_change",
format_labeled_requests_snapshot(
"First post-resume turn where resumed config model differs from rollout and personality changes.",
"First post-legacy-resume turn where resumed config model differs from rollout and personality changes.",
&[
("Last Request Before Resume", &initial_request),
("First Request After Resume", &resumed_request),
+4 -4
View File
@@ -194,7 +194,7 @@ async fn config_personality_some_sets_instructions_template() -> anyhow::Result<
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
let request = resp_mock.single_request();
let instructions_text = request.instructions_text();
let instructions_text = request.message_input_texts("developer").join("\n\n");
assert!(
instructions_text.contains(LOCAL_FRIENDLY_TEMPLATE),
@@ -241,7 +241,7 @@ async fn config_personality_none_sends_no_personality() -> anyhow::Result<()> {
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
let request = resp_mock.single_request();
let instructions_text = request.instructions_text();
let instructions_text = request.message_input_texts("developer").join("\n\n");
assert!(
!instructions_text.contains(LOCAL_FRIENDLY_TEMPLATE),
"expected no friendly personality template, got: {instructions_text:?}"
@@ -294,7 +294,7 @@ async fn default_personality_is_pragmatic_without_config_toml() -> anyhow::Resul
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
let request = resp_mock.single_request();
let instructions_text = request.instructions_text();
let instructions_text = request.message_input_texts("developer").join("\n\n");
assert!(
instructions_text.contains(LOCAL_PRAGMATIC_TEMPLATE),
"expected default friendly template, got: {instructions_text:?}"
@@ -636,7 +636,7 @@ async fn remote_model_friendly_personality_instructions_with_feature() -> anyhow
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
let request = resp_mock.single_request();
let instructions_text = request.instructions_text();
let instructions_text = request.message_input_texts("developer").join("\n\n");
assert!(
instructions_text.contains(friendly_personality_message),
+31 -24
View File
@@ -110,7 +110,6 @@ fn normalize_newlines(text: &str) -> String {
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> {
skip_if_no_network!(Ok(()));
use pretty_assertions::assert_eq;
let server = start_mock_server().await;
let req1 = mount_sse_once(
@@ -198,7 +197,8 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> {
"tool_search",
"web_search",
]);
let body0 = req1.single_request().body_json();
let request0 = req1.single_request();
let body0 = request0.body_json();
let expected_instructions = if expected_tools_names.contains(&"apply_patch") {
base_instructions
@@ -206,16 +206,25 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> {
[base_instructions, APPLY_PATCH_TOOL_INSTRUCTIONS.to_string()].join("\n")
};
assert_eq!(
body0["instructions"],
serde_json::json!(expected_instructions),
assert!(body0.get("instructions").is_none());
let developer_texts0 = request0.message_input_texts("developer");
assert!(
developer_texts0
.iter()
.any(|text| text.starts_with(&expected_instructions)),
"expected model instructions in developer input, got {developer_texts0:?}"
);
assert_tool_names(&body0, &expected_tools_names);
let body1 = req2.single_request().body_json();
assert_eq!(
body1["instructions"],
serde_json::json!(expected_instructions),
let request1 = req2.single_request();
let body1 = request1.body_json();
assert!(body1.get("instructions").is_none());
let developer_texts1 = request1.message_input_texts("developer");
assert!(
developer_texts1
.iter()
.any(|text| text.starts_with(&expected_instructions)),
"expected model instructions in developer input, got {developer_texts1:?}"
);
assert_tool_names(&body1, &expected_tools_names);
@@ -280,19 +289,19 @@ async fn gpt_5_tools_without_apply_patch_append_apply_patch_instructions() -> an
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
let body0 = req1.single_request().body_json();
let instructions0 = body0["instructions"]
.as_str()
.expect("instructions should be a string");
let request0 = req1.single_request();
assert!(request0.body_json().get("instructions").is_none());
let developer_texts0 = request0.message_input_texts("developer");
let instructions0 = developer_texts0.first().expect("developer instructions");
assert!(
instructions0.contains("You are"),
"expected non-empty instructions"
);
let body1 = req2.single_request().body_json();
let instructions1 = body1["instructions"]
.as_str()
.expect("instructions should be a string");
let request1 = req2.single_request();
assert!(request1.body_json().get("instructions").is_none());
let developer_texts1 = request1.message_input_texts("developer");
let instructions1 = developer_texts1.first().expect("developer instructions");
assert_eq!(
normalize_newlines(instructions1),
normalize_newlines(instructions0)
@@ -647,15 +656,13 @@ async fn override_before_first_turn_emits_environment_context() -> anyhow::Resul
let permissions_texts: Vec<&str> = input
.iter()
.filter_map(|msg| {
let role = msg["role"].as_str()?;
if role != "developer" {
return None;
}
.filter(|msg| msg["role"].as_str() == Some("developer"))
.flat_map(|msg| {
msg["content"]
.as_array()
.and_then(|content| content.first())
.and_then(|item| item["text"].as_str())
.into_iter()
.flatten()
.filter_map(|item| item["text"].as_str())
})
.collect();
assert!(
+9 -3
View File
@@ -826,9 +826,15 @@ async fn remote_models_apply_remote_base_instructions() -> Result<()> {
let base_model_info = models_manager
.get_model_info("gpt-5.2", &config.to_models_manager_config())
.await;
let body = response_mock.single_request().body_json();
let instructions = body["instructions"].as_str().unwrap();
assert_eq!(instructions, base_model_info.base_instructions);
let request = response_mock.single_request();
assert!(request.body_json().get("instructions").is_none());
let developer_texts = request.message_input_texts("developer");
assert!(
developer_texts
.iter()
.any(|text| text.contains(&base_model_info.base_instructions)),
"expected model instructions in developer input, got {developer_texts:?}"
);
Ok(())
}
+7 -8
View File
@@ -274,12 +274,9 @@ async fn resume_switches_models_preserves_base_instructions() -> Result<()> {
.await?;
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
let initial_body = initial_mock.single_request().body_json();
let initial_instructions = initial_body
.get("instructions")
.and_then(|v| v.as_str())
.unwrap_or_default()
.to_string();
let initial_request = initial_mock.single_request();
assert!(initial_request.body_json().get("instructions").is_none());
let initial_developer_texts = initial_request.message_input_texts("developer");
let resumed_mock = mount_sse_sequence(
&server,
@@ -342,8 +339,9 @@ async fn resume_switches_models_preserves_base_instructions() -> Result<()> {
assert_eq!(requests.len(), 2, "expected two resumed requests");
let first_resumed = &requests[0];
assert_eq!(first_resumed.instructions_text(), initial_instructions);
assert!(first_resumed.body_json().get("instructions").is_none());
let first_developer_texts = first_resumed.message_input_texts("developer");
assert!(first_developer_texts.starts_with(&initial_developer_texts));
let first_model_switch_count = first_developer_texts
.iter()
.filter(|text| text.contains("<model_switch>"))
@@ -354,8 +352,9 @@ async fn resume_switches_models_preserves_base_instructions() -> Result<()> {
);
let second_resumed = &requests[1];
assert_eq!(second_resumed.instructions_text(), initial_instructions);
assert!(second_resumed.body_json().get("instructions").is_none());
let second_developer_texts = second_resumed.message_input_texts("developer");
assert!(second_developer_texts.starts_with(&initial_developer_texts));
let second_model_switch_count = second_developer_texts
.iter()
.filter(|text| text.contains("<model_switch>"))
@@ -27,6 +27,7 @@ fn resume_history(
let turn_id = "resume-warning-seed-turn".to_string();
let turn_ctx = TurnContextItem {
turn_id: Some(turn_id.clone()),
inline_instructions: false,
cwd: config.cwd.clone(),
workspace_roots: None,
current_date: None,
+9 -3
View File
@@ -627,9 +627,15 @@ async fn review_input_isolated_from_parent_history() {
"user message should only contain the raw review prompt"
);
// Ensure the REVIEW_PROMPT rubric is sent via instructions.
let instructions = body["instructions"].as_str().expect("instructions string");
assert_eq!(instructions, REVIEW_PROMPT);
// Ensure the REVIEW_PROMPT rubric is sent via developer input.
assert!(body.get("instructions").is_none());
let developer_texts = request.message_input_texts("developer");
assert!(
developer_texts
.iter()
.any(|text| text.contains(REVIEW_PROMPT)),
"expected review instructions in developer input, got {developer_texts:?}"
);
// Also verify that a user interruption note was recorded in the rollout.
let path = codex.rollout_path().expect("rollout path");
@@ -1,11 +1,13 @@
---
source: core/tests/suite/additional_context.rs
expression: "context_snapshot::format_labeled_requests_snapshot(\"additional context is inserted before the user turn input.\",\n&[(\"Request\", &request)], &additional_context_snapshot_options(),)"
expression: "context_snapshot::format_labeled_requests_snapshot(\"additional context is inserted before the user turn input.\",\n&[(\"Request\", &request)],\n&ContextSnapshotOptions::default().strip_capability_instructions().render_mode(ContextSnapshotRenderMode::KindWithTextPrefix\n{ max_chars: 160 }),)"
---
Scenario: additional context is inserted before the user turn input.
## Request
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
00:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
01:message/developer:<automation_info>run one</automation_info>
02:message/user:<external_browser_info>tab one</external_browser_info>
03:message/user:inspect the active tab
@@ -5,7 +5,9 @@ expression: "format_labeled_requests_snapshot(\"Manual /compact with prior user
Scenario: Manual /compact with prior user history compacts existing history and the follow-up turn includes the compact summary plus new user message.
## Local Compaction Request
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
00:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:first manual turn
03:message/assistant:FIRST_REPLY
@@ -14,6 +16,8 @@ Scenario: Manual /compact with prior user history compacts existing history and
## Local Post-Compaction History Layout
00:message/user:first manual turn
01:message/user:<COMPACTION_SUMMARY>\nFIRST_MANUAL_SUMMARY
02:message/developer:<PERMISSIONS_INSTRUCTIONS>
02:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
03:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
04:message/user:second manual turn
@@ -9,6 +9,8 @@ Scenario: Manual /compact with no prior user turn currently still issues a compa
## Local Post-Compaction History Layout
00:message/user:<COMPACTION_SUMMARY>\nMANUAL_EMPTY_SUMMARY
01:message/developer:<PERMISSIONS_INSTRUCTIONS>
01:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
02:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
03:message/user:AFTER_MANUAL_EMPTY_COMPACT
@@ -5,7 +5,9 @@ expression: "format_labeled_requests_snapshot(\"True mid-turn continuation compa
Scenario: True mid-turn continuation compaction after tool output: compact request includes tool artifacts, and the continuation request includes the summary in the same turn.
## Local Compaction Request
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
00:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:function call limit push
03:function_call/test_tool
@@ -13,7 +15,9 @@ Scenario: True mid-turn continuation compaction after tool output: compact reque
05:message/user:<SUMMARIZATION_PROMPT>
## Local Post-Compaction History Layout
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
00:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:function call limit push
03:message/user:<COMPACTION_SUMMARY>\nAUTO_SUMMARY
@@ -5,12 +5,16 @@ expression: "format_labeled_requests_snapshot(\"Pre-sampling compaction on model
Scenario: Pre-sampling compaction on model switch to a smaller context window: current behavior compacts using prior-turn history only (incoming user message excluded), and the follow-up request carries compacted history plus the new user message.
## Initial Request (Previous Model)
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
00:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:before switch
## Pre-sampling Compaction Request
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
00:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:before switch
03:message/assistant:before switch
@@ -19,8 +23,9 @@ Scenario: Pre-sampling compaction on model switch to a smaller context window: c
## Post-Compaction Follow-up Request (Next Model)
00:message/user:before switch
01:message/user:<COMPACTION_SUMMARY>\nPRE_SAMPLING_SUMMARY
02:message/developer[2]:
[01] <model_switch>\nThe user was previously using a different model....
[02] <PERMISSIONS_INSTRUCTIONS>
02:message/developer[3]:
[01] <MODEL_INSTRUCTIONS>
[02] <model_switch>\nThe user was previously using a different model....
[03] <PERMISSIONS_INSTRUCTIONS>
03:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
04:message/user:after switch
@@ -5,7 +5,9 @@ expression: "format_labeled_requests_snapshot(\"Pre-turn auto-compaction context
Scenario: Pre-turn auto-compaction context-window failure: compaction request excludes the incoming user message and the turn errors.
## Local Compaction Request (Incoming User Excluded)
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
00:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:USER_ONE
03:message/assistant:FIRST_REPLY
@@ -5,7 +5,9 @@ expression: "format_labeled_requests_snapshot(\"Pre-turn auto-compaction with a
Scenario: Pre-turn auto-compaction with a context override emits the context diff in the compact request while the incoming user message is still excluded.
## Local Compaction Request
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
00:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:USER_ONE
03:message/assistant:FIRST_REPLY
@@ -17,7 +19,9 @@ Scenario: Pre-turn auto-compaction with a context override emits the context dif
00:message/user:USER_ONE
01:message/user:USER_TWO
02:message/user:<COMPACTION_SUMMARY>\nPRE_TURN_SUMMARY
03:message/developer:<PERMISSIONS_INSTRUCTIONS>
03:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
04:message/user:<ENVIRONMENT_CONTEXT:cwd=PRETURN_CONTEXT_DIFF_CWD>
05:message/user[2]:
[01] <input_image:detail,image_url>
@@ -5,12 +5,16 @@ expression: "format_labeled_requests_snapshot(\"Pre-turn compaction during model
Scenario: Pre-turn compaction during model switch (without pre-sampling model-switch compaction): current behavior strips incoming <model_switch> from the compact request and restores it in the post-compaction follow-up request.
## Initial Request (Previous Model)
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
00:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:BEFORE_SWITCH_USER
## Local Compaction Request
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
00:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:BEFORE_SWITCH_USER
03:message/assistant:BEFORE_SWITCH_REPLY
@@ -19,9 +23,10 @@ Scenario: Pre-turn compaction during model switch (without pre-sampling model-sw
## Local Post-Compaction History Layout
00:message/user:BEFORE_SWITCH_USER
01:message/user:<COMPACTION_SUMMARY>\nPRETURN_SWITCH_SUMMARY
02:message/developer[3]:
[01] <model_switch>\nThe user was previously using a different model....
[02] <PERMISSIONS_INSTRUCTIONS>
[03] <personality_spec> The user has requested a new communication st...
02:message/developer[4]:
[01] <MODEL_INSTRUCTIONS>
[02] <model_switch>\nThe user was previously using a different model....
[03] <PERMISSIONS_INSTRUCTIONS>
[04] <personality_spec> The user has requested a new communication st...
03:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
04:message/user:AFTER_SWITCH_USER
@@ -5,17 +5,19 @@ expression: "format_labeled_requests_snapshot(\"After remote manual /compact and
Scenario: After remote manual /compact and resume, the first resumed turn rebuilds history from the compaction item and restates realtime-end instructions from reconstructed previous-turn settings.
## Remote Compaction Request
00:message/developer[2]:
[01] <PERMISSIONS_INSTRUCTIONS>
[02] <realtime_conversation>\nRealtime conversation started.\n\nYou a...
00:message/developer[3]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
[03] <realtime_conversation>\nRealtime conversation started.\n\nYou a...
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:USER_ONE
03:message/assistant:REMOTE_FIRST_REPLY
## Remote Post-Resume History Layout
00:compaction:encrypted=true
01:message/developer[2]:
[01] <PERMISSIONS_INSTRUCTIONS>
[02] <realtime_conversation>\nRealtime conversation ended.\n\nSubsequ...
01:message/developer[3]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
[03] <realtime_conversation>\nRealtime conversation ended.\n\nSubsequ...
02:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
03:message/user:USER_TWO
@@ -5,17 +5,19 @@ expression: "format_labeled_requests_snapshot(\"Remote manual /compact while rea
Scenario: Remote manual /compact while realtime remains active: the next regular turn restates realtime-start instructions after compaction clears the baseline.
## Remote Compaction Request
00:message/developer[2]:
[01] <PERMISSIONS_INSTRUCTIONS>
[02] <realtime_conversation>\nRealtime conversation started.\n\nYou a...
00:message/developer[3]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
[03] <realtime_conversation>\nRealtime conversation started.\n\nYou a...
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:USER_ONE
03:message/assistant:REMOTE_FIRST_REPLY
## Remote Post-Compaction History Layout
00:compaction:encrypted=true
01:message/developer[2]:
[01] <PERMISSIONS_INSTRUCTIONS>
[02] <realtime_conversation>\nRealtime conversation started.\n\nYou a...
01:message/developer[3]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
[03] <realtime_conversation>\nRealtime conversation started.\n\nYou a...
02:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
03:message/user:USER_TWO
@@ -5,13 +5,17 @@ expression: "format_labeled_requests_snapshot(\"Remote manual /compact where rem
Scenario: Remote manual /compact where remote compact output is compaction-only: follow-up layout uses the returned compaction item plus new user message.
## Remote Compaction Request
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
00:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:hello remote compact
03:message/assistant:FIRST_REMOTE_REPLY
## Remote Post-Compaction History Layout
00:compaction:encrypted=true
01:message/developer:<PERMISSIONS_INSTRUCTIONS>
01:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
02:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
03:message/user:after compact
@@ -5,6 +5,8 @@ expression: "format_labeled_requests_snapshot(\"Remote manual /compact with no p
Scenario: Remote manual /compact with no prior user turn skips the remote compact request; the follow-up turn carries canonical context and new user message.
## Remote Post-Compaction History Layout
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
00:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:USER_ONE
@@ -5,9 +5,10 @@ expression: "format_labeled_requests_snapshot(\"Remote mid-turn continuation com
Scenario: Remote mid-turn continuation compaction after realtime was closed before the turn: the initial second-turn request emits realtime-end instructions, but the continuation request does not restate them after compaction because the current turn already established the inactive baseline.
## Second Turn Initial Request
00:message/developer[2]:
[01] <PERMISSIONS_INSTRUCTIONS>
[02] <realtime_conversation>\nRealtime conversation started.\n\nYou a...
00:message/developer[3]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
[03] <realtime_conversation>\nRealtime conversation started.\n\nYou a...
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:SETUP_USER
03:message/assistant:REMOTE_SETUP_REPLY
@@ -15,9 +16,10 @@ Scenario: Remote mid-turn continuation compaction after realtime was closed befo
05:message/user:USER_TWO
## Remote Compaction Request
00:message/developer[2]:
[01] <PERMISSIONS_INSTRUCTIONS>
[02] <realtime_conversation>\nRealtime conversation started.\n\nYou a...
00:message/developer[3]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
[03] <realtime_conversation>\nRealtime conversation started.\n\nYou a...
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:SETUP_USER
03:message/assistant:REMOTE_SETUP_REPLY
@@ -27,6 +29,8 @@ Scenario: Remote mid-turn continuation compaction after realtime was closed befo
07:function_call_output:unsupported call: test_tool
## Remote Post-Compaction History Layout
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
00:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:compaction:encrypted=true
@@ -7,10 +7,19 @@ Scenario: After a prior manual /compact produced an older remote compaction item
## Remote Compaction Request
00:message/user:USER_ONE
01:compaction:encrypted=true
02:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
03:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
04:message/user:USER_TWO
05:function_call/shell_command
06:function_call_output:Exit code: 0\nWall time: <DURATION>\nOutput:\nmulti-summary\n
## Second Turn Request (After Compaction)
00:message/user:USER_ONE
01:compaction:encrypted=true
02:message/developer:<PERMISSIONS_INSTRUCTIONS>
02:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
03:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
04:message/user:USER_TWO
@@ -5,14 +5,18 @@ expression: "format_labeled_requests_snapshot(\"Remote mid-turn continuation com
Scenario: Remote mid-turn continuation compaction after tool output: compact request includes tool artifacts and the follow-up request includes the returned compaction item.
## Remote Compaction Request
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
00:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:USER_ONE
03:function_call/test_tool
04:function_call_output:unsupported call: test_tool
## Remote Post-Compaction History Layout
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
00:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:USER_ONE
03:compaction:encrypted=true
@@ -5,13 +5,17 @@ expression: "format_labeled_requests_snapshot(\"Remote mid-turn compaction where
Scenario: Remote mid-turn compaction where compact output has only a compaction item: continuation layout reinjects context before that compaction item.
## Remote Compaction Request
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
00:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:USER_ONE
03:function_call/test_tool
04:function_call_output:unsupported call: test_tool
## Remote Post-Compaction History Layout
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
00:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:compaction:encrypted=true
@@ -5,7 +5,9 @@ expression: "format_labeled_requests_snapshot(\"Remote pre-turn auto-compaction
Scenario: Remote pre-turn auto-compaction context-window failure: compaction request excludes the incoming user message and the turn errors.
## Remote Compaction Request (Incoming User Excluded)
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
00:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:USER_ONE
03:message/assistant:REMOTE_FIRST_REPLY
@@ -5,7 +5,9 @@ expression: "format_labeled_requests_snapshot(\"Remote pre-turn auto-compaction
Scenario: Remote pre-turn auto-compaction parse failure: compaction request excludes the incoming user message and the turn stops.
## Remote Compaction Request (Incoming User Excluded)
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
00:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:turn that exceeds token threshold
03:message/assistant:initial turn complete
@@ -5,7 +5,9 @@ expression: "format_labeled_requests_snapshot(\"Remote pre-turn auto-compaction
Scenario: Remote pre-turn auto-compaction with a context override emits the context diff in the compact request while excluding the incoming user message.
## Remote Compaction Request
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
00:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:USER_ONE
03:message/assistant:REMOTE_FIRST_REPLY
@@ -16,6 +18,8 @@ Scenario: Remote pre-turn auto-compaction with a context override emits the cont
00:message/user:USER_ONE
01:message/user:USER_TWO
02:compaction:encrypted=true
03:message/developer:<PERMISSIONS_INSTRUCTIONS>
03:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
04:message/user:<ENVIRONMENT_CONTEXT:cwd=PRETURN_CONTEXT_DIFF_CWD>
05:message/user:USER_THREE
@@ -5,17 +5,19 @@ expression: "format_labeled_requests_snapshot(\"Remote pre-turn auto-compaction
Scenario: Remote pre-turn auto-compaction after realtime was closed between turns: the follow-up request emits realtime-end instructions from previous-turn settings even though compaction cleared the reference baseline.
## Remote Compaction Request
00:message/developer[2]:
[01] <PERMISSIONS_INSTRUCTIONS>
[02] <realtime_conversation>\nRealtime conversation started.\n\nYou a...
00:message/developer[3]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
[03] <realtime_conversation>\nRealtime conversation started.\n\nYou a...
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:USER_ONE
03:message/assistant:REMOTE_FIRST_REPLY
## Remote Post-Compaction History Layout
00:compaction:encrypted=true
01:message/developer[2]:
[01] <PERMISSIONS_INSTRUCTIONS>
[02] <realtime_conversation>\nRealtime conversation ended.\n\nSubsequ...
01:message/developer[3]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
[03] <realtime_conversation>\nRealtime conversation ended.\n\nSubsequ...
02:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
03:message/user:USER_TWO
@@ -5,17 +5,19 @@ expression: "format_labeled_requests_snapshot(\"Remote pre-turn auto-compaction
Scenario: Remote pre-turn auto-compaction while realtime remains active: compaction clears the reference baseline, so the follow-up request restates realtime-start instructions.
## Remote Compaction Request
00:message/developer[2]:
[01] <PERMISSIONS_INSTRUCTIONS>
[02] <realtime_conversation>\nRealtime conversation started.\n\nYou a...
00:message/developer[3]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
[03] <realtime_conversation>\nRealtime conversation started.\n\nYou a...
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:USER_ONE
03:message/assistant:REMOTE_FIRST_REPLY
## Remote Post-Compaction History Layout
00:compaction:encrypted=true
01:message/developer[2]:
[01] <PERMISSIONS_INSTRUCTIONS>
[02] <realtime_conversation>\nRealtime conversation started.\n\nYou a...
01:message/developer[3]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
[03] <realtime_conversation>\nRealtime conversation started.\n\nYou a...
02:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
03:message/user:USER_TWO
@@ -5,12 +5,16 @@ expression: "format_labeled_requests_snapshot(\"Remote pre-turn compaction durin
Scenario: Remote pre-turn compaction during model switch currently excludes incoming user input, strips incoming <model_switch> from the compact request payload, and restores it in the post-compaction follow-up request.
## Initial Request (Previous Model)
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
00:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:BEFORE_SWITCH_USER
## Remote Compaction Request
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
00:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:BEFORE_SWITCH_USER
03:message/assistant:BEFORE_SWITCH_REPLY
@@ -18,9 +22,10 @@ Scenario: Remote pre-turn compaction during model switch currently excludes inco
## Remote Post-Compaction History Layout
00:message/user:BEFORE_SWITCH_USER
01:compaction:encrypted=true
02:message/developer[3]:
[01] <model_switch>\nThe user was previously using a different model....
[02] <PERMISSIONS_INSTRUCTIONS>
[03] <personality_spec> The user has requested a new communication st...
02:message/developer[4]:
[01] <MODEL_INSTRUCTIONS>
[02] <model_switch>\nThe user was previously using a different model....
[03] <PERMISSIONS_INSTRUCTIONS>
[04] <personality_spec> The user has requested a new communication st...
03:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
04:message/user:AFTER_SWITCH_USER
@@ -5,7 +5,9 @@ expression: "context_snapshot::format_labeled_requests_snapshot(\"rollback trims
Scenario: rollback trims pre-turn override context updates before the follow-up request
## rolled-back turn request
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
00:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:turn 1 user
03:message/assistant:turn 1 assistant
@@ -14,7 +16,9 @@ Scenario: rollback trims pre-turn override context updates before the follow-up
06:message/user:turn 2 user
## follow-up request after rollback
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
00:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:turn 1 user
03:message/assistant:turn 1 assistant
@@ -5,7 +5,9 @@ expression: "context_snapshot::format_labeled_requests_snapshot(\"rollback past
Scenario: rollback past compaction replay after rollback
## compaction request
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
00:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:hello world
03:message/assistant:FIRST_REPLY
@@ -14,13 +16,17 @@ Scenario: rollback past compaction replay after rollback
## before rollback
00:message/user:hello world
01:message/user:<COMPACTION_SUMMARY>\nSUMMARY_ONLY_CONTEXT
02:message/developer:<PERMISSIONS_INSTRUCTIONS>
02:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
03:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
04:message/user:EDITED_AFTER_COMPACT
## after rollback
00:message/user:hello world
01:message/user:<COMPACTION_SUMMARY>\nSUMMARY_ONLY_CONTEXT
02:message/developer:<PERMISSIONS_INSTRUCTIONS>
02:message/developer[2]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
03:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
04:message/user:AFTER_ROLLBACK
@@ -5,16 +5,18 @@ expression: "format_labeled_requests_snapshot(\"Second turn changes cwd to a dir
Scenario: Second turn changes cwd to a directory with different AGENTS.md; current behavior does not emit refreshed AGENTS instructions.
## First Request (agents_one)
00:message/developer[2]:
[01] <PERMISSIONS_INSTRUCTIONS>
[02] <SKILLS_INSTRUCTIONS>
00:message/developer[3]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
[03] <SKILLS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:first turn in agents_one
## Second Request (agents_two cwd)
00:message/developer[2]:
[01] <PERMISSIONS_INSTRUCTIONS>
[02] <SKILLS_INSTRUCTIONS>
00:message/developer[3]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
[03] <SKILLS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:first turn in agents_one
03:message/assistant:turn one complete
@@ -5,16 +5,18 @@ expression: "format_labeled_requests_snapshot(\"First post-resume turn where pre
Scenario: First post-resume turn where pre-turn override sets model to rollout model; no model-switch update should appear.
## Last Request Before Resume
00:message/developer[2]:
[01] <PERMISSIONS_INSTRUCTIONS>
[02] <SKILLS_INSTRUCTIONS>
00:message/developer[3]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
[03] <SKILLS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:seed resume history
## First Request After Resume + Override
00:message/developer[2]:
[01] <PERMISSIONS_INSTRUCTIONS>
[02] <SKILLS_INSTRUCTIONS>
00:message/developer[3]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
[03] <SKILLS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:seed resume history
03:message/assistant:recorded before resume
@@ -1,17 +1,19 @@
---
source: core/tests/suite/model_visible_layout.rs
expression: "format_labeled_requests_snapshot(\"First post-resume turn where resumed config model differs from rollout and personality changes.\",\n&[(\"Last Request Before Resume\", &initial_request),\n(\"First Request After Resume\", &resumed_request),])"
expression: "format_labeled_requests_snapshot(\"First post-legacy-resume turn where resumed config model differs from rollout and personality changes.\",\n&[(\"Last Request Before Resume\", &initial_request),\n(\"First Request After Resume\", &resumed_request),])"
---
Scenario: First post-resume turn where resumed config model differs from rollout and personality changes.
Scenario: First post-legacy-resume turn where resumed config model differs from rollout and personality changes.
## Last Request Before Resume
00:message/developer[2]:
[01] <PERMISSIONS_INSTRUCTIONS>
[02] <SKILLS_INSTRUCTIONS>
00:message/developer[3]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
[03] <SKILLS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:seed resume history
## First Request After Resume
instructions:<MODEL_INSTRUCTIONS>
00:message/developer[2]:
[01] <PERMISSIONS_INSTRUCTIONS>
[02] <SKILLS_INSTRUCTIONS>
@@ -5,16 +5,18 @@ expression: "format_labeled_requests_snapshot(\"Second turn changes cwd, approva
Scenario: Second turn changes cwd, approval policy, and personality while keeping model constant.
## First Request (Baseline)
00:message/developer[2]:
[01] <PERMISSIONS_INSTRUCTIONS>
[02] <SKILLS_INSTRUCTIONS>
00:message/developer[3]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
[03] <SKILLS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:first turn
## Second Request (Turn Overrides)
00:message/developer[2]:
[01] <PERMISSIONS_INSTRUCTIONS>
[02] <SKILLS_INSTRUCTIONS>
00:message/developer[3]:
[01] <MODEL_INSTRUCTIONS>
[02] <PERMISSIONS_INSTRUCTIONS>
[03] <SKILLS_INSTRUCTIONS>
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
02:message/user:first turn
03:message/assistant:turn one complete
+7
View File
@@ -3035,6 +3035,10 @@ pub struct TurnContextNetworkItem {
pub struct TurnContextItem {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub turn_id: Option<String>,
/// Whether base model instructions are present in the model input as a developer message.
/// Missing values come from legacy rollouts that used the top-level Responses API field.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub inline_instructions: bool,
pub cwd: AbsolutePathBuf,
/// Effective workspace roots used to materialize symbolic
/// `:workspace_roots` filesystem permissions in `permission_profile`.
@@ -5372,6 +5376,7 @@ mod tests {
assert_eq!(item.network, None);
assert_eq!(item.file_system_sandbox_policy, None);
assert_eq!(item.comp_hash, None);
assert!(!item.inline_instructions);
Ok(())
}
@@ -5437,6 +5442,7 @@ mod tests {
fn turn_context_item_serializes_network_when_present() -> Result<()> {
let item = TurnContextItem {
turn_id: None,
inline_instructions: true,
cwd: test_path_buf("/tmp").abs(),
workspace_roots: None,
current_date: None,
@@ -5468,6 +5474,7 @@ mod tests {
};
let value = serde_json::to_value(item)?;
assert_eq!(value["inline_instructions"], true);
assert_eq!(
value["network"],
json!({
+1
View File
@@ -1137,6 +1137,7 @@ async fn resume_candidate_matches_cwd_reads_latest_turn_context() -> std::io::Re
timestamp: "2025-01-03T13:00:01Z".to_string(),
item: RolloutItem::TurnContext(TurnContextItem {
turn_id: Some("turn-1".to_string()),
inline_instructions: false,
cwd: serde_json::from_value(serde_json::json!(&latest_cwd))
.expect("absolute latest cwd"),
workspace_roots: None,
+4
View File
@@ -349,6 +349,7 @@ mod tests {
&mut metadata,
&RolloutItem::TurnContext(TurnContextItem {
turn_id: Some("turn-1".to_string()),
inline_instructions: false,
cwd: serde_json::from_value(serde_json::json!(
std::env::current_dir()
.expect("current directory")
@@ -394,6 +395,7 @@ mod tests {
&mut metadata,
&RolloutItem::TurnContext(TurnContextItem {
turn_id: Some("turn-1".to_string()),
inline_instructions: false,
cwd: serde_json::from_value(serde_json::json!(
std::env::current_dir()
.expect("current directory")
@@ -439,6 +441,7 @@ mod tests {
&mut metadata,
&RolloutItem::TurnContext(TurnContextItem {
turn_id: Some("turn-1".to_string()),
inline_instructions: false,
cwd: serde_json::from_value(serde_json::json!(&fallback_cwd))
.expect("absolute fallback cwd"),
workspace_roots: None,
@@ -473,6 +476,7 @@ mod tests {
&mut metadata,
&RolloutItem::TurnContext(TurnContextItem {
turn_id: Some("turn-1".to_string()),
inline_instructions: false,
cwd: serde_json::from_value(serde_json::json!(
std::env::current_dir()
.expect("current directory")