mirror of
https://github.com/openai/codex.git
synced 2026-10-29 19:08:43 +00:00
inline model instructions in initial context
This commit is contained in:
@@ -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()),
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
|
||||
+4
-2
@@ -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
|
||||
|
||||
+6
-2
@@ -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
|
||||
|
||||
+3
-1
@@ -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
|
||||
|
||||
+6
-2
@@ -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
|
||||
|
||||
+10
-5
@@ -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
|
||||
|
||||
+3
-1
@@ -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
|
||||
|
||||
+6
-2
@@ -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>
|
||||
|
||||
+11
-6
@@ -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
|
||||
|
||||
+8
-6
@@ -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
|
||||
|
||||
+8
-6
@@ -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
|
||||
|
||||
+6
-2
@@ -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
|
||||
|
||||
+3
-1
@@ -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
|
||||
|
||||
+11
-7
@@ -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
|
||||
|
||||
+10
-1
@@ -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
|
||||
|
||||
+6
-2
@@ -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
|
||||
|
||||
+6
-2
@@ -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
|
||||
|
||||
+3
-1
@@ -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
|
||||
|
||||
+3
-1
@@ -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
|
||||
|
||||
+6
-2
@@ -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
|
||||
|
||||
+8
-6
@@ -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
|
||||
|
||||
+8
-6
@@ -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
|
||||
|
||||
+11
-6
@@ -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
|
||||
|
||||
+6
-2
@@ -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
|
||||
|
||||
+9
-3
@@ -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
|
||||
|
||||
+8
-6
@@ -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
|
||||
|
||||
+8
-6
@@ -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
|
||||
|
||||
+7
-5
@@ -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>
|
||||
|
||||
+8
-6
@@ -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
|
||||
|
||||
@@ -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!({
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user