diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,3 @@
+# Changelog for streaming-nonempty
+
+## Unreleased changes
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Author Paolo Veronelli (c) 2021
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Author name here nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,27 @@
+# streaming-nonempty
+
+A correct _groupBy_ function is expected to produce only non-empty groups, which means we should be able to fold them up with only Semigroup constraint on the values. This is not the case for `Data.List.groupBy` as well for the Streaming lib counterpart.
+
+This package export a _groupBy_ function that produce `f (Stream f m a)` groups which are then guaranteed to have the functorial layer.
+
+The `NEStream (Of a) m r` newtype is supporting _sconcat_ which means we can define
+
+```haskell
+
+groupSemigroupBy :: (Semigroup a, Monad m) => (a -> a -> Bool) -> Stream (Of a) m r -> Stream (Of a) m r
+groupSemigroupBy f = S.mapped sconcat . groupBy f
+
+```
+
+with expected behavior to collapse groups using semigroup composition
+
+In contrast using the standard _groupBy_ we are stuck with 
+
+```haskell
+
+groupMonoidBy :: (Monoid a, Monad m) => (a -> a -> Bool) -> Stream (Of a) m r -> Stream (Of a) m r
+groupMonoidBy f = S.mapped mconcat . groupBy f
+
+```
+
+It would be legit to use an `sconcatUnsafe` that would panic on empty streams because *we know* groups are not empty. 
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/src/Streaming/NonEmpty.hs b/src/Streaming/NonEmpty.hs
new file mode 100644
--- /dev/null
+++ b/src/Streaming/NonEmpty.hs
@@ -0,0 +1,74 @@
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+module Streaming.NonEmpty where
+
+import Control.Category (id, (.))
+import Control.Monad (Monad (return))
+import Data.Bool (Bool)
+import Data.Either
+import Data.Eq ((==))
+import Data.Function (($))
+import Data.Functor (Functor, fmap, (<$>))
+import Data.List.NonEmpty hiding (groupBy)
+import Data.Semigroup (Semigroup, (<>))
+import qualified Data.Semigroup as Semigroup
+import Streaming (Of ((:>)), Stream, first, runIdentity, wrap)
+import Streaming.Internal
+import qualified Streaming.Prelude as S
+
+-- | A stream with at least one functorial layer
+newtype NEStream f m a = NEStream (f (Stream f m a)) deriving (Functor)
+
+-- | Is a stream
+toStream :: (Functor f, Monad m) => NEStream f m r -> Stream f m r
+toStream (NEStream f) = wrap f
+
+-- | fold1 is safe
+fold1 :: Monad m => (a -> a -> a) -> NEStream (Of a) m r -> m (Of a r)
+fold1 f (NEStream (x :> s)) = S.fold f x id s
+
+-- | sconcat as in 'Data.Semigroup', with result
+sconcat :: (Semigroup a, Monad m) => NEStream (Of a) m r -> m (Of a r)
+sconcat = fold1 (<>)
+
+-- | harvest the list with the result
+toNonEmpty :: Monad m => NEStream (Of a) m r -> m (Of (NonEmpty a) r)
+toNonEmpty (NEStream (x :> r)) = first (x :|) <$> S.toList r
+
+-- | harvest the list only
+toNonEmpty_ :: Monad m => NEStream (Of a) m r -> m (NonEmpty a)
+toNonEmpty_ = fmap S.fst' . toNonEmpty
+
+-- | sconcat as in 'Data.Semigroup'
+sconcat_ :: (Monad m, Semigroup a) => NEStream (Of a) m r -> m a
+sconcat_ (NEStream (x :> s)) = S.fold_ (<>) x id s
+
+-- | group by some  equality  in non empty groups
+groupBy
+  :: (Monad m)
+  => (a -> a -> Bool)
+  -> Stream (Of a) m r
+  -> Stream (NEStream (Of a) m) m r
+groupBy equals = loop
+  where
+    loop stream = Effect $ do
+      e <- S.next stream
+      return $ case e of
+        Left r -> Return r
+        Right (a, p') ->
+          Step $
+            fmap loop (NEStream (a :> S.span (equals a) p'))
+
+-- | what groupBy could be
+groupByPure :: (a -> a -> Bool) -> [a] -> [NonEmpty a]
+groupByPure equals = runIdentity . S.toList_ . S.mapped toNonEmpty . groupBy equals . S.each
+
+-- | collapse semigroups by some equality
+groupSemigroupBy :: (Semigroup a, Monad m) => (a -> a -> Bool) -> Stream (Of a) m r -> Stream (Of a) m r
+groupSemigroupBy f = S.mapped sconcat . groupBy f
+
+-- | what should be possible to do with 'groupBy'
+-- fmap sconcat . groupBy equals
+groupSemigroupByPure :: Semigroup b => (b -> b -> Bool) -> [b] -> [b]
+groupSemigroupByPure equals = runIdentity . S.toList_ . groupSemigroupBy equals . S.each
diff --git a/streaming-nonempty.cabal b/streaming-nonempty.cabal
new file mode 100644
--- /dev/null
+++ b/streaming-nonempty.cabal
@@ -0,0 +1,51 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.34.4.
+--
+-- see: https://github.com/sol/hpack
+
+name:           streaming-nonempty
+version:        0.1.0.0
+synopsis:       Add support for non empty streams to Streaming lib
+description:    Please see the README on GitHub at <https://gitlab.com/paolo.veronelli/streaming-nonempty/-/blob/master/README.md>
+category:       Streaming
+author:         Paolo Veronelli
+maintainer:     paolo.veronelli@gmail.com
+copyright:      2021 Paolo Veronelli
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    ChangeLog.md
+
+source-repository head
+  type: git
+  location: https://gitlab.com/paolo.veronelli/streaming-nonempty
+
+library
+  exposed-modules:
+      Streaming.NonEmpty
+  other-modules:
+      Paths_streaming_nonempty
+  hs-source-dirs:
+      src
+  build-depends:
+      base >=4.7 && <5
+    , streaming
+  default-language: Haskell2010
+
+test-suite streaming-nonempty-test
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  other-modules:
+      Paths_streaming_nonempty
+  hs-source-dirs:
+      test
+  ghc-options: -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      base >=4.7 && <5
+    , hspec
+    , streaming
+    , streaming-nonempty
+  default-language: Haskell2010
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,29 @@
+{-# LANGUAGE TypeApplications #-}
+
+import Data.Semigroup (Sum (Sum))
+import Streaming.NonEmpty (groupSemigroupBy, groupSemigroupByPure)
+import qualified Streaming.Prelude as S
+import Test.Hspec
+
+main :: IO ()
+main = hspec $ do
+  describe "groupSemigroupBy" $ do
+    it "collapse no stream" $ do
+      xs S.:> () <- S.toList $ groupSemigroupBy (==) $ S.each ([] @(Sum Int))
+      xs `shouldBe` []
+    it "collapse one elem stream" $ do
+      xs S.:> () <- S.toList $ groupSemigroupBy (==) $ S.each [Sum 1]
+      xs `shouldBe` [Sum 1]
+    it "collapse 2 elems of different groups stream" $ do
+      xs S.:> () <- S.toList $ groupSemigroupBy (==) $ S.each [Sum 1, Sum 2]
+      xs `shouldBe` [Sum 1, Sum 2]
+    it "collapse 2 elems of same group stream " $ do
+      xs S.:> () <- S.toList $ groupSemigroupBy (==) $ S.each [Sum 1, Sum 1]
+      xs `shouldBe` [Sum 2]
+    it "collapse 4 elems of 2 groups stream" $ do
+      xs S.:> () <- S.toList $ groupSemigroupBy (==) $ S.each [Sum 1, Sum 1, Sum 2, Sum 2]
+      xs `shouldBe` [Sum 2, Sum 4]
+  describe "groupSemigroupByPure" $ do
+    it "collapse 4 elems of group 2 stream" $ do
+      groupSemigroupByPure (==) [Sum 1, Sum 1, Sum 2, Sum 2]
+        `shouldBe` [Sum 2, Sum 4]
