Fix compaction context reinjection and model baselines (#12252)

## Summary
- move regular-turn context diff/full-context persistence into
`run_turn` so pre-turn compaction runs before incoming context updates
are recorded
- after successful pre-turn compaction, rely on a cleared
`reference_context_item` to trigger full context reinjection on the
follow-up regular turn (manual `/compact` keeps replacement history
summary-only and also clears the baseline)
- preserve `<model_switch>` when full context is reinjected, and inject
it *before* the rest of the full-context items
- scope `reference_context_item` and `previous_model` to regular user
turns only so standalone tasks (`/compact`, shell, review, undo) cannot
suppress future reinjection or `<model_switch>` behavior
- make context-diff persistence + `reference_context_item` updates
explicit in the regular-turn path, with clearer docs/comments around the
invariant
- stop persisting local `/compact` `RolloutItem::TurnContext` snapshots
(only regular turns persist `TurnContextItem` now)
- simplify resume/fork previous-model/reference-baseline hydration by
looking up the last surviving turn context from rollout lifecycle
events, including rollback and compaction-crossing handling
- remove the legacy fallback that guessed from bare `TurnContext`
rollouts without lifecycle events
- update compaction/remote-compaction/model-visible snapshots and
compact test assertions (including remote compaction mock response
shape)

## Why
We were persisting incoming context items before spawning the regular
turn task, which let pre-turn compaction requests accidentally include
incoming context diffs without the new user message. Fixing that exposed
follow-on baseline issues around `/compact`, resume/fork, and standalone
tasks that could cause duplicate context injection or suppress
`<model_switch>` instructions.

This PR re-centers the invariants around regular turns:
- regular turns persist model-visible context diffs/full reinjection and
update the `reference_context_item`
- standalone tasks do not advance those regular-turn baselines
- compaction clears the baseline when replacement history may have
stripped the referenced context diffs

## Follow-ups (TODOs left in code)
- `TODO(ccunningham)`: fix rollback/backtracking baseline handling more
comprehensively
- `TODO(ccunningham)`: include pending incoming context items in
pre-turn compaction threshold estimation
- `TODO(ccunningham)`: inject updated personality spec alongside
`<model_switch>` so some model-switch paths can avoid forced full
reinjection
- `TODO(ccunningham)`: review task turn lifecycle
(`TurnStarted`/`TurnComplete`) behavior and emit task-start context
diffs for task types that should have them (excluding `/compact`)

## Validation
- `just fmt`
- CI should cover the updated compaction/resume/model-visible snapshot
expectations and rollout-hydration behavior
- I did **not** rerun the full local test suite after the latest
resume-lookup / rollout-persistence simplifications
This commit is contained in:
Charley Cunningham
2026-02-20 23:13:08 -08:00
committed by GitHub
parent 264fc444b6
commit bb0ac5be70
31 changed files with 1289 additions and 1206 deletions

View File

@@ -867,7 +867,7 @@ pub async fn mount_compact_json_once(server: &MockServer, body: serde_json::Valu
/// Mount a `/responses/compact` mock that mirrors the default remote compaction shape:
/// keep user+developer messages from the request, drop assistant/tool artifacts, and append one
/// summary user message.
/// compaction item carrying the provided summary text.
pub async fn mount_compact_user_history_with_summary_once(
server: &MockServer,
summary_text: &str,
@@ -911,6 +911,9 @@ pub async fn mount_compact_user_history_with_summary_sequence(
.cloned()
.unwrap_or_default()
.into_iter()
// TODO(ccunningham): Update this mock to match future compaction model behavior:
// return user/developer/assistant messages since the last compaction item, then
// append a single newest compaction item.
// Match current remote compaction behavior: keep user/developer messages and
// omit assistant/tool history entries.
.filter(|item| {
@@ -921,11 +924,10 @@ pub async fn mount_compact_user_history_with_summary_sequence(
)
})
.collect::<Vec<Value>>();
// Append the synthetic summary message as the newest user item.
// Append a synthetic compaction item as the newest item.
output.push(serde_json::json!({
"type": "message",
"role": "user",
"content": [{"type": "input_text", "text": summary_text}],
"type": "compaction",
"encrypted_content": summary_text,
}));
ResponseTemplate::new(200)
.insert_header("content-type", "application/json")

View File

@@ -363,7 +363,7 @@ async fn summarize_context_three_requests_and_instructions() {
codex.submit(Op::Shutdown).await.unwrap();
wait_for_event(&codex, |ev| matches!(ev, EventMsg::ShutdownComplete)).await;
// Verify rollout contains APITurn entries for each API call and a Compacted entry.
// Verify rollout contains regular sampling TurnContext entries and a Compacted entry.
println!("rollout path: {}", rollout_path.display());
let text = std::fs::read_to_string(&rollout_path).unwrap_or_else(|e| {
panic!(
@@ -371,7 +371,7 @@ async fn summarize_context_three_requests_and_instructions() {
rollout_path.display()
)
});
let mut api_turn_count = 0usize;
let mut regular_turn_context_count = 0usize;
let mut saw_compacted_summary = false;
for line in text.lines() {
let trimmed = line.trim();
@@ -383,7 +383,7 @@ async fn summarize_context_three_requests_and_instructions() {
};
match entry.item {
RolloutItem::TurnContext(_) => {
api_turn_count += 1;
regular_turn_context_count += 1;
}
RolloutItem::Compacted(ci) => {
if ci.message == expected_summary_message {
@@ -395,8 +395,8 @@ async fn summarize_context_three_requests_and_instructions() {
}
assert!(
api_turn_count == 3,
"expected three APITurn entries in rollout"
regular_turn_context_count == 2,
"expected two regular sampling TurnContext entries in rollout"
);
assert!(
saw_compacted_summary,
@@ -2414,26 +2414,36 @@ async fn manual_compact_twice_preserves_latest_user_messages() {
first_request_user_texts[first_turn_user_index], first_user_message,
"first turn request should end with the submitted user message"
);
let seeded_user_prefix = &first_request_user_texts[..first_turn_user_index];
let initial_seeded_user_prefix = &first_request_user_texts[..first_turn_user_index];
let final_request_user_texts = requests
.last()
.unwrap_or_else(|| panic!("final turn request missing for {final_user_message}"))
.message_input_texts("user");
assert!(
final_request_user_texts
.as_slice()
.starts_with(seeded_user_prefix),
"final request should start with seeded user prefix from first request: {seeded_user_prefix:?}"
!initial_seeded_user_prefix.is_empty(),
"first turn should include seeded user prefix before the submitted user message"
);
let final_output = &final_request_user_texts[seeded_user_prefix.len()..];
let expected = vec![
let (final_request_last_user_text, final_request_before_last_user) = final_request_user_texts
.split_last()
.unwrap_or_else(|| panic!("final turn request missing user messages"));
assert_eq!(
final_request_last_user_text, final_user_message,
"final turn request should end with the submitted user message"
);
let history_before_seeded_prefix = final_request_before_last_user
.strip_suffix(initial_seeded_user_prefix)
.unwrap_or_else(|| {
panic!(
"final request should end with the seeded user prefix from the first request: {initial_seeded_user_prefix:?}"
)
});
let expected_history = vec![
first_user_message.to_string(),
second_user_message.to_string(),
expected_second_summary,
final_user_message.to_string(),
];
assert_eq!(final_output, expected.as_slice());
assert_eq!(history_before_seeded_prefix, expected_history.as_slice());
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]

View File

@@ -93,12 +93,9 @@ async fn remote_compact_replaces_history_for_followups() -> Result<()> {
)
.await;
let compacted_history = vec![
responses::user_message_item("REMOTE_COMPACTED_SUMMARY"),
ResponseItem::Compaction {
encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(),
},
];
let compacted_history = vec![ResponseItem::Compaction {
encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(),
}];
let compact_mock = responses::mount_compact_json_once(
harness.server(),
serde_json::json!({ "output": compacted_history.clone() }),
@@ -159,7 +156,7 @@ async fn remote_compact_replaces_history_for_followups() -> Result<()> {
let follow_up_request = response_requests.last().expect("follow-up request missing");
let follow_up_body = follow_up_request.body_json().to_string();
assert!(
follow_up_body.contains("REMOTE_COMPACTED_SUMMARY"),
follow_up_body.contains("\"type\":\"compaction\""),
"expected follow-up request to use compacted history"
);
assert!(
@@ -178,7 +175,7 @@ async fn remote_compact_replaces_history_for_followups() -> Result<()> {
insta::assert_snapshot!(
"remote_manual_compact_with_history_shapes",
format_labeled_requests_snapshot(
"Remote manual /compact where remote compact output is summary-only: follow-up layout uses returned summary plus new user message.",
"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", &compact_request),
("Remote Post-Compaction History Layout", follow_up_request),
@@ -958,7 +955,6 @@ async fn remote_compact_persists_replacement_history_in_rollout() -> Result<()>
.await;
let compacted_history = vec![
responses::user_message_item("COMPACTED_USER_SUMMARY"),
ResponseItem::Compaction {
encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(),
},
@@ -1012,17 +1008,6 @@ async fn remote_compact_persists_replacement_history_in_rollout() -> Result<()>
&& compacted.message.is_empty()
&& let Some(replacement_history) = compacted.replacement_history.as_ref()
{
let has_compacted_user_summary = replacement_history.iter().any(|item| {
matches!(
item,
ResponseItem::Message { role, content, .. }
if role == "user"
&& content.iter().any(|part| matches!(
part,
ContentItem::InputText { text } if text == "COMPACTED_USER_SUMMARY"
))
)
});
let has_compaction_item = replacement_history.iter().any(|item| {
matches!(
item,
@@ -1054,7 +1039,7 @@ async fn remote_compact_persists_replacement_history_in_rollout() -> Result<()>
)
});
if has_compacted_user_summary && has_compaction_item && has_compacted_assistant_note {
if has_compaction_item && has_compacted_assistant_note {
assert!(
!has_permissions_developer_message,
"manual remote compact rollout replacement history should not inject permissions context"
@@ -1110,7 +1095,6 @@ async fn remote_compact_and_resume_refresh_stale_developer_instructions() -> Res
.await;
let compacted_history = vec![
responses::user_message_item("REMOTE_COMPACTED_SUMMARY"),
ResponseItem::Message {
id: None,
role: "developer".to_string(),
@@ -1196,8 +1180,8 @@ async fn remote_compact_and_resume_refresh_stale_developer_instructions() -> Res
"fresh developer instructions should be present after compaction"
);
assert!(
after_compact_body.contains("REMOTE_COMPACTED_SUMMARY"),
"compacted summary should be present after compaction"
after_compact_body.contains("ENCRYPTED_COMPACTION_SUMMARY"),
"compaction item should be present after compaction"
);
let after_resume_body = after_resume_request.body_json().to_string();
@@ -1210,8 +1194,8 @@ async fn remote_compact_and_resume_refresh_stale_developer_instructions() -> Res
"fresh developer instructions should be present after resume"
);
assert!(
after_resume_body.contains("REMOTE_COMPACTED_SUMMARY"),
"compacted summary should persist after resume"
after_resume_body.contains("ENCRYPTED_COMPACTION_SUMMARY"),
"compaction item should persist after resume"
);
Ok(())
@@ -1243,7 +1227,6 @@ async fn remote_compact_refreshes_stale_developer_instructions_without_resume()
.await;
let compacted_history = vec![
responses::user_message_item("REMOTE_COMPACTED_SUMMARY"),
ResponseItem::Message {
id: None,
role: "developer".to_string(),
@@ -1302,8 +1285,8 @@ async fn remote_compact_refreshes_stale_developer_instructions_without_resume()
"fresh developer instructions should be present after compaction"
);
assert!(
after_compact_body.contains("REMOTE_COMPACTED_SUMMARY"),
"compacted summary should be present after compaction"
after_compact_body.contains("ENCRYPTED_COMPACTION_SUMMARY"),
"compaction item should be present after compaction"
);
Ok(())
@@ -1706,7 +1689,7 @@ async fn snapshot_request_shape_remote_mid_turn_continuation_compaction() -> Res
insta::assert_snapshot!(
"remote_mid_turn_compaction_shapes",
format_labeled_requests_snapshot(
"Remote mid-turn continuation compaction after tool output: compact request includes tool artifacts and follow-up request includes the summary.",
"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", &compact_request),
("Remote Post-Compaction History Layout", &requests[1]),
@@ -1749,9 +1732,9 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_summary_only_reinject
)
.await;
let compacted_history = vec![responses::user_message_item(&summary_with_prefix(
"REMOTE_SUMMARY_ONLY",
))];
let compacted_history = vec![ResponseItem::Compaction {
encrypted_content: summary_with_prefix("REMOTE_SUMMARY_ONLY"),
}];
let compact_mock = responses::mount_compact_json_once(
harness.server(),
serde_json::json!({ "output": compacted_history }),
@@ -1786,7 +1769,7 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_summary_only_reinject
insta::assert_snapshot!(
"remote_mid_turn_compaction_summary_only_reinjects_context_shapes",
format_labeled_requests_snapshot(
"Remote mid-turn compaction where compact output has only summary user content: continuation layout reinjects canonical context before that summary.",
"Remote mid-turn compaction where compact output has only a compaction item: continuation layout reinjects context before that compaction item.",
&[
("Remote Compaction Request", &compact_request),
(
@@ -1893,13 +1876,13 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_multi_summary_reinjec
insta::assert_snapshot!(
"remote_mid_turn_compaction_multi_summary_reinjects_above_last_summary_shapes",
format_labeled_requests_snapshot(
"Remote mid-turn compaction after an earlier summary compaction: the older summary remains in model-visible history and round-trips into the next compact request.",
"After a prior manual /compact produced an older remote compaction item, the next turn hits remote auto-compaction before the next sampling request. The compact request carries forward that earlier compaction item, and the next sampling request shows the latest compaction item with context reinjected before USER_TWO.",
&[
("Remote Compaction Request", &compact_request),
(
"Second Turn Request (Before Mid-Turn Compaction)",
"Second Turn Request (After Compaction)",
&second_turn_request
),
("Remote Compaction Request", &compact_request),
]
)
);

View File

@@ -81,25 +81,32 @@ fn normalize_line_endings_str(text: &str) -> String {
}
}
fn extract_summary_message(request: &Value, summary_text: &str) -> Value {
fn extract_summary_user_text(request: &Value, summary_text: &str) -> String {
json_message_input_texts(request, "user")
.into_iter()
.find(|text| text.contains(summary_text))
.unwrap_or_else(|| panic!("expected summary message {summary_text}"))
}
fn json_message_input_texts(request: &Value, role: &str) -> Vec<String> {
request
.get("input")
.and_then(Value::as_array)
.and_then(|items| {
items.iter().find(|item| {
item.get("type").and_then(Value::as_str) == Some("message")
&& item.get("role").and_then(Value::as_str) == Some("user")
&& item
.get("content")
.and_then(Value::as_array)
.and_then(|arr| arr.first())
.and_then(|entry| entry.get("text"))
.and_then(Value::as_str)
.is_some_and(|text| text.contains(summary_text))
})
.into_iter()
.flatten()
.filter(|item| {
item.get("type").and_then(Value::as_str) == Some("message")
&& item.get("role").and_then(Value::as_str) == Some(role)
})
.cloned()
.unwrap_or_else(|| panic!("expected summary message {summary_text}"))
.filter_map(|item| {
item.get("content")
.and_then(Value::as_array)
.and_then(|content| content.first())
.and_then(|entry| entry.get("text"))
.and_then(Value::as_str)
.map(str::to_string)
})
.collect()
}
fn normalize_compact_prompts(requests: &mut [Value]) {
@@ -200,459 +207,88 @@ async fn compact_resume_and_fork_preserve_model_history_view() {
&fork_arr[..compact_arr.len()]
);
let expected_model = requests[0]["model"]
.as_str()
.unwrap_or_default()
.to_string();
let prompt = requests[0]["instructions"]
.as_str()
.unwrap_or_default()
.to_string();
let permissions_message = requests[0]["input"][0].clone();
let user_instructions = requests[0]["input"][1]["content"][0]["text"]
.as_str()
.unwrap_or_default()
.to_string();
let environment_context = requests[0]["input"][2]["content"][0]["text"]
.as_str()
.unwrap_or_default()
.to_string();
let tool_calls = json!(requests[0]["tools"].as_array());
let prompt_cache_key = requests[0]["prompt_cache_key"]
.as_str()
.unwrap_or_default()
.to_string();
let fork_prompt_cache_key = requests[requests.len() - 1]["prompt_cache_key"]
.as_str()
.unwrap_or_default()
.to_string();
let summary_after_compact = extract_summary_message(&requests[2], SUMMARY_TEXT);
let summary_after_resume = extract_summary_message(&requests[3], SUMMARY_TEXT);
let summary_after_fork = extract_summary_message(&requests[4], SUMMARY_TEXT);
let user_turn_1 = json!(
{
"model": expected_model,
"instructions": prompt,
"input": [
permissions_message,
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": user_instructions
}
]
},
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": environment_context
}
]
},
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "hello world"
}
]
let first_request_user_texts = json_message_input_texts(&requests[0], "user");
let first_turn_user_index = first_request_user_texts
.len()
.checked_sub(1)
.unwrap_or_else(|| panic!("first turn request missing user messages"));
assert_eq!(
first_request_user_texts[first_turn_user_index],
"hello world"
);
let seeded_user_prefix = &first_request_user_texts[..first_turn_user_index];
let summary_after_compact = extract_summary_user_text(&requests[2], SUMMARY_TEXT);
let summary_after_resume = extract_summary_user_text(&requests[3], SUMMARY_TEXT);
let summary_after_fork = extract_summary_user_text(&requests[4], SUMMARY_TEXT);
let mut expected_after_compact_user_texts =
vec!["hello world".to_string(), summary_after_compact];
expected_after_compact_user_texts.extend_from_slice(seeded_user_prefix);
expected_after_compact_user_texts.push("AFTER_COMPACT".to_string());
assert_eq!(
json_message_input_texts(&requests[2], "user"),
expected_after_compact_user_texts
);
let mut expected_after_resume_user_texts =
vec!["hello world".to_string(), summary_after_resume];
expected_after_resume_user_texts.extend_from_slice(seeded_user_prefix);
expected_after_resume_user_texts.push("AFTER_COMPACT".to_string());
let after_resume_user_texts = json_message_input_texts(&requests[3], "user");
let (after_resume_last, after_resume_prefix) = after_resume_user_texts
.split_last()
.unwrap_or_else(|| panic!("after-resume request missing user messages"));
assert_eq!(after_resume_last, "AFTER_RESUME");
assert!(
after_resume_prefix.starts_with(&expected_after_resume_user_texts),
"after-resume user texts should preserve compacted history prefix"
);
let after_resume_seeded_suffix = &after_resume_prefix[expected_after_resume_user_texts.len()..];
if seeded_user_prefix.is_empty() {
assert!(
after_resume_seeded_suffix.is_empty(),
"after-resume request should not append unexpected user prefix items"
);
} else {
let mut chunks = after_resume_seeded_suffix.chunks_exact(seeded_user_prefix.len());
assert!(
chunks.remainder().is_empty(),
"after-resume suffix should be whole seeded-prefix repeats"
);
for chunk in &mut chunks {
assert_eq!(chunk, seeded_user_prefix);
}
],
"tools": tool_calls,
"tool_choice": "auto",
"parallel_tool_calls": false,
"reasoning": {
"effort": "medium",
"summary": "auto"
},
"store": false,
"stream": true,
"include": [
"reasoning.encrypted_content"
],
"prompt_cache_key": prompt_cache_key
});
let compact_1 = json!(
{
"model": expected_model,
"instructions": prompt,
"input": [
permissions_message,
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": user_instructions
}
]
},
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": environment_context
}
]
},
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "hello world"
}
]
},
{
"type": "message",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "FIRST_REPLY"
}
]
},
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": SUMMARIZATION_PROMPT
}
]
}
let after_fork_user_texts = json_message_input_texts(&requests[4], "user");
let mut expected_after_fork_history_prefix =
vec!["hello world".to_string(), summary_after_fork];
expected_after_fork_history_prefix.extend_from_slice(seeded_user_prefix);
expected_after_fork_history_prefix.push("AFTER_COMPACT".to_string());
let (after_fork_last, after_fork_prefix) = after_fork_user_texts
.split_last()
.unwrap_or_else(|| panic!("after-fork request missing user messages"));
assert_eq!(after_fork_last, "AFTER_FORK");
assert!(
after_fork_prefix.starts_with(&expected_after_fork_history_prefix),
"after-fork user texts should preserve compacted user history prefix"
);
let after_fork_seeded_suffix = &after_fork_prefix[expected_after_fork_history_prefix.len()..];
if seeded_user_prefix.is_empty() {
assert!(
after_fork_seeded_suffix.is_empty(),
"after-fork request should not append unexpected user prefix items"
);
} else {
let mut chunks = after_fork_seeded_suffix.chunks_exact(seeded_user_prefix.len());
assert!(
chunks.remainder().is_empty(),
"after-fork suffix should be whole seeded-prefix repeats"
);
for chunk in &mut chunks {
assert_eq!(chunk, seeded_user_prefix);
}
],
"tools": [],
"tool_choice": "auto",
"parallel_tool_calls": false,
"reasoning": {
"effort": "medium",
"summary": "auto"
},
"store": false,
"stream": true,
"include": [
"reasoning.encrypted_content"
],
"prompt_cache_key": prompt_cache_key
});
let user_turn_2_after_compact = json!(
{
"model": expected_model,
"instructions": prompt,
"input": [
permissions_message,
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": user_instructions
}
]
},
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": environment_context
}
]
},
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "hello world"
}
]
},
summary_after_compact,
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "AFTER_COMPACT"
}
]
}
],
"tools": tool_calls,
"tool_choice": "auto",
"parallel_tool_calls": false,
"reasoning": {
"effort": "medium",
"summary": "auto"
},
"store": false,
"stream": true,
"include": [
"reasoning.encrypted_content"
],
"prompt_cache_key": prompt_cache_key
});
let usert_turn_3_after_resume = json!(
{
"model": expected_model,
"instructions": prompt,
"input": [
permissions_message,
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": user_instructions
}
]
},
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": environment_context
}
]
},
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "hello world"
}
]
},
summary_after_resume,
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "AFTER_COMPACT"
}
]
},
{
"type": "message",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "AFTER_COMPACT_REPLY"
}
]
},
permissions_message,
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": user_instructions
}
]
},
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": environment_context
}
]
},
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "AFTER_RESUME"
}
]
}
],
"tools": tool_calls,
"tool_choice": "auto",
"parallel_tool_calls": false,
"reasoning": {
"effort": "medium",
"summary": "auto"
},
"store": false,
"stream": true,
"include": [
"reasoning.encrypted_content"
],
"prompt_cache_key": prompt_cache_key
});
let user_turn_3_after_fork = json!(
{
"model": expected_model,
"instructions": prompt,
"input": [
permissions_message,
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": user_instructions
}
]
},
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": environment_context
}
]
},
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "hello world"
}
]
},
summary_after_fork,
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "AFTER_COMPACT"
}
]
},
{
"type": "message",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "AFTER_COMPACT_REPLY"
}
]
},
permissions_message,
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": user_instructions
}
]
},
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": environment_context
}
]
},
permissions_message,
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": user_instructions
}
]
},
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": environment_context
}
]
},
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "AFTER_FORK"
}
]
}
],
"tools": tool_calls,
"tool_choice": "auto",
"parallel_tool_calls": false,
"reasoning": {
"effort": "medium",
"summary": "auto"
},
"store": false,
"stream": true,
"include": [
"reasoning.encrypted_content"
],
"prompt_cache_key": fork_prompt_cache_key
});
let mut expected = json!([
user_turn_1,
compact_1,
user_turn_2_after_compact,
usert_turn_3_after_resume,
user_turn_3_after_fork
]);
normalize_line_endings(&mut expected);
if let Some(arr) = expected.as_array_mut() {
normalize_compact_prompts(arr);
}
assert_eq!(requests.len(), 5);
assert_eq!(json!(requests), expected);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
@@ -725,118 +361,47 @@ async fn compact_resume_after_second_compaction_preserves_history() {
compact_filtered.as_slice(),
&resume_filtered[..compact_filtered.len()]
);
// hard coded test
let prompt = requests[0]["instructions"]
.as_str()
.unwrap_or_default()
.to_string();
let permissions_message = requests[0]["input"][0].clone();
let user_instructions = requests[0]["input"][1]["content"][0]["text"]
.as_str()
.unwrap_or_default()
.to_string();
let environment_instructions = requests[0]["input"][2]["content"][0]["text"]
.as_str()
.unwrap_or_default()
.to_string();
// Build expected final request input: initial context + forked user message +
// compacted summary + post-compact user message + resumed user message.
let first_request_user_texts = json_message_input_texts(&requests[0], "user");
let first_turn_user_index = first_request_user_texts
.len()
.checked_sub(1)
.unwrap_or_else(|| panic!("first turn request missing user messages"));
assert_eq!(
first_request_user_texts[first_turn_user_index],
"hello world"
);
let seeded_user_prefix = &first_request_user_texts[..first_turn_user_index];
let summary_after_second_compact =
extract_summary_message(&requests[requests.len() - 3], SUMMARY_TEXT);
let mut expected = json!([
{
"instructions": prompt,
"input": [
permissions_message,
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": user_instructions
}
]
},
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": environment_instructions
}
]
},
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "AFTER_FORK"
}
]
},
summary_after_second_compact,
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "AFTER_COMPACT_2"
}
]
},
permissions_message,
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": user_instructions
}
]
},
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": environment_instructions
}
]
},
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "AFTER_SECOND_RESUME"
}
]
}
],
}
]);
normalize_line_endings(&mut expected);
let mut last_request_after_2_compacts = json!([{
"instructions": requests[requests.len() -1]["instructions"],
"input": requests[requests.len() -1]["input"],
}]);
if let Some(arr) = expected.as_array_mut() {
normalize_compact_prompts(arr);
extract_summary_user_text(&requests[requests.len() - 3], SUMMARY_TEXT);
let mut expected_after_second_compact_user_texts =
vec!["AFTER_FORK".to_string(), summary_after_second_compact];
expected_after_second_compact_user_texts.extend_from_slice(seeded_user_prefix);
expected_after_second_compact_user_texts.push("AFTER_COMPACT_2".to_string());
let final_user_texts = json_message_input_texts(&requests[requests.len() - 1], "user");
let (final_last, final_prefix) = final_user_texts
.split_last()
.unwrap_or_else(|| panic!("after-second-resume request missing user messages"));
assert_eq!(final_last, AFTER_SECOND_RESUME);
assert!(
final_prefix.starts_with(&expected_after_second_compact_user_texts),
"after-second-resume user texts should preserve post-compact user history prefix"
);
let final_seeded_suffix = &final_prefix[expected_after_second_compact_user_texts.len()..];
if seeded_user_prefix.is_empty() {
assert!(
final_seeded_suffix.is_empty(),
"after-second-resume request should not append unexpected user prefix items"
);
} else {
let mut chunks = final_seeded_suffix.chunks_exact(seeded_user_prefix.len());
assert!(
chunks.remainder().is_empty(),
"after-second-resume suffix should be whole seeded-prefix repeats"
);
for chunk in &mut chunks {
assert_eq!(chunk, seeded_user_prefix);
}
}
if let Some(arr) = last_request_after_2_compacts.as_array_mut() {
normalize_compact_prompts(arr);
}
assert_eq!(expected, last_request_after_2_compacts);
}
fn normalize_line_endings(value: &mut Value) {

View File

@@ -308,6 +308,8 @@ async fn snapshot_model_visible_layout_resume_with_personality_change() -> Resul
config.personality = Some(Personality::Pragmatic);
});
let resumed = resume_builder.resume(&server, home, rollout_path).await?;
let resume_override_cwd = resumed.cwd_path().join(PRETURN_CONTEXT_DIFF_CWD);
fs::create_dir_all(&resume_override_cwd)?;
resumed
.codex
.submit(Op::UserTurn {
@@ -316,7 +318,7 @@ async fn snapshot_model_visible_layout_resume_with_personality_change() -> Resul
text_elements: Vec::new(),
}],
final_output_json_schema: None,
cwd: resumed.cwd_path().to_path_buf(),
cwd: resume_override_cwd,
approval_policy: AskForApproval::Never,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
model: resumed.session_configured.model.clone(),
@@ -398,10 +400,12 @@ async fn snapshot_model_visible_layout_resume_override_matches_rollout_model() -
config.model = Some("gpt-5.2-codex".to_string());
});
let resumed = resume_builder.resume(&server, home, rollout_path).await?;
let resume_override_cwd = resumed.cwd_path().join(PRETURN_CONTEXT_DIFF_CWD);
fs::create_dir_all(&resume_override_cwd)?;
resumed
.codex
.submit(Op::OverrideTurnContext {
cwd: None,
cwd: Some(resume_override_cwd),
approval_policy: None,
sandbox_policy: None,
windows_sandbox_level: None,

View File

@@ -672,12 +672,7 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res
});
let expected_permissions_msg = body1["input"][0].clone();
let body1_input = body1["input"].as_array().expect("input array");
let expected_permissions_msg_2 = body2["input"][body1_input.len() + 1].clone();
assert_ne!(
expected_permissions_msg_2, expected_permissions_msg,
"expected updated permissions message after per-turn override"
);
let expected_model_switch_msg = body2["input"][body1_input.len() + 2].clone();
let expected_model_switch_msg = body2["input"][body1_input.len()].clone();
assert_eq!(
expected_model_switch_msg["role"].as_str(),
Some("developer")
@@ -688,10 +683,15 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res
.is_some_and(|text| text.contains("<model_switch>")),
"expected model switch message after model override: {expected_model_switch_msg:?}"
);
let expected_permissions_msg_2 = body2["input"][body1_input.len() + 2].clone();
assert_ne!(
expected_permissions_msg_2, expected_permissions_msg,
"expected updated permissions message after per-turn override"
);
let mut expected_body2 = body1_input.to_vec();
expected_body2.push(expected_model_switch_msg);
expected_body2.push(expected_env_msg_2);
expected_body2.push(expected_permissions_msg_2);
expected_body2.push(expected_model_switch_msg);
expected_body2.push(expected_user_message_2);
assert_eq!(body2["input"], serde_json::Value::Array(expected_body2));
@@ -900,12 +900,7 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu
assert_eq!(body1["input"], expected_input_1);
let body1_input = body1["input"].as_array().expect("input array");
let expected_permissions_msg_2 = body2["input"][body1_input.len()].clone();
assert_ne!(
expected_permissions_msg_2, expected_permissions_msg,
"expected updated permissions message after policy change"
);
let expected_model_switch_msg = body2["input"][body1_input.len() + 1].clone();
let expected_model_switch_msg = body2["input"][body1_input.len()].clone();
assert_eq!(
expected_model_switch_msg["role"].as_str(),
Some("developer")
@@ -916,14 +911,19 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu
.is_some_and(|text| text.contains("<model_switch>")),
"expected model switch message after model override: {expected_model_switch_msg:?}"
);
let expected_permissions_msg_2 = body2["input"][body1_input.len() + 1].clone();
assert_ne!(
expected_permissions_msg_2, expected_permissions_msg,
"expected updated permissions message after policy change"
);
let expected_user_message_2 = text_user_input("hello 2".to_string());
let expected_input_2 = serde_json::Value::Array(vec![
expected_permissions_msg,
expected_ui_msg,
expected_env_msg_1,
expected_user_message_1,
expected_permissions_msg_2,
expected_model_switch_msg,
expected_permissions_msg_2,
expected_user_message_2,
]);
assert_eq!(body2["input"], expected_input_2);

