mirror of
https://github.com/openai/codex.git
synced 2026-10-16 16:58:46 +00:00
Retain client-marked tool outputs across compaction (#52937)
## Why Client-supplied tool outputs can carry task instructions. Even when marked with `retain: true`, these instructions could be trimmed before compaction and disappear from the compacted history. ## What changed Add the disabled-by-default `retain_client_tool_outputs` feature to honor retention for explicitly marked, named `function_call_output` items without a `call_id`. - Preserve eligible outputs in remote compaction v2 and when starting a new context window, keeping their original role. - Stop emergency tool-output trimming at a retained output. - Share the existing 64,000-token retention budget, keeping outputs whole and dropping an oversized output and older items rather than partially truncating it. - Place initial context before retained standalone outputs and account for their images in retained-image counts. ## Testing Add a scenario covering enabled and disabled retention, emergency trimming, oversized-output handling, and preservation of the latest instruction after compaction and cold resume. Verify that retention annotations stay out of model requests. GitOrigin-RevId: 449179670e4d2be2623928700738758e43b5dc1a
This commit is contained in:
@@ -1124,6 +1124,9 @@
|
||||
"retain_client_developer_messages": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"retain_client_tool_outputs": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"rollout_budget": {
|
||||
"$ref": "#/definitions/FeatureToml_for_RolloutBudgetConfigToml"
|
||||
},
|
||||
@@ -7427,6 +7430,9 @@
|
||||
"retain_client_developer_messages": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"retain_client_tool_outputs": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"rollout_budget": {
|
||||
"$ref": "#/definitions/FeatureToml_for_RolloutBudgetConfigToml"
|
||||
},
|
||||
|
||||
@@ -606,7 +606,7 @@ pub(crate) fn is_summary_message(message: &str) -> bool {
|
||||
/// model-expected boundary.
|
||||
///
|
||||
/// Placement rules:
|
||||
/// - Prefer immediately before the last real user or agent message.
|
||||
/// - Prefer immediately before the last real user, agent message, or retained standalone output.
|
||||
/// - If no real user messages remain, insert before the compaction summary so
|
||||
/// the summary stays last.
|
||||
/// - If there are no user messages, insert before the last compaction item so
|
||||
@@ -619,6 +619,10 @@ pub(crate) fn insert_initial_context_before_last_real_user_or_summary(
|
||||
let mut last_user_or_summary_index = None;
|
||||
let mut last_real_user_index = None;
|
||||
for (i, item) in compacted_history.iter().enumerate().rev() {
|
||||
if crate::compact_retained_tool_outputs::is_retained_tool_output(item) {
|
||||
last_real_user_index = Some(i);
|
||||
break;
|
||||
}
|
||||
if let ResponseItem::AgentMessage { content, .. } = &item.item
|
||||
&& !matches!(
|
||||
content.first(),
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use crate::compact_retained_tool_outputs::is_retained_tool_output;
|
||||
use crate::context::ContextualUserFragment;
|
||||
use crate::context::ImageResizeNotice;
|
||||
use crate::context_manager::ContextManager;
|
||||
use crate::context_manager::estimate_item_token_count;
|
||||
use crate::session::turn_context::TurnContext;
|
||||
use codex_features::Feature;
|
||||
use codex_history::ResponseItemEnvelope;
|
||||
use codex_protocol::models::BaseInstructions;
|
||||
use codex_protocol::models::ContentItem;
|
||||
@@ -97,10 +99,19 @@ pub(crate) fn trim_function_call_history_to_fit_context_window(
|
||||
let source_index = original_items
|
||||
.len()
|
||||
.saturating_sub(consumed_items.saturating_add(group_item_count));
|
||||
let Some(rewritten_item) = original_items
|
||||
.get(source_index)
|
||||
.and_then(rewritten_output_for_context_window)
|
||||
else {
|
||||
let Some(rewritten_item) = original_items.get(source_index).and_then(|envelope| {
|
||||
if turn_context
|
||||
.config
|
||||
.features
|
||||
.enabled(Feature::RetainClientToolOutputs)
|
||||
&& is_retained_tool_output(envelope)
|
||||
{
|
||||
// Enabled retained outputs form the same boundary as user/developer messages.
|
||||
None
|
||||
} else {
|
||||
rewritten_output_for_context_window(envelope)
|
||||
}
|
||||
}) else {
|
||||
break;
|
||||
};
|
||||
estimated_tokens = estimated_tokens
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::compact_retained_tool_outputs::is_retained_tool_output;
|
||||
|
||||
use crate::Prompt;
|
||||
use crate::ResponseStream;
|
||||
use crate::client::ModelClientSession;
|
||||
@@ -35,6 +37,7 @@ use codex_analytics::CompactionTrigger;
|
||||
use codex_context_fragments::set_annotated_content;
|
||||
use codex_context_fragments::to_annotated_content;
|
||||
use codex_features::Feature;
|
||||
use codex_features::Features;
|
||||
use codex_history::CodexHarnessMetadata;
|
||||
use codex_history::ResponseItemEnvelope;
|
||||
use codex_protocol::error::CodexErr;
|
||||
@@ -44,6 +47,8 @@ use codex_protocol::items::ContextCompactionItem;
|
||||
use codex_protocol::items::TurnItem;
|
||||
use codex_protocol::models::AgentMessageInputContent;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::FunctionCallOutputBody;
|
||||
use codex_protocol::models::FunctionCallOutputContentItem;
|
||||
#[cfg(test)]
|
||||
use codex_protocol::models::ImageReference;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
@@ -303,7 +308,7 @@ async fn run_remote_compact_task_inner_impl(
|
||||
prompt_input,
|
||||
prompt_input_metadata,
|
||||
compaction_output,
|
||||
sess.enabled(Feature::RetainClientDeveloperMessages),
|
||||
sess.features().get(),
|
||||
if sess.enabled(Feature::CompactionImageBudget) {
|
||||
RetainedImageBudget::Enabled
|
||||
} else {
|
||||
@@ -495,7 +500,7 @@ fn build_v2_compacted_history(
|
||||
prompt_input: Vec<ResponseItem>,
|
||||
prompt_input_metadata: Vec<Option<CodexHarnessMetadata>>,
|
||||
compaction_output: ResponseItem,
|
||||
retain_client_developer_messages: bool,
|
||||
features: &Features,
|
||||
image_budget: RetainedImageBudget,
|
||||
) -> (Vec<ResponseItemEnvelope>, usize) {
|
||||
debug_assert_eq!(prompt_input.len(), prompt_input_metadata.len());
|
||||
@@ -505,9 +510,7 @@ fn build_v2_compacted_history(
|
||||
.map(|(item, metadata)| ResponseItemEnvelope { item, metadata })
|
||||
.collect::<Vec<_>>();
|
||||
let retained = v2_history_item_groups(prompt_input)
|
||||
.filter(|group| {
|
||||
is_retained_for_remote_compaction_v2(&group.source, retain_client_developer_messages)
|
||||
})
|
||||
.filter(|group| is_retained_for_remote_compaction_v2(&group.source, features))
|
||||
.flat_map(HistoryItemGroup::into_items)
|
||||
.collect::<Vec<_>>();
|
||||
let mut retained =
|
||||
@@ -544,8 +547,11 @@ fn v2_history_item_groups(
|
||||
|
||||
fn is_retained_for_remote_compaction_v2(
|
||||
envelope: &ResponseItemEnvelope,
|
||||
retain_client_developer_messages: bool,
|
||||
features: &Features,
|
||||
) -> bool {
|
||||
if features.enabled(Feature::RetainClientToolOutputs) && is_retained_tool_output(envelope) {
|
||||
return true;
|
||||
}
|
||||
let item = &envelope.item;
|
||||
if let ResponseItem::AgentMessage {
|
||||
author,
|
||||
@@ -583,13 +589,22 @@ fn is_retained_for_remote_compaction_v2(
|
||||
Some(TurnItem::UserMessage(_) | TurnItem::HookPrompt(_))
|
||||
),
|
||||
"developer" => {
|
||||
retain_client_developer_messages && is_client_authored_developer_message(envelope)
|
||||
features.enabled(Feature::RetainClientDeveloperMessages)
|
||||
&& is_client_authored_developer_message(envelope)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn retained_input_image_count(item: &ResponseItem) -> usize {
|
||||
if let ResponseItem::FunctionCallOutput { output, .. } = item
|
||||
&& let FunctionCallOutputBody::ContentItems(content) = &output.body
|
||||
{
|
||||
return content
|
||||
.iter()
|
||||
.filter(|item| matches!(item, FunctionCallOutputContentItem::InputImage { .. }))
|
||||
.count();
|
||||
}
|
||||
let ResponseItem::Message { content, .. } = item else {
|
||||
return 0;
|
||||
};
|
||||
@@ -648,6 +663,9 @@ fn truncate_retained_messages(
|
||||
}
|
||||
truncated_reversed.push(group.source);
|
||||
remaining = remaining.saturating_sub(token_count);
|
||||
} else if is_retained_tool_output(&group.source) {
|
||||
// Keep outputs whole and stop before older items to preserve newest-first ordering.
|
||||
break;
|
||||
} else if remaining > notice_tokens {
|
||||
let available_tokens = remaining - notice_tokens;
|
||||
let content_budget = if client_developer {
|
||||
@@ -804,7 +822,7 @@ mod tests {
|
||||
input,
|
||||
metadata,
|
||||
output,
|
||||
/*retain_client_developer_messages*/ false,
|
||||
&Features::with_defaults(),
|
||||
RetainedImageBudget::Disabled,
|
||||
)
|
||||
}
|
||||
@@ -907,6 +925,10 @@ mod tests {
|
||||
};
|
||||
|
||||
for enabled in [false, true] {
|
||||
let mut features = Features::with_defaults();
|
||||
if enabled {
|
||||
features.enable(Feature::RetainClientDeveloperMessages);
|
||||
}
|
||||
let (history, _) = build_v2_compacted_history(
|
||||
vec![
|
||||
harness.clone(),
|
||||
@@ -924,7 +946,7 @@ mod tests {
|
||||
None,
|
||||
],
|
||||
output.clone(),
|
||||
enabled,
|
||||
&features,
|
||||
RetainedImageBudget::Disabled,
|
||||
);
|
||||
let mut expected = vec![
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
//! Retains explicitly marked, named function outputs that have no matching call.
|
||||
//! Outputs share the normal history token budget and are retained whole or dropped.
|
||||
|
||||
use codex_history::ResponseItemEnvelope;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
|
||||
pub(crate) fn is_retained_tool_output(envelope: &ResponseItemEnvelope) -> bool {
|
||||
envelope
|
||||
.metadata
|
||||
.as_ref()
|
||||
.is_some_and(|metadata| metadata.client_authored)
|
||||
&& matches!(
|
||||
envelope.item,
|
||||
ResponseItem::FunctionCallOutput {
|
||||
call_id: None,
|
||||
name: Some(_),
|
||||
..
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -36,6 +36,7 @@ pub use turn_extension_data::WithTurnExtensionData;
|
||||
mod compact_model_fallback;
|
||||
mod compact_remote_history;
|
||||
mod compact_remote_v2;
|
||||
mod compact_retained_tool_outputs;
|
||||
mod compact_token_budget;
|
||||
mod thread_startup_metadata;
|
||||
pub use codex_network_proxy::EnvironmentNetworkPolicy;
|
||||
|
||||
@@ -4663,22 +4663,23 @@ impl Session {
|
||||
let turn_context = step_context.turn.as_ref();
|
||||
let history = self.clone_history().await;
|
||||
let input_goal_ids = crate::context::UserGoalUpdate::message_ids(history.raw_items());
|
||||
let retained_client_developer_messages =
|
||||
if self.enabled(Feature::RetainClientDeveloperMessages) {
|
||||
crate::compact_remote_v2::truncate_retained_messages_for_remote_compaction(
|
||||
history
|
||||
.annotated_items()
|
||||
.iter()
|
||||
.filter(|item| {
|
||||
crate::compact_remote_v2::is_client_authored_developer_message(item)
|
||||
})
|
||||
.cloned()
|
||||
.collect(),
|
||||
crate::compact_remote_v2::RETAINED_MESSAGE_TOKEN_BUDGET,
|
||||
)
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let retained_client_items =
|
||||
crate::compact_remote_v2::truncate_retained_messages_for_remote_compaction(
|
||||
history
|
||||
.annotated_items()
|
||||
.iter()
|
||||
.filter(|item| {
|
||||
(self.enabled(Feature::RetainClientToolOutputs)
|
||||
&& crate::compact_retained_tool_outputs::is_retained_tool_output(item))
|
||||
|| (self.enabled(Feature::RetainClientDeveloperMessages)
|
||||
&& crate::compact_remote_v2::is_client_authored_developer_message(
|
||||
item,
|
||||
))
|
||||
})
|
||||
.cloned()
|
||||
.collect(),
|
||||
crate::compact_remote_v2::RETAINED_MESSAGE_TOKEN_BUDGET,
|
||||
);
|
||||
let window = {
|
||||
let mut state = self.state.lock().await;
|
||||
state.start_new_context_window()
|
||||
@@ -4691,7 +4692,7 @@ impl Session {
|
||||
crate::context_manager::updates::merge_world_state_updates(context_updates)
|
||||
.into_iter()
|
||||
.map(ResponseItemEnvelope::new)
|
||||
.chain(retained_client_developer_messages)
|
||||
.chain(retained_client_items)
|
||||
.collect();
|
||||
let turn_context_item = step_context.to_turn_context_item();
|
||||
self.replace_compacted_history(
|
||||
|
||||
@@ -89,6 +89,9 @@ use tokio::sync::oneshot;
|
||||
|
||||
const ONE_PIXEL_PNG_BASE64: &str = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==";
|
||||
|
||||
#[path = "scenarios_retained_tool_outputs.rs"]
|
||||
mod retained_tool_outputs;
|
||||
|
||||
#[path = "scenarios_incremental_tools.rs"]
|
||||
mod incremental_tools;
|
||||
#[path = "scenarios_incremental_tools_resume.rs"]
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
//! Client-marked function outputs survive compaction and a cold resume in their original role.
|
||||
|
||||
use super::*;
|
||||
use codex_protocol::turn_input::AnnotatedResponseItem;
|
||||
use codex_protocol::turn_input::ResponseItemAnnotations;
|
||||
use codex_protocol::turn_input::TurnInput;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn retained_tool_output_survives_compaction_and_resume() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
for enabled in [false, true] {
|
||||
let server = start_mock_server().await;
|
||||
let mock = mount_sse_sequence(&server, vec![
|
||||
sse(vec![ev_assistant_message("accepted", "I will investigate the tests."), ev_completed("accepted-response")]),
|
||||
sse(vec![ev_assistant_message("details-accepted", "I will use those task details."), ev_completed("details-response")]),
|
||||
sse(vec![ev_completed("instruction-response")]),
|
||||
sse(vec![json!({"type":"response.output_item.done", "item":{"type":"compaction", "encrypted_content":"RETAINED_TOOL_OUTPUT_CHECKPOINT"}}), ev_completed("compact-response")]),
|
||||
sse(vec![ev_assistant_message("resumed", "The request was to investigate the tests."), ev_completed("resumed-response")]),
|
||||
]).await;
|
||||
let configure = move |config: &mut Config| {
|
||||
configure_scenario_catalog(config);
|
||||
config
|
||||
.features
|
||||
.set_enabled(Feature::RetainClientToolOutputs, enabled)
|
||||
.unwrap();
|
||||
config.model_context_window = Some(1_000);
|
||||
config.model_auto_compact_token_limit = Some(i64::MAX);
|
||||
// Let this scenario exercise the compaction budget rather than ingestion truncation.
|
||||
config
|
||||
.model_catalog
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.models
|
||||
.iter_mut()
|
||||
.find(|model| model.slug == "gpt-6-astra")
|
||||
.unwrap()
|
||||
.truncation_policy = codex_protocol::openai_models::TruncationPolicyConfig::tokens(
|
||||
/*limit*/ 100_000,
|
||||
);
|
||||
};
|
||||
let test = test_codex()
|
||||
.with_model("gpt-6-astra")
|
||||
.with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
|
||||
.with_config(configure)
|
||||
.build_with_auto_env(&server)
|
||||
.await?;
|
||||
let details = "Investigate the failing tests carefully. ".repeat(7_000);
|
||||
let latest_instruction = "Report your findings without changing files.";
|
||||
for (name, output) in [
|
||||
(
|
||||
"send_message_to_thread",
|
||||
"Investigate the failing tests; report your findings without changing files.",
|
||||
),
|
||||
("task_details", details.as_str()),
|
||||
("send_message_to_thread", latest_instruction),
|
||||
] {
|
||||
let request = TurnInputRequest::new(TurnInput::AnnotatedResponseItem(
|
||||
AnnotatedResponseItem {
|
||||
item: serde_json::from_value(json!({
|
||||
"type": "function_call_output", "name": name, "namespace": "codex_app", "output": output,
|
||||
}))?,
|
||||
annotations: ResponseItemAnnotations { retain: true },
|
||||
},
|
||||
));
|
||||
test.codex.start_or_steer_turn(request).await?;
|
||||
wait_for_event(&test.codex, |event| {
|
||||
matches!(event, EventMsg::TurnComplete(_))
|
||||
})
|
||||
.await;
|
||||
}
|
||||
test.codex
|
||||
.inject_response_items(vec![serde_json::from_value(json!({
|
||||
"type": "function_call_output", "name": "progress", "namespace": "example",
|
||||
"output": "Transient progress update. ".repeat(1000),
|
||||
}))?])
|
||||
.await?;
|
||||
test.codex.submit(Op::Compact).await?;
|
||||
wait_for_event(&test.codex, |event| {
|
||||
matches!(event, EventMsg::TurnComplete(_))
|
||||
})
|
||||
.await;
|
||||
let resumed = test_codex()
|
||||
.with_model("gpt-6-astra")
|
||||
.with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
|
||||
.with_config(move |config| {
|
||||
configure(config);
|
||||
config.model_context_window = Some(200_000);
|
||||
})
|
||||
.restart_with_auto_env(&server, &test)
|
||||
.await?;
|
||||
resumed
|
||||
.submit_text_turn("What request are you working on?")
|
||||
.await?;
|
||||
let requests = mock.requests();
|
||||
assert_eq!(requests.len(), 5);
|
||||
for request in &requests[..3] {
|
||||
assert!(request.inputs_of_type("compaction_trigger").is_empty());
|
||||
}
|
||||
let compact_outputs = requests[3].inputs_of_type("function_call_output");
|
||||
assert_eq!(compact_outputs.len(), 4);
|
||||
// Emergency trimming removes the trailing log, then stops at the marked instruction.
|
||||
assert_eq!(compact_outputs[1]["output"], details);
|
||||
if enabled {
|
||||
assert_eq!(compact_outputs[2]["output"], latest_instruction);
|
||||
} else {
|
||||
assert_eq!(
|
||||
compact_outputs[2]["output"],
|
||||
"Output exceeded the available model context and was truncated"
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
compact_outputs[3]["output"],
|
||||
"Output exceeded the available model context and was truncated"
|
||||
);
|
||||
let last = requests.last().unwrap();
|
||||
assert!(last.inputs_of_type("compaction_trigger").is_empty());
|
||||
assert!(last.body_contains_text("What request are you working on?"));
|
||||
let outputs = last.inputs_of_type("function_call_output");
|
||||
if enabled {
|
||||
// Oversized details and older instructions are dropped; the latest stays whole.
|
||||
assert_eq!(outputs, vec![compact_outputs[2].clone()]);
|
||||
for output in outputs {
|
||||
assert!(output.get("retain").is_none());
|
||||
assert!(output.get("client_authored").is_none());
|
||||
}
|
||||
} else {
|
||||
assert!(outputs.is_empty());
|
||||
}
|
||||
insta::assert_snapshot!(
|
||||
if enabled {
|
||||
"retained_tool_output_survives_compaction_and_resume"
|
||||
} else {
|
||||
"retained_tool_output_retention_disabled"
|
||||
},
|
||||
context_snapshot::format_request_history_snapshot(
|
||||
if enabled {
|
||||
"Retained thread instructions stop emergency tool-output trimming. Compaction keeps whole outputs within the shared 64K retention budget, dropping oversized task details and older instructions. A cold resume preserves the latest instruction unchanged."
|
||||
} else {
|
||||
"With retention disabled, the client can still send retain=true. Tool outputs use ordinary emergency trimming and are not retained after compaction and cold resume."
|
||||
},
|
||||
&requests,
|
||||
&ContextSnapshotOptions::default().rewrite_known_segments(),
|
||||
)
|
||||
);
|
||||
resumed.codex.shutdown_and_wait().await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
---
|
||||
source: core/tests/suite/scenarios_retained_tool_outputs.rs
|
||||
expression: "context_snapshot::format_request_history_snapshot(if enabled\n{\n \"Retained thread instructions stop emergency tool-output trimming. Compaction shares the 64K retention budget with oversized task details, and a cold resume preserves the retained tool content.\"\n} else\n{\n \"With retention disabled, the client can still send retain=true. Tool outputs use ordinary emergency trimming and are not retained after compaction and cold resume.\"\n}, &requests, &ContextSnapshotOptions::default().rewrite_known_segments(),)"
|
||||
---
|
||||
Scenario: With retention disabled, the client can still send retain=true. Tool outputs use ordinary emergency trimming and are not retained after compaction and cold resume.
|
||||
|
||||
## Window 1
|
||||
-- request 1 (turn) --
|
||||
00:additional_tools/developer (3; hash=bf2e30605ff5dc17):
|
||||
- namespace/functions; hash=fada4a476a3754dd
|
||||
- custom/exec
|
||||
- function/wait
|
||||
- function/request_user_input
|
||||
- function/request_user_input_async
|
||||
- namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
|
||||
- function/sleep
|
||||
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
|
||||
- function/followup_task
|
||||
- function/interrupt_agent
|
||||
- function/list_agents
|
||||
- function/send_message
|
||||
- function/spawn_agent
|
||||
- function/wait_agent
|
||||
01:message/developer:
|
||||
<MODEL_INSTRUCTIONS>
|
||||
02:message/developer[2]:
|
||||
[01] <PERMISSIONS_INSTRUCTIONS>
|
||||
[02] <COLLABORATION_MODE>
|
||||
03:message/developer:
|
||||
<MULTI_AGENT_ROLE>
|
||||
04:message/developer:
|
||||
<MULTI_AGENT_MODE>
|
||||
05:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
06:function_call_output/codex_app.send_message_to_thread:Investigate the failing tests; report your findings without changing files.
|
||||
-- request 2 (turn) --
|
||||
07:message/assistant:
|
||||
I will investigate the tests.
|
||||
08:function_call_output/codex_app.task_details:Investigate the failing tests carefully. Investigate the failing tests carefully. Investigate the failin...s carefully. Investigate the failing tests carefully. [hash=3c65017b6f98a38f]
|
||||
-- request 3 (turn) --
|
||||
09:message/assistant:
|
||||
I will use those task details.
|
||||
10:function_call_output/codex_app.send_message_to_thread:Report your findings without changing files.
|
||||
|
||||
## Window 2 (after request 3: input diverged at item 10)
|
||||
-- request 4 (compaction) --
|
||||
00:additional_tools/developer (3; hash=bf2e30605ff5dc17):
|
||||
- namespace/functions; hash=fada4a476a3754dd
|
||||
- custom/exec
|
||||
- function/wait
|
||||
- function/request_user_input
|
||||
- function/request_user_input_async
|
||||
- namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
|
||||
- function/sleep
|
||||
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
|
||||
- function/followup_task
|
||||
- function/interrupt_agent
|
||||
- function/list_agents
|
||||
- function/send_message
|
||||
- function/spawn_agent
|
||||
- function/wait_agent
|
||||
01:message/developer:
|
||||
<MODEL_INSTRUCTIONS>
|
||||
02:message/developer[2]:
|
||||
[01] <PERMISSIONS_INSTRUCTIONS>
|
||||
[02] <COLLABORATION_MODE>
|
||||
03:message/developer:
|
||||
<MULTI_AGENT_ROLE>
|
||||
04:message/developer:
|
||||
<MULTI_AGENT_MODE>
|
||||
05:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
06:function_call_output/codex_app.send_message_to_thread:Investigate the failing tests; report your findings without changing files.
|
||||
07:message/assistant:
|
||||
I will investigate the tests.
|
||||
08:function_call_output/codex_app.task_details:Investigate the failing tests carefully. Investigate the failing tests carefully. Investigate the failin...s carefully. Investigate the failing tests carefully. [hash=3c65017b6f98a38f]
|
||||
09:message/assistant:
|
||||
I will use those task details.
|
||||
10:function_call_output/codex_app.send_message_to_thread:Output exceeded the available model context and was truncated
|
||||
11:function_call_output/example.progress:Output exceeded the available model context and was truncated
|
||||
12:compaction_trigger
|
||||
|
||||
## Window 3 (after request 4: input diverged at item 06)
|
||||
-- request 5 (turn) --
|
||||
00:additional_tools/developer (3; hash=bf2e30605ff5dc17):
|
||||
- namespace/functions; hash=fada4a476a3754dd
|
||||
- custom/exec
|
||||
- function/wait
|
||||
- function/request_user_input
|
||||
- function/request_user_input_async
|
||||
- namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
|
||||
- function/sleep
|
||||
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
|
||||
- function/followup_task
|
||||
- function/interrupt_agent
|
||||
- function/list_agents
|
||||
- function/send_message
|
||||
- function/spawn_agent
|
||||
- function/wait_agent
|
||||
01:message/developer:
|
||||
<MODEL_INSTRUCTIONS>
|
||||
02:message/developer[2]:
|
||||
[01] <PERMISSIONS_INSTRUCTIONS>
|
||||
[02] <COLLABORATION_MODE>
|
||||
03:message/developer:
|
||||
<MULTI_AGENT_ROLE>
|
||||
04:message/developer:
|
||||
<MULTI_AGENT_MODE>
|
||||
05:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
06:compaction:encrypted=true; chars=31; hash=dc1cfb1784ca88e3
|
||||
07:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
08:message/user:
|
||||
What request are you working on?
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
---
|
||||
source: core/tests/suite/scenarios_retained_tool_outputs.rs
|
||||
expression: "context_snapshot::format_request_history_snapshot(\"Retained thread instructions stop emergency tool-output trimming. Compaction keeps whole outputs within the shared 64K retention budget, dropping oversized task details and older instructions. A cold resume preserves the latest instruction unchanged.\",\n&requests, &ContextSnapshotOptions::default().rewrite_known_segments(),)"
|
||||
---
|
||||
Scenario: Retained thread instructions stop emergency tool-output trimming. Compaction keeps whole outputs within the shared 64K retention budget, dropping oversized task details and older instructions. A cold resume preserves the latest instruction unchanged.
|
||||
|
||||
## Window 1
|
||||
-- request 1 (turn) --
|
||||
00:additional_tools/developer (3; hash=bf2e30605ff5dc17):
|
||||
- namespace/functions; hash=fada4a476a3754dd
|
||||
- custom/exec
|
||||
- function/wait
|
||||
- function/request_user_input
|
||||
- function/request_user_input_async
|
||||
- namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
|
||||
- function/sleep
|
||||
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
|
||||
- function/followup_task
|
||||
- function/interrupt_agent
|
||||
- function/list_agents
|
||||
- function/send_message
|
||||
- function/spawn_agent
|
||||
- function/wait_agent
|
||||
01:message/developer:
|
||||
<MODEL_INSTRUCTIONS>
|
||||
02:message/developer[2]:
|
||||
[01] <PERMISSIONS_INSTRUCTIONS>
|
||||
[02] <COLLABORATION_MODE>
|
||||
03:message/developer:
|
||||
<MULTI_AGENT_ROLE>
|
||||
04:message/developer:
|
||||
<MULTI_AGENT_MODE>
|
||||
05:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
06:function_call_output/codex_app.send_message_to_thread:Investigate the failing tests; report your findings without changing files.
|
||||
-- request 2 (turn) --
|
||||
07:message/assistant:
|
||||
I will investigate the tests.
|
||||
08:function_call_output/codex_app.task_details:Investigate the failing tests carefully. Investigate the failing tests carefully. Investigate the failin...s carefully. Investigate the failing tests carefully. [hash=3c65017b6f98a38f]
|
||||
-- request 3 (turn) --
|
||||
09:message/assistant:
|
||||
I will use those task details.
|
||||
10:function_call_output/codex_app.send_message_to_thread:Report your findings without changing files.
|
||||
-- request 4 (compaction) --
|
||||
11:function_call_output/example.progress:Output exceeded the available model context and was truncated
|
||||
12:compaction_trigger
|
||||
|
||||
## Window 2 (after request 4: input diverged at item 06)
|
||||
-- request 5 (turn) --
|
||||
00:additional_tools/developer (3; hash=bf2e30605ff5dc17):
|
||||
- namespace/functions; hash=fada4a476a3754dd
|
||||
- custom/exec
|
||||
- function/wait
|
||||
- function/request_user_input
|
||||
- function/request_user_input_async
|
||||
- namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
|
||||
- function/sleep
|
||||
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
|
||||
- function/followup_task
|
||||
- function/interrupt_agent
|
||||
- function/list_agents
|
||||
- function/send_message
|
||||
- function/spawn_agent
|
||||
- function/wait_agent
|
||||
01:message/developer:
|
||||
<MODEL_INSTRUCTIONS>
|
||||
02:message/developer[2]:
|
||||
[01] <PERMISSIONS_INSTRUCTIONS>
|
||||
[02] <COLLABORATION_MODE>
|
||||
03:message/developer:
|
||||
<MULTI_AGENT_ROLE>
|
||||
04:message/developer:
|
||||
<MULTI_AGENT_MODE>
|
||||
05:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
06:function_call_output/codex_app.send_message_to_thread:Report your findings without changing files.
|
||||
07:compaction:encrypted=true; chars=31; hash=dc1cfb1784ca88e3
|
||||
08:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
09:message/user:
|
||||
What request are you working on?
|
||||
@@ -423,6 +423,8 @@ pub enum Feature {
|
||||
CompactionImageBudget,
|
||||
/// Retain client-authored developer messages across compacted context windows.
|
||||
RetainClientDeveloperMessages,
|
||||
/// Honor client-requested tool-output retention across context windows.
|
||||
RetainClientToolOutputs,
|
||||
/// Use Agent Identity for ChatGPT-authenticated sessions.
|
||||
UseAgentIdentity,
|
||||
/// Enable workspace dependency support.
|
||||
@@ -2052,6 +2054,12 @@ pub const FEATURES: &[FeatureSpec] = &[
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::RetainClientToolOutputs,
|
||||
key: "retain_client_tool_outputs",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::UseAgentIdentity,
|
||||
key: "use_agent_identity",
|
||||
|
||||
Reference in New Issue
Block a user