diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,17 @@
 # Revsion history of io-classes
 
+## 1.5.0.0
+
+### Breaking changes
+
+* `MonadST` depends on `PrimMonad`.
+* Provide a default implementation of `withLiftST`.
+* Added `annotateIO` to `MonadThrow` (only supported for ghc-9.10 or newer).
+
+### Non-breaking change
+
+* Add `writeTMVar` to `MonadSTM`.
+
 ## 1.4.1.0
 
 ### Non-breaking changes
diff --git a/io-classes.cabal b/io-classes.cabal
--- a/io-classes.cabal
+++ b/io-classes.cabal
@@ -1,6 +1,6 @@
 cabal-version:       3.0
 name:                io-classes
-version:             1.4.1.0
+version:             1.5.0.0
 synopsis:            Type classes for concurrency with STM, ST and timing
 description:
   IO Monad class hierarchy compatible with
@@ -19,7 +19,7 @@
 build-type:          Simple
 extra-doc-files:     CHANGELOG.md README.md
 bug-reports:         https://github.com/input-output-hk/io-sim/issues
-tested-with:         GHC == { 8.10, 9.2, 9.4, 9.6, 9.8 }
+tested-with:         GHC == { 8.10, 9.2, 9.4, 9.6, 9.8, 9.10 }
 
 source-repository head
   type:     git
@@ -93,14 +93,16 @@
                        TypeFamilyDependencies
                        TypeOperators
                        UndecidableInstances
-  build-depends:       base  >=4.9 && <4.20,
+  build-depends:       base  >=4.9 && <4.21,
                        array,
                        async >=2.1,
                        bytestring,
                        mtl   >=2.2 && <2.4,
-                       primitive >= 0.7 && <0.10,
+                       primitive >= 0.7 && <0.11,
                        stm   >=2.5 && <2.6,
                        time  >=1.9.1 && <1.13
+  if impl(ghc >= 9.10)
+    build-depends:     ghc-internal
 
   if flag(asserts)
     ghc-options: -fno-ignore-asserts
diff --git a/src/Control/Concurrent/Class/MonadSTM/TMVar.hs b/src/Control/Concurrent/Class/MonadSTM/TMVar.hs
--- a/src/Control/Concurrent/Class/MonadSTM/TMVar.hs
+++ b/src/Control/Concurrent/Class/MonadSTM/TMVar.hs
@@ -16,6 +16,7 @@
   , readTMVar
   , tryReadTMVar
   , swapTMVar
+  , writeTMVar
   , isEmptyTMVar
     -- * MonadLabelledSTM
   , labelTMVar
diff --git a/src/Control/Monad/Class/MonadST.hs b/src/Control/Monad/Class/MonadST.hs
--- a/src/Control/Monad/Class/MonadST.hs
+++ b/src/Control/Monad/Class/MonadST.hs
@@ -6,7 +6,7 @@
 import Control.Monad.Reader
 
 import Control.Monad.Primitive
-import Control.Monad.ST as ST (ST, stToIO)
+import Control.Monad.ST (ST)
 
 
 -- | This class is for abstracting over 'stToIO' which allows running 'ST'
@@ -36,18 +36,18 @@
 -- allows us to instantiate it with @RealWorld@ in the @IO@ case, and the local
 -- @s@ in a case where we are embedding into another @ST@ action.
 --
-class Monad m => MonadST m where
+class PrimMonad m => MonadST m where
   -- | @since 1.4.1.0
   stToIO :: ST (PrimState m) a -> m a
 
   -- | Deprecated. Use 'stToIO' instead.
   withLiftST :: (forall s. (forall a. ST s a -> m a) -> b) -> b
