Suggestion: improve local compaction handoff

Open 💬 1 comment Opened May 17, 2026 by xCatalitY

Suggestion

Local compaction should hand off enough state for the next model to continue the active task without re-discovering recent context.

This is related to the prompt-adjustment discussion here:
https://github.com/openai/codex/discussions/17330

I could not open a PR against openai/codex from this account because PR creation is restricted, but I pushed a working implementation branch here:

Proposed behavior

  • Make the local compaction prompt explicitly produce a CONTEXT CHECKPOINT SUMMARY handoff.
  • Preserve the latest real user turn plus a bounded recent assistant/reasoning/tool tail.
  • Keep only complete tool call/output pairs when retaining tool context.
  • Emit the generated compact summary as an agent message, while preserving the existing compact warning event.
  • Keep existing remote compaction routing and configured compact_prompt overrides intact.

Representative implementation

The branch adds bounded recent-tail retention before building replacement history:

let user_messages = collect_user_messages(&pre_compaction_history_items);
let recent_tail = collect_recent_context_tail(&pre_compaction_history_items);

let mut new_history = build_compacted_history_with_recent_tail(
    Vec::new(),
    &user_messages,
    &summary_text,
    recent_tail,
);

The retained tail starts from the last real user message, then walks recent model-visible output backward under item/token caps, and drops orphaned tool calls or outputs:

const COMPACT_RECENT_OUTPUT_ITEMS_MAX: usize = 16;
const COMPACT_RECENT_OUTPUT_MAX_TOKENS: i64 = 20_000;

fn collect_recent_context_tail(items: &[ResponseItem]) -> Vec<ResponseItem> {
    let Some(last_user_index) = items
        .iter()
        .enumerate()
        .rev()
        .find_map(|(index, item)| real_user_message_text(item).is_some().then_some(index))
    else {
        return Vec::new();
    };

    let mut selected_outputs = Vec::new();
    let mut remaining_tokens = COMPACT_RECENT_OUTPUT_MAX_TOKENS;
    for item in items[last_user_index + 1..].iter().rev() {
        if !is_recent_output_item(item) {
            continue;
        }
        if selected_outputs.len() >= COMPACT_RECENT_OUTPUT_ITEMS_MAX {
            break;
        }

        let item_tokens =
            approx_tokens_from_byte_count_i64(estimate_response_item_model_visible_bytes(item));
        if item_tokens > remaining_tokens {
            break;
        }
        remaining_tokens = remaining_tokens.saturating_sub(item_tokens.max(0));
        selected_outputs.push(item.clone());
    }
    selected_outputs.reverse();
    let selected_outputs = retain_complete_tool_pairs(selected_outputs);

    let mut recent_tail = Vec::with_capacity(selected_outputs.len() + 1);
    recent_tail.push(items[last_user_index].clone());
    recent_tail.extend(selected_outputs);
    recent_tail
}

The prompt text is changed to request a dense handoff with explicit PREVIOUSLY, PARKED TASKS, and CURRENT TASK sections, and to copy delicate runtime state such as pending jobs, subagents, plans, exact command outputs, and immediate next validation checks.

Validation run on the branch

just fmt
just fix -p codex-core
RUST_MIN_STACK=8388608 cargo test -p codex-core compact::tests
RUST_MIN_STACK=8388608 cargo test -p codex-core --test all suite::compact::
cargo build -p codex-cli

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