View File

@@ -6,9 +6,13 @@ use codex_core::protocol::EventMsg;
use codex_core::protocol::InitialHistory;
use codex_core::protocol::ResumedHistory;
use codex_core::protocol::RolloutItem;
use codex_core::protocol::TurnCompleteEvent;
use codex_core::protocol::TurnContextItem;
use codex_core::protocol::TurnStartedEvent;
use codex_core::protocol::UserMessageEvent;
use codex_core::protocol::WarningEvent;
use codex_protocol::ThreadId;
use codex_protocol::config_types::ModeKind;
use core::time::Duration;
use core_test_support::load_default_config_for_test;
use core_test_support::wait_for_event;
@@ -19,8 +23,9 @@ fn resume_history(
previous_model: &str,
rollout_path: &std::path::Path,
) -> InitialHistory {
let turn_id = "resume-warning-seed-turn".to_string();
let turn_ctx = TurnContextItem {
turn_id: None,
turn_id: Some(turn_id.clone()),
cwd: config.cwd.clone(),
approval_policy: config.permissions.approval_policy.value(),
sandbox_policy: config.permissions.sandbox_policy.get().clone(),
@@ -38,7 +43,24 @@ fn resume_history(
InitialHistory::Resumed(ResumedHistory {
conversation_id: ThreadId::default(),
history: vec![RolloutItem::TurnContext(turn_ctx)],
history: vec![
RolloutItem::EventMsg(EventMsg::TurnStarted(TurnStartedEvent {
turn_id: turn_id.clone(),
model_context_window: None,
collaboration_mode_kind: ModeKind::Default,
})),
RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent {
message: "seed".to_string(),
images: None,
local_images: vec![],
text_elements: vec![],
})),
RolloutItem::TurnContext(turn_ctx),
RolloutItem::EventMsg(EventMsg::TurnComplete(TurnCompleteEvent {
turn_id,
last_agent_message: None,
})),
],
rollout_path: rollout_path.to_path_buf(),
})
}