+  withLiftST = \k -> k stToIO
 
 {-# DEPRECATED withLiftST "Use the simpler 'stToIO' instead." #-}
 
 instance MonadST IO where
   stToIO = stToPrim
-  withLiftST = \f -> f ST.stToIO
 
 instance MonadST (ST s) where
   stToIO = stToPrim
diff --git a/src/Control/Monad/Class/MonadSTM/Internal.hs b/src/Control/Monad/Class/MonadSTM/Internal.hs
--- a/src/Control/Monad/Class/MonadSTM/Internal.hs
+++ b/src/Control/Monad/Class/MonadSTM/Internal.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE CPP                        #-}
 {-# LANGUAGE DataKinds                  #-}
 {-# LANGUAGE DefaultSignatures          #-}
 {-# LANGUAGE DerivingStrategies         #-}
@@ -45,6 +46,7 @@
   , readTMVarDefault
   , tryReadTMVarDefault
   , swapTMVarDefault
+  , writeTMVarDefault
   , isEmptyTMVarDefault
   , labelTMVarDefault
   , traceTMVarDefault
@@ -189,6 +191,7 @@
   readTMVar       :: TMVar m a      -> STM m a
   tryReadTMVar    :: TMVar m a      -> STM m (Maybe a)
   swapTMVar       :: TMVar m a -> a -> STM m a
+  writeTMVar      :: TMVar m a -> a -> STM m ()
   isEmptyTMVar    :: TMVar m a      -> STM m Bool
 
   type TQueue m  :: Type -> Type
@@ -569,6 +572,11 @@
   readTMVar      = STM.readTMVar
   tryReadTMVar   = STM.tryReadTMVar
   swapTMVar      = STM.swapTMVar
+#if MIN_VERSION_stm(2, 5, 1)
+  writeTMVar     = STM.writeTMVar
+#else
+  writeTMVar     = writeTMVar'
+#endif
   isEmptyTMVar   = STM.isEmptyTMVar
   newTQueue      = STM.newTQueue
   readTQueue     = STM.readTQueue
@@ -740,6 +748,9 @@
     Nothing  -> retry
     Just old -> do writeTVar t (Just new); return old
 
+writeTMVarDefault :: MonadSTM m => TMVarDefault m a -> a -> STM m ()
+writeTMVarDefault (TMVar t) new = writeTVar t (Just new)
+
 isEmptyTMVarDefault :: MonadSTM m => TMVarDefault m a -> STM m Bool
 isEmptyTMVarDefault (TMVar t) = do
   m <- readTVar t
@@ -1188,6 +1199,7 @@
     readTMVar      = lift .  readTMVar
     tryReadTMVar   = lift .  tryReadTMVar
     swapTMVar      = lift .: swapTMVar
+    writeTMVar     = lift .: writeTMVar
     isEmptyTMVar   = lift .  isEmptyTMVar
 
     type TQueue (ReaderT r m) = TQueue m
@@ -1238,3 +1250,10 @@
 
 (.:) :: (c -> d) -> (a -> b -> c) -> (a -> b -> d)
 (f .: g) x y = f (g x y)
+
+-- TODO: writeTMVar was introduced in stm-2.5.1. But io-sim supports stm older than that
+-- Therefore this can be removed once we don't need backwards compatibility with stm.
+#if !MIN_VERSION_stm(2,5,1)
+writeTMVar' :: STM.TMVar a -> a -> STM.STM ()
+writeTMVar' t new = STM.tryTakeTMVar t >> STM.putTMVar t new
+#endif
diff --git a/src/Control/Monad/Class/MonadThrow.hs b/src/Control/Monad/Class/MonadThrow.hs
--- a/src/Control/Monad/Class/MonadThrow.hs
+++ b/src/Control/Monad/Class/MonadThrow.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE CPP                       #-}
 {-# LANGUAGE DefaultSignatures         #-}
 {-# LANGUAGE DeriveFunctor             #-}
 {-# LANGUAGE ExistentialQuantification #-}
@@ -34,18 +35,30 @@
 import Control.Monad.STM (STM)
 import Control.Monad.STM qualified as STM
 
+#if __GLASGOW_HASKELL__ >= 910
+import GHC.Internal.Exception.Context (ExceptionAnnotation)
+#endif
+
 -- | Throwing exceptions, and resource handling in the presence of exceptions.
 --
 -- Does not include the ability to respond to exceptions.
 --
 class Monad m => MonadThrow m where
 
+#if __GLASGOW_HASKELL__ >= 910
+  {-# MINIMAL throwIO, annotateIO #-}
+#else
   {-# MINIMAL throwIO #-}
+#endif
+
   throwIO :: Exception e => e -> m a
 
   bracket  :: m a -> (a -> m b) -> (a -> m c) -> m c
   bracket_ :: m a -> m b -> m c -> m c
   finally  :: m a -> m b -> m a
+#if __GLASGOW_HASKELL__ >= 910
+  annotateIO :: forall e a. ExceptionAnnotation e => e -> m a -> m a
+#endif
 
   default bracket :: MonadCatch m => m a -> (a -> m b) -> (a -> m c) -> m c
 
@@ -206,11 +219,14 @@
 
 instance MonadThrow IO where
 
-  throwIO  = IO.throwIO
+  throwIO    = IO.throwIO
 
-  bracket  = IO.bracket
-  bracket_ = IO.bracket_
-  finally  = IO.finally
+  bracket    = IO.bracket
+  bracket_   = IO.bracket_
+  finally    = IO.finally
+#if __GLASGOW_HASKELL__ >= 910
+  annotateIO = IO.annotateIO
+#endif
 
 
 instance MonadCatch IO where
@@ -249,6 +265,9 @@
 
 instance MonadThrow STM where
   throwIO = STM.throwSTM
+#if __GLASGOW_HASKELL__ >= 910
+  annotateIO ann io = io `catch` \e -> throwIO (IO.addExceptionContext ann e)
+#endif
 
 instance MonadCatch STM where
   catch  = STM.catchSTM
@@ -273,6 +292,10 @@
       (      runReaderT acquire     env)
       (\a -> runReaderT (release a) env)
       (\a -> runReaderT (use a)     env)
+#if __GLASGOW_HASKELL__ >= 910
+  annotateIO ann io = ReaderT $ \env ->
+    annotateIO ann (runReaderT io env)
+#endif
 
 instance MonadCatch m => MonadCatch (ReaderT r m) where
   catch act handler = ReaderT $ \env ->
