diff --git a/core/HaskellWorks/Control/Monad.hs b/core/HaskellWorks/Control/Monad.hs
new file mode 100644
--- /dev/null
+++ b/core/HaskellWorks/Control/Monad.hs
@@ -0,0 +1,32 @@
+module HaskellWorks.Control.Monad
+  ( repeatNUntilM_,
+    repeatNWhileM_,
+  ) where
+
+import           HaskellWorks.Prelude
+
+-- | Repeat an action n times until the action returns True.
+repeatNUntilM_ :: ()
+  => Monad m
+  => Int
+  -> (Int -> m Bool)
+  -> m ()
+repeatNUntilM_ n action = go 0
+  where
+    go i =
+      when (i < n) $ do
+        shouldTerminate <- action i
+        unless shouldTerminate $ go (i + 1)
+
+-- | Repeat an action n times while the action returns True.
+repeatNWhileM_ :: ()
+  => Monad m
+  => Int
+  -> (Int -> m Bool)
+  -> m ()
+repeatNWhileM_ n action = go 0
+  where
+    go i =
+      when (i < n) $ do
+        shouldContinue <- action i
+        when shouldContinue $ go (i + 1)
diff --git a/hw-polysemy.cabal b/hw-polysemy.cabal
--- a/hw-polysemy.cabal
+++ b/hw-polysemy.cabal
@@ -1,6 +1,6 @@
 cabal-version:          3.4
 name:                   hw-polysemy
-version:                0.2.14.4
+version:                0.2.14.5
 synopsis:               Opinionated polysemy library
 description:            Opinionated polysemy library.
 license:                Apache-2.0
@@ -125,7 +125,8 @@
   if os(windows)
     exposed-modules:    HaskellWorks.IO.Win32.NamedPipe
 
-  exposed-modules:      HaskellWorks.Error
+  exposed-modules:      HaskellWorks.Control.Monad
+                        HaskellWorks.Error
                         HaskellWorks.Error.Types
                         HaskellWorks.IO.Network
                         HaskellWorks.IO.Network.NamedPipe
