Skip to content

Locate compaction prefix boundary positionally to avoid dropping messages#524

Open
PratikDhanave wants to merge 1 commit into
microsoft:mainfrom
PratikDhanave:fix-compaction-repeated-content-drop
Open

Locate compaction prefix boundary positionally to avoid dropping messages#524
PratikDhanave wants to merge 1 commit into
microsoft:mainfrom
PratikDhanave:fix-compaction-repeated-content-drop

Conversation

@PratikDhanave

Copy link
Copy Markdown
Contributor

Summary

MessageIndex.Update (agent/compaction/index.go) locates the end of the already-processed prefix by searching backward for the last message whose content equals lastProcessedMessage. When message content repeats, that latches the boundary onto a later duplicate, so the turns appended since the previous update are silently dropped from the index.

Trigger (verified):

  • Turn 1 history [user "hi", assistant "hello", user "continue"] → indexed (3 messages).
  • Turn 2 history [user "hi", assistant "hello", user "continue", assistant "sure", user "continue"] — the user re-sent "continue".
  • Update finds the last "continue" (index 4) as the boundary and appends nothing after it → the index has 3 messages, not 5. "sure" and the new "continue" are lost.

Message IDs are empty for message.NewText, so the boundary match falls back to role+content equality (equality.go), and two empty-content messages also compare equal — so this fires for any repeated short message ("yes", "ok", "continue") or empty-content messages. The index is persisted via state.MessageGroups, so the divergence compounds across turns.

Fix

Check the expected position (processedMessageCount - 1, where lastProcessedMessage sits when nothing earlier was trimmed) first; fall back to the backward search only when that position doesn't match (i.e. the prefix was trimmed). This fixes the repeated-content drop while preserving the existing trim-tolerance / rebuild behavior.

Design note: the backward search exists to tolerate history trimming (when earlier messages are removed, lastProcessedMessage shifts to an earlier index). The positional-first check keeps that fallback intact — it only changes which occurrence is chosen when the untrimmed prefix already matches at its expected position. Happy to adjust if you'd prefer a different boundary strategy (e.g. tracking more prefix state).

Public API

No exported symbols change.

Tests

Adds TestMessageIndex_Update_RepeatedContentDoesNotDropMessages (black-box, in index_test.go): a repeated "continue" across two Update calls. It reports [hi hello continue] (dropped) before this change and the full [hi hello continue sure continue] after. Full ./agent/compaction suite passes (trim-tolerance and other behavior unaffected).

…ages

MessageIndex.Update located the end of the already-processed prefix by
searching backward for the last message whose CONTENT equals
lastProcessedMessage. When a message repeats (e.g. a user re-sending
"continue", or any empty-content message), that latched the boundary onto a
later duplicate, so the turns appended since the previous update were
silently dropped from the index. Because the index is persisted across turns
(state.MessageGroups), the loss compounds.

Check the expected position (processedMessageCount-1) first, where
lastProcessedMessage sits when nothing earlier was trimmed, and fall back to
the backward search only when the prefix was actually trimmed — preserving
the existing trim-tolerance / rebuild behavior.

Adds a black-box test (repeated "continue") that drops messages before this
change and passes after.
@PratikDhanave
PratikDhanave requested a review from a team as a code owner July 17, 2026 18:15
Copilot AI review requested due to automatic review settings July 17, 2026 18:15

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes an incremental indexing bug in MessageIndex.Update where repeated message content could cause the “already-processed prefix” boundary to latch onto a later duplicate, silently dropping newly appended turns from the compaction index across updates.

Changes:

  • Update MessageIndex.Update to prefer a positional boundary check before falling back to a backward search for lastProcessedMessage.
  • Add a regression test that reproduces the repeated-content drop (“continue” repeated across turns) and validates the fix.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
agent/compaction/index.go Adjusts how the processed-prefix boundary is located to avoid dropping newly appended messages when content repeats.
agent/compaction/index_test.go Adds a regression test to ensure repeated content across Update calls doesn’t drop appended turns.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread agent/compaction/index.go
Comment on lines 80 to +84
processedMessageCount := index.includedRawMessageCount()
if index.lastProcessedMessage != nil && len(messages) >= processedMessageCount && messageContentEqual(messages[len(messages)-1], index.lastProcessedMessage) {
return
}

// Locate where the already-processed prefix ends. lastProcessedMessage sits
// at index processedMessageCount-1 when nothing earlier was trimmed, so check
// that expected position first. Matching only the last occurrence of its
// content would latch the boundary onto a later duplicate when a message is
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants