diff --git a/openhands/memory/condenser/condenser.py b/openhands/memory/condenser/condenser.py index 723473c6aab9..411ed39386f6 100644 --- a/openhands/memory/condenser/condenser.py +++ b/openhands/memory/condenser/condenser.py @@ -161,13 +161,13 @@ def __init__(self) -> None: @override def condensed_history(self, state: State) -> list[Event]: - # If history has been truncated, reset the condenser state + # The history should grow monotonically -- if it doesn't, something has + # truncated the history and we need to reset our tracking. if len(state.history) < self._last_history_length: self._condensation = [] self._last_history_length = 0 - new_events = state.history - else: - new_events = state.history[self._last_history_length :] + + new_events = state.history[self._last_history_length :] with self.metadata_batch(state): results = self.condense(self._condensation + new_events) diff --git a/tests/unit/test_condenser.py b/tests/unit/test_condenser.py index 0179fb9ba142..fd1e922a103a 100644 --- a/tests/unit/test_condenser.py +++ b/tests/unit/test_condenser.py @@ -453,18 +453,16 @@ def test_llm_attention_condenser_invalid_config(): pytest.raises(ValueError, LLMAttentionCondenser.from_config, config) -class TestRollingCondenser(RollingCondenser): - """Test implementation of RollingCondenser that just returns all events.""" +def test_rolling_condenser_handles_truncation(mock_state: State): + """Test that RollingCondenser correctly handles history truncation.""" - def condense(self, events: list[Event]) -> list[Event]: - return events + class TestRollingCondenser(RollingCondenser): + """Test implementation of RollingCondenser that just returns all events.""" + def condense(self, events: list[Event]) -> list[Event]: + return events -def test_rolling_condenser_handles_truncation(): - """Test that RollingCondenser correctly handles history truncation.""" condenser = TestRollingCondenser() - mock_state = MagicMock(spec=State) - mock_state.extra_data = {} # Initial history with 3 events events = [ @@ -479,29 +477,23 @@ def test_rolling_condenser_handles_truncation(): assert len(results) == 3 assert [e._id for e in results] == [1, 2, 3] - # Add 2 more events - events.extend([ - create_test_event('Event 4', id=4), - create_test_event('Event 5', id=5), - ]) - mock_state.history = events + # Simulate truncation - history is now shorter, and the condensation should + # just include the truncated history + mock_state.history = mock_state.history[-1:] - # Second condensation - should return all 5 events results = condenser.condensed_history(mock_state) - assert len(results) == 5 - assert [e._id for e in results] == [1, 2, 3, 4, 5] + assert len(results) == 1 + assert results[0]._id == 3 - # Simulate truncation - history is now shorter - truncated_events = [ + # Adding more events and condensing should "rebase" us from the truncated history + mock_state.history += [ create_test_event('Event 4', id=4), create_test_event('Event 5', id=5), ] - mock_state.history = truncated_events - # Third condensation - should handle truncation gracefully results = condenser.condensed_history(mock_state) - assert len(results) == 2 - assert [e._id for e in results] == [4, 5] + assert len(results) == 3 + assert [e._id for e in results] == [3, 4, 5] def test_llm_attention_condenser_keeps_first_events(mock_llm, mock_state):