diff --git a/heart-core.cabal b/heart-core.cabal
--- a/heart-core.cabal
+++ b/heart-core.cabal
@@ -4,10 +4,10 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: 1f883982f64deac0d3cefeb13edb5bb554a6e3b717ae53a5a428f51ea1d0fd01
+-- hash: c73018931129ae191793890d4afc45b2d689e79b8aeca338c967169cc97f627c
 
 name:           heart-core
-version:        0.3.1
+version:        0.3.2
 synopsis:       An opinionated library prelude in the UnliftIO style
 description:    Please see the README on GitHub at <https://github.com/ejconlon/heart-core#readme>
 category:       Prelude
diff --git a/src/Heart/Core/Prelude.hs b/src/Heart/Core/Prelude.hs
--- a/src/Heart/Core/Prelude.hs
+++ b/src/Heart/Core/Prelude.hs
@@ -28,6 +28,7 @@
   , MonadThrow (..)
   , MonadTrans (..)
   , MonadUnliftIO (..)
+  , MonadWriter (..)
   , Newtype
   , NFData
   , Prism'
@@ -83,6 +84,7 @@
 import Control.Monad.Reader (MonadReader (..))
 import Control.Monad.State (MonadState (..))
 import Control.Monad.Trans (MonadTrans (..))
+import Control.Monad.Writer (MonadWriter (..))
 import Control.Newtype.Generics (Newtype)
 import Data.Aeson (FromJSON (..), FromJSONKey, ToJSON (..), ToJSONKey)
 import Data.Coerce (Coercible, coerce)
diff --git a/src/Heart/Core/RIO.hs b/src/Heart/Core/RIO.hs
--- a/src/Heart/Core/RIO.hs
+++ b/src/Heart/Core/RIO.hs
@@ -8,17 +8,21 @@
 -}
 module Heart.Core.RIO
   ( HasStateRef (..)
+  , HasWriteRef (..)
   , SomeRef
   , RIO (..)
   , getStateRef
   , liftRIO
+  , listenWriteRef
   , newSomeRef
   , mapRIO
   , modifySomeRef
   , modifyStateRef
+  , passWriteRef
   , putStateRef
   , runRIO
   , readSomeRef
+  , tellWriteRef
   , writeSomeRef
   ) where
 
@@ -88,3 +92,37 @@
 instance HasStateRef st env => MonadState st (RIO env) where
   get = getStateRef
   put = putStateRef
+
+class HasWriteRef w env | env -> w where
+  writeRefL :: Lens' env (SomeRef w)
+
+instance HasWriteRef a (SomeRef a) where
+  writeRefL = id
+
+tellWriteRef :: (HasWriteRef w env, MonadReader env m, MonadIO m, Semigroup w) => w -> m ()
+tellWriteRef value = do
+  ref <- view writeRefL
+  liftIO $ modifySomeRef ref (<> value)
+
+listenWriteRef :: (HasWriteRef w env, MonadReader env m, MonadIO m) => m a -> m (a, w)
+listenWriteRef action = do
+  w1 <- view writeRefL >>= liftIO . readSomeRef
+  a <- action
+  w2 <- do
+    refEnv <- view writeRefL
+    v <- liftIO $ readSomeRef refEnv
+    _ <- liftIO $ writeSomeRef refEnv w1
+    return v
+  return (a, w2)
+
+passWriteRef :: (HasWriteRef w env, MonadReader env m, MonadIO m) => m (a, w -> w) -> m a
+passWriteRef action = do
+  (a, transF) <- action
+  ref <- view writeRefL
+  liftIO $ modifySomeRef ref transF
+  return a
+
+instance (Monoid w, HasWriteRef w env) => MonadWriter w (RIO env) where
+  tell = tellWriteRef
+  listen = listenWriteRef
+  pass = passWriteRef