View File

@@ -13,9 +13,9 @@ Scenario: Manual /compact with prior user history compacts existing history and
05:message/user:<SUMMARIZATION_PROMPT>
## Local Post-Compaction History Layout
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
01:message/user:<AGENTS_MD>
02:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
03:message/user:first manual turn
04:message/user:<COMPACTION_SUMMARY>\nFIRST_MANUAL_SUMMARY
00:message/user:first manual turn
01:message/user:<COMPACTION_SUMMARY>\nFIRST_MANUAL_SUMMARY
02:message/developer:<PERMISSIONS_INSTRUCTIONS>
03:message/user:<AGENTS_MD>
04:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
05:message/user:second manual turn

View File

@@ -11,8 +11,8 @@ Scenario: Manual /compact with no prior user turn currently still issues a compa
03:message/user:<SUMMARIZATION_PROMPT>
## Local Post-Compaction History Layout
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
01:message/user:<AGENTS_MD>
02:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
03:message/user:<COMPACTION_SUMMARY>\nMANUAL_EMPTY_SUMMARY
00:message/user:<COMPACTION_SUMMARY>\nMANUAL_EMPTY_SUMMARY
01:message/developer:<PERMISSIONS_INSTRUCTIONS>
02:message/user:<AGENTS_MD>
03:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
04:message/user:AFTER_MANUAL_EMPTY_COMPACT

