Skip to content

compact.rs truncate_oldest: message ordering scrambled when no system message exists (keep_system=false) #202

@Felicity73

Description

@Felicity73

Bug Description

In cortex-engine/src/compact.rs, the truncate_oldest() method at lines 225-235 iterates messages in reverse order (newest first) and inserts each one using result.insert(if result.is_empty() { 0 } else { 1 }, ...). When a system message is present at index 0, all inserts go to index 1 (pushing previous entries right), producing correct chronological order. However, when no system message exists (keep_system=false or no system message in input), the first insert goes to index 0 and stays there permanently, scrambling message order.

Evidence

compact.rs lines 225-235:

// Take from the end (most recent)
let non_system: Vec<_> = messages.iter().filter(|m| !m.is_system).collect();

for msg in non_system.iter().rev() {
    if current_tokens + msg.tokens <= self.config.target_tokens {
        result.insert(if result.is_empty() { 0 } else { 1 }, (*msg).clone());
        current_tokens += msg.tokens;
    } else {
        removed += 1;
    }
}

Trace without system message:
Input: [A(10tok), B(20tok), C(30tok), D(40tok), E(50tok)] oldest→newest, target=100

Reverse iteration: E, D, C, B, A

  1. E(50): result.is_empty()=true → insert at 0 → [E], tokens=50
  2. D(40): result.is_empty()=false → insert at 1 → [E, D], tokens=90
  3. C(30): 90+30=120 > 100 → skip
  4. B(20): 90+20=110 > 100 → skip
  5. A(10): 90+10=100 ≤ 100 → insert at 1 → [E, A, D], tokens=100

Result: [E, A, D] — WRONG! Correct order should be [A, D, E].

The newest message E is stuck at index 0 because the first insert used index 0, and all subsequent inserts at index 1 go after it.

With system message (correct): The system message occupies index 0, so all data inserts go at index 1, producing correct order [SYS, A, D, E].

Impact

When keep_system is false (a valid CompactionConfig option), or when the conversation has no system message, truncate_oldest produces messages in scrambled chronological order. This corrupts the conversation context sent to the LLM, potentially causing confused or incoherent responses since the model sees messages out of order.

Expected Fix

Always insert at index 0 (prepend) since we iterate newest→oldest:

let insert_pos = if self.config.keep_system && result.first().map_or(false, |m| m.is_system) { 1 } else { 0 };
for msg in non_system.iter().rev() {
    if current_tokens + msg.tokens <= self.config.target_tokens {
        result.insert(insert_pos, (*msg).clone());
        current_tokens += msg.tokens;
    }
}

Or reverse the kept messages after collection.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions