diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,3 +1,9 @@
+1.1
+
+- add sampleRunT and samplesRunT: both return the final state of the PRNG as a tuple of Word64
+- add unit test that asserts that sample runs are deterministic given the same starting PRNG state
+- 
+
 1.0
 
 - add MonadReader r (GenT m) instance
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
 # splitmix-distributions
 
+[![Haskell CI](https://github.com/ocramz/splitmix-distributions/actions/workflows/ci.yaml/badge.svg)](https://github.com/ocramz/splitmix-distributions/actions/workflows/ci.yaml)
+
 Random samplers for some common distributions, as well as a convenient interface for composing them, based on `splitmix`.
 
 
diff --git a/splitmix-distributions.cabal b/splitmix-distributions.cabal
--- a/splitmix-distributions.cabal
+++ b/splitmix-distributions.cabal
@@ -1,5 +1,5 @@
 name:           splitmix-distributions
-version:        1.0.0
+version:        1.1.0
 description:    Random samplers for some common distributions, as well as a convenient interface for composing them, based on splitmix. Please see the README on GitHub at <https://github.com/ocramz/splitmix-distributions#readme>
 homepage:       https://github.com/ocramz/splitmix-distributions#readme
 bug-reports:    https://github.com/ocramz/splitmix-distributions/issues
@@ -12,7 +12,7 @@
 license-file:   LICENSE
 build-type:     Simple
 cabal-version:  >= 1.10
-tested-with:    GHC == 8.6.5, GHC == 8.10.4
+tested-with:    GHC == 8.6.5, GHC == 8.10.4, GHC == 9.6.6
 extra-source-files:
     README.md
     ChangeLog.md
@@ -35,6 +35,7 @@
                   , splitmix
                   , transformers
   default-language: Haskell2010
+  ghc-options: -Wall
 
 test-suite test
   type: exitcode-stdio-1.0
@@ -44,10 +45,6 @@
   ghc-options: -threaded -rtsopts -with-rtsopts=-N
   build-depends:
       base >=4.7 && <5
-    , erf
     , hspec
-    , mtl
-    , splitmix
     , splitmix-distributions
-    , transformers
   default-language: Haskell2010
diff --git a/src/System/Random/SplitMix/Distributions.hs b/src/System/Random/SplitMix/Distributions.hs
--- a/src/System/Random/SplitMix/Distributions.hs
+++ b/src/System/Random/SplitMix/Distributions.hs
@@ -58,10 +58,14 @@
   zipf,
   crp,
   -- * PRNG
+  SMGenState,
   -- ** Pure
   Gen, sample, samples,
   -- ** Monadic
   GenT, sampleT, samplesT,
+  -- *** Return final PRNG state
+  sampleRunT,
+  samplesRunT,
   -- ** IO-based
   sampleIO, samplesIO,
   -- ** splitmix utilities
@@ -70,6 +74,7 @@
 
 import Control.Monad (replicateM)
 import Control.Monad.IO.Class (MonadIO(..))
+import Data.Bifunctor (second)
 import Data.Foldable (toList)
 import Data.Functor.Identity (Identity(..))
 import Data.List (findIndex)
@@ -87,7 +92,7 @@
 import Control.Monad.Reader (MonadReader(..))
 import Control.Monad.State (MonadState(..), modify)
 -- splitmix
-import System.Random.SplitMix (SMGen, mkSMGen, initSMGen, unseedSMGen, splitSMGen, nextDouble)
+import System.Random.SplitMix (SMGen, mkSMGen, seedSMGen', initSMGen, unseedSMGen, splitSMGen, nextDouble)
 -- transformers
 import Control.Monad.Trans.Reader (ReaderT(..))
 import Control.Monad.Trans.State (StateT(..), runStateT, evalStateT, State, runState, evalState)
@@ -115,6 +120,16 @@
         -> m a
 sampleT seed gg = evalStateT (unGen gg) (mkSMGen seed)
 
+-- | Sample in a monadic context, returning the final PRNG state as well
+--
+-- This makes it possible to have deterministic chains of invocations, for reproducible results
+sampleRunT :: (Functor m) =>
+              SMGenState -- ^ random seed
+           -> GenT m a
+           -> m (a, SMGenState) -- ^ (result, final PRNG state)
+sampleRunT seed gg = second unseedSMGen <$> runStateT (unGen gg) (seedSMGen' seed)
+
+
 -- | Initialize a splitmix random generator from system entropy (current time etc.) and sample from the PRNG.
 sampleIO :: MonadIO m => GenT m b -> m b
 sampleIO gg = do
@@ -128,6 +143,24 @@
          -> GenT m a
          -> m [a]
 samplesT n seed gg = sampleT seed (replicateM n gg)
+
+-- | Sample a batch in a monadic context, returning the final PRNG state as well
+--
+-- Same as @n@ repeated invocations of `sampleRunT`, while threading the PRNG state.
+samplesRunT :: Monad m =>
+               Int -- ^ size of sample
+            -> SMGenState -- ^ random seed
+            -> GenT m a
+            -> m ([a], SMGenState) -- ^ (result, final PRNG state)
+samplesRunT n seed gg = second unseedSMGen <$> runStateT (replicateM n $ unGen gg) (seedSMGen' seed)
+
+-- | Internal state of the splitmix PRNG.
+--
+-- This representation has the benefit of being serializable (e.g. can be passed back and forth between API calls)
+type SMGenState = (Word64, Word64)
+
+-- getSeed :: SMGen -> Word64
+-- getSeed = fst . unseedSMGen
 
 -- | Initialize a splitmix random generator from system entropy (current time etc.) and sample n times from the PRNG.
 samplesIO :: MonadIO m => Int -> GenT m a -> m [a]