View File

@@ -1,6 +1,5 @@
---
source: core/tests/suite/compact.rs
assertion_line: 1773
expression: "format_labeled_requests_snapshot(\"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.\",\n&[(\"Initial Request (Previous Model)\", &requests[0]),\n(\"Pre-sampling Compaction Request\", &requests[1]),\n(\"Post-Compaction Follow-up Request (Next Model)\", &requests[2]),])"
---
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.
@@ -22,10 +21,10 @@ Scenario: Pre-sampling compaction on model switch to a smaller context window: c
06:message/user:<SUMMARIZATION_PROMPT>
## Post-Compaction Follow-up Request (Next Model)
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
01:message/user:<AGENTS_MD>
02:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
03:message/user:before switch
04:message/user:<COMPACTION_SUMMARY>\nPRE_SAMPLING_SUMMARY
05:message/developer:<model_switch>\nThe user was previously using a different model....
00:message/user:before switch
01:message/user:<COMPACTION_SUMMARY>\nPRE_SAMPLING_SUMMARY
02:message/developer:<model_switch>\nThe user was previously using a different model....
03:message/developer:<PERMISSIONS_INSTRUCTIONS>
04:message/user:<AGENTS_MD>
05:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
06:message/user:after switch

View File

@@ -12,14 +12,13 @@ Scenario: Pre-turn auto-compaction with a context override emits the context dif
04:message/assistant:FIRST_REPLY
05:message/user:USER_TWO
06:message/assistant:SECOND_REPLY
07:message/user:<ENVIRONMENT_CONTEXT:cwd=PRETURN_CONTEXT_DIFF_CWD>
08:message/user:<SUMMARIZATION_PROMPT>
07:message/user:<SUMMARIZATION_PROMPT>
## Local Post-Compaction History Layout
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
01:message/user:<AGENTS_MD>
02:message/user:<ENVIRONMENT_CONTEXT:cwd=PRETURN_CONTEXT_DIFF_CWD>
03:message/user:USER_ONE
04:message/user:USER_TWO
05:message/user:<COMPACTION_SUMMARY>\nPRE_TURN_SUMMARY
00:message/user:USER_ONE
01:message/user:USER_TWO
02:message/user:<COMPACTION_SUMMARY>\nPRE_TURN_SUMMARY
03:message/developer:<PERMISSIONS_INSTRUCTIONS>
04:message/user:<AGENTS_MD>
05:message/user:<ENVIRONMENT_CONTEXT:cwd=PRETURN_CONTEXT_DIFF_CWD>
06:message/user:<image> | <input_image:image_url> | </image> | USER_THREE

View File

@@ -1,6 +1,5 @@
---
source: core/tests/suite/compact.rs
assertion_line: 3152
expression: "format_labeled_requests_snapshot(\"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.\",\n&[(\"Initial Request (Previous Model)\", &requests[0]),\n(\"Local Compaction Request\", &requests[1]),\n(\"Local Post-Compaction History Layout\", &requests[2]),])"
---
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.
@@ -22,11 +21,11 @@ Scenario: Pre-turn compaction during model switch (without pre-sampling model-sw
06:message/user:<SUMMARIZATION_PROMPT>
## Local Post-Compaction History Layout
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
01:message/developer:<personality_spec> The user has requested a new communication st...
02:message/user:<AGENTS_MD>
03:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
04:message/user:BEFORE_SWITCH_USER
05:message/user:<COMPACTION_SUMMARY>\nPRETURN_SWITCH_SUMMARY
06:message/developer:<model_switch>\nThe user was previously using a different model....
00:message/user:BEFORE_SWITCH_USER
01:message/user:<COMPACTION_SUMMARY>\nPRETURN_SWITCH_SUMMARY
02:message/developer:<model_switch>\nThe user was previously using a different model....
03:message/developer:<PERMISSIONS_INSTRUCTIONS>
04:message/developer:<personality_spec> The user has requested a new communication st...
05:message/user:<AGENTS_MD>
06:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
07:message/user:AFTER_SWITCH_USER

View File

@@ -1,9 +1,8 @@
---
source: core/tests/suite/compact_remote.rs
assertion_line: 178
expression: "format_labeled_requests_snapshot(\"Remote manual /compact where remote compact output is summary-only: follow-up layout uses returned summary plus new user message.\",\n&[(\"Remote Compaction Request\", &compact_request),\n(\"Remote Post-Compaction History Layout\", follow_up_request),])"
expression: "format_labeled_requests_snapshot(\"Remote manual /compact where remote compact output is compaction-only: follow-up layout uses the returned compaction item plus new user message.\",\n&[(\"Remote Compaction Request\", &compact_request),\n(\"Remote Post-Compaction History Layout\", follow_up_request),])"
---
Scenario: Remote manual /compact where remote compact output is summary-only: follow-up layout uses returned summary plus new user message.
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>
@@ -13,9 +12,8 @@ Scenario: Remote manual /compact where remote compact output is summary-only: fo
04:message/assistant:FIRST_REMOTE_REPLY
## Remote Post-Compaction History Layout
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
01:message/user:<AGENTS_MD>
02:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
03:message/user:REMOTE_COMPACTED_SUMMARY
04:compaction:encrypted=true
05:message/user:after compact
00:compaction:encrypted=true
01:message/developer:<PERMISSIONS_INSTRUCTIONS>
02:message/user:<AGENTS_MD>
03:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
04:message/user:after compact

View File

@@ -1,21 +1,18 @@
---
source: core/tests/suite/compact_remote.rs
expression: "format_labeled_requests_snapshot(\"Remote mid-turn compaction after an earlier summary compaction: the older summary remains in model-visible history and round-trips into the next compact request.\",\n&[(\"Second Turn Request (Before Mid-Turn Compaction)\", &requests[1]),\n(\"Remote Compaction Request\", &compact_request),])"
assertion_line: 1876
expression: "format_labeled_requests_snapshot(\"After a prior manual /compact produced an older remote compaction item, the next turn hits remote auto-compaction before the next sampling request. The compact request carries forward that earlier compaction item, and the next sampling request shows the latest compaction item with context reinjected before USER_TWO.\",\n&[(\"Remote Compaction Request\", &compact_request),\n(\"Second Turn Request (After Compaction)\", &second_turn_request),])"
---
Scenario: Remote mid-turn compaction after an earlier summary compaction: the older summary remains in model-visible history and round-trips into the next compact request.
## Second Turn Request (Before Mid-Turn Compaction)
00:message/user:USER_ONE
01:message/user:<COMPACTION_SUMMARY>\nREMOTE_OLDER_SUMMARY
02:message/developer:<PERMISSIONS_INSTRUCTIONS>
03:message/user:<AGENTS_MD>
04:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
05:message/user:<COMPACTION_SUMMARY>\nREMOTE_LATEST_SUMMARY
06:message/user:USER_TWO
Scenario: After a prior manual /compact produced an older remote compaction item, the next turn hits remote auto-compaction before the next sampling request. The compact request carries forward that earlier compaction item, and the next sampling request shows the latest compaction item with context reinjected before USER_TWO.
## Remote Compaction Request
00:message/user:USER_ONE
01:message/developer:<PERMISSIONS_INSTRUCTIONS>
02:message/user:<AGENTS_MD>
03:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
04:message/user:<COMPACTION_SUMMARY>\nREMOTE_OLDER_SUMMARY
01:compaction:encrypted=true
## Second Turn Request (After Compaction)
00:message/user:USER_ONE
01:compaction:encrypted=true
02:message/developer:<PERMISSIONS_INSTRUCTIONS>
03:message/user:<AGENTS_MD>
04:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
05:message/user:USER_TWO

View File

@@ -1,8 +1,8 @@
---
source: core/tests/suite/compact_remote.rs
expression: "format_labeled_requests_snapshot(\"Remote mid-turn continuation compaction after tool output: compact request includes tool artifacts and follow-up request includes the summary.\",\n&[(\"Remote Compaction Request\", &compact_request),\n(\"Remote Post-Compaction History Layout\", &requests[1]),])"
expression: "format_labeled_requests_snapshot(\"Remote mid-turn continuation compaction after tool output: compact request includes tool artifacts and the follow-up request includes the returned compaction item.\",\n&[(\"Remote Compaction Request\", &compact_request),\n(\"Remote Post-Compaction History Layout\", &requests[1]),])"
---
Scenario: Remote mid-turn continuation compaction after tool output: compact request includes tool artifacts and follow-up request includes the summary.
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>
@@ -13,8 +13,8 @@ Scenario: Remote mid-turn continuation compaction after tool output: compact req
05:function_call_output:unsupported call: test_tool
## Remote Post-Compaction History Layout
00:message/user:USER_ONE
01:message/developer:<PERMISSIONS_INSTRUCTIONS>
02:message/user:<AGENTS_MD>
03:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
04:message/user:<COMPACTION_SUMMARY>\nREMOTE_MID_TURN_SUMMARY
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
01:message/user:<AGENTS_MD>
02:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
03:message/user:USER_ONE
04:compaction:encrypted=true

View File

@@ -1,8 +1,8 @@
---
source: core/tests/suite/compact_remote.rs
expression: "format_labeled_requests_snapshot(\"Remote mid-turn compaction where compact output has only summary user content: continuation layout reinjects canonical context before that summary.\",\n&[(\"Remote Compaction Request\", &compact_request),\n(\"Remote Post-Compaction History Layout\", &requests[1]),])"
expression: "format_labeled_requests_snapshot(\"Remote mid-turn compaction where compact output has only a compaction item: continuation layout reinjects context before that compaction item.\",\n&[(\"Remote Compaction Request\", &compact_request),\n(\"Remote Post-Compaction History Layout\", &requests[1]),])"
---
Scenario: Remote mid-turn compaction where compact output has only summary user content: continuation layout reinjects canonical context before that summary.
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>
@@ -16,4 +16,4 @@ Scenario: Remote mid-turn compaction where compact output has only summary user
00:message/developer:<PERMISSIONS_INSTRUCTIONS>
01:message/user:<AGENTS_MD>
02:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
03:message/user:<COMPACTION_SUMMARY>\nREMOTE_SUMMARY_ONLY
03:compaction:encrypted=true

View File

@@ -12,13 +12,12 @@ Scenario: Remote pre-turn auto-compaction with a context override emits the cont
04:message/assistant:REMOTE_FIRST_REPLY
05:message/user:USER_TWO
06:message/assistant:REMOTE_SECOND_REPLY
07:message/user:<ENVIRONMENT_CONTEXT:cwd=PRETURN_CONTEXT_DIFF_CWD>
## Remote Post-Compaction History Layout
00:message/user:USER_ONE
01:message/user:USER_TWO
02:message/developer:<PERMISSIONS_INSTRUCTIONS>
03:message/user:<AGENTS_MD>
04:message/user:<ENVIRONMENT_CONTEXT:cwd=PRETURN_CONTEXT_DIFF_CWD>
05:message/user:<COMPACTION_SUMMARY>\nREMOTE_PRE_TURN_SUMMARY
02:compaction:encrypted=true
03:message/developer:<PERMISSIONS_INSTRUCTIONS>
04:message/user:<AGENTS_MD>
05:message/user:<ENVIRONMENT_CONTEXT:cwd=PRETURN_CONTEXT_DIFF_CWD>
06:message/user:USER_THREE

View File

@@ -1,6 +1,6 @@
---
source: core/tests/suite/compact_remote.rs
expression: "format_labeled_requests_snapshot(\"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.\",\n&[(\"Initial Request (Previous Model)\", &requests[0]),\n(\"Remote Compaction Request\", &compact_request),\n(\"Remote Post-Compaction History Layout\", &requests[1]),])"
expression: "format_labeled_requests_snapshot(\"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.\",\n&[(\"Initial Request (Previous Model)\", &initial_turn_request),\n(\"Remote Compaction Request\", &compact_request),\n(\"Remote Post-Compaction History Layout\", &post_compact_turn_request),])"
---
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.
@@ -19,10 +19,10 @@ Scenario: Remote pre-turn compaction during model switch currently excludes inco
## Remote Post-Compaction History Layout
00:message/user:BEFORE_SWITCH_USER
01:message/developer:<PERMISSIONS_INSTRUCTIONS>
02:message/developer:<personality_spec> The user has requested a new communication st...
03:message/user:<AGENTS_MD>
04:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
05:message/user:<COMPACTION_SUMMARY>\nREMOTE_SWITCH_SUMMARY
06:message/developer:<model_switch>\nThe user was previously using a different model....
01:compaction:encrypted=true
02:message/developer:<model_switch>\nThe user was previously using a different model....
03:message/developer:<PERMISSIONS_INSTRUCTIONS>
04:message/developer:<personality_spec> The user has requested a new communication st...
05:message/user:<AGENTS_MD>
06:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
07:message/user:AFTER_SWITCH_USER

View File

@@ -1,5 +1,6 @@
---
source: core/tests/suite/model_visible_layout.rs
assertion_line: 435
expression: "format_labeled_requests_snapshot(\"First post-resume turn where pre-turn override sets model to rollout model; no model-switch update should appear.\",\n&[(\"Last Request Before Resume\", &initial_request),\n(\"First Request After Resume + Override\", &resumed_request),])"
---
Scenario: First post-resume turn where pre-turn override sets model to rollout model; no model-switch update should appear.
@@ -16,7 +17,5 @@ Scenario: First post-resume turn where pre-turn override sets model to rollout m
02:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
03:message/user:seed resume history
04:message/assistant:recorded before resume
05:message/developer:<PERMISSIONS_INSTRUCTIONS>
06:message/user:<AGENTS_MD>
07:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
08:message/user:first resumed turn after model override
05:message/user:<ENVIRONMENT_CONTEXT:cwd=PRETURN_CONTEXT_DIFF_CWD>
06:message/user:first resumed turn after model override

View File

@@ -1,5 +1,6 @@
---
source: core/tests/suite/model_visible_layout.rs
assertion_line: 337
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),])"
---
Scenario: First post-resume turn where resumed config model differs from rollout and personality changes.
@@ -16,11 +17,7 @@ Scenario: First post-resume turn where resumed config model differs from rollout
02:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
03:message/user:seed resume history
04:message/assistant:recorded before resume
05:message/developer:<PERMISSIONS_INSTRUCTIONS>
06:message/developer:<personality_spec> The user has requested a new communication style. Future messages should adhe...
07:message/user:<AGENTS_MD>
08:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
09:message/developer:<PERMISSIONS_INSTRUCTIONS>
10:message/developer:<model_switch>\nThe user was previously using a different model. Please continue the conversatio...
11:message/developer:<personality_spec> The user has requested a new communication style. Future messages should adhe...
12:message/user:resume and change personality
05:message/developer:<model_switch>\nThe user was previously using a different model. Please continue the conversatio...
06:message/user:<ENVIRONMENT_CONTEXT:cwd=PRETURN_CONTEXT_DIFF_CWD>
07:message/developer:<PERMISSIONS_INSTRUCTIONS>
08:message/user:resume and change personality