diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -2,10 +2,12 @@
 
 ## Unreleased Changes
 
+## 0.3.0.0 - October 3 2025
+- Deprecate `interpretReadlineAsInputT` as it wasn't possible to support interrupts using it alone
+- Bump GHC versions
+
 ## 0.2.0.0 - July 27th 2021
-- Add `runReadlineFinal` and improved instructions for mitigating lack of
-  support for repl history when using `runReadline` in the simplest way
-  possible.
+- Add `runReadlineFinal` and improved instructions for mitigating lack of support for repl history when using `runReadline` in the simplest way possible.
 
 ## 0.1.0.0 — June 24th 2021
 - Release candidate for Hackage.
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -6,9 +6,8 @@
 
 ## Contributions
 Bug reports and PRs are welcome. In particular there are a number of things that I don't use frequently enough from Haskeline to justify working on or just haven't gotten around to implementing yet:
-- interrupt handling: `withInterrupt`, `handleInterrupt`
+- version bumps for dependencies that already compile but aren't allowed
 - pure interpreter for use in tests
 - support for older versions of Haskeline (currently only 0.8.1+ is supported)
-- version bumps for dependencies that already compile but aren't allowed
 
 PRs for any of these things would be greatly appreciated or I will try to get around to implementing them myself later 🙂.
diff --git a/examples/Echo.hs b/examples/Echo.hs
--- a/examples/Echo.hs
+++ b/examples/Echo.hs
@@ -17,6 +17,5 @@
 main =
   runInputT defaultSettings
     . runFinal
-    . embedToFinal
-    . interpretReadlineAsInputT
+    . runReadlineFinal
     $ repl
diff --git a/polysemy-readline.cabal b/polysemy-readline.cabal
--- a/polysemy-readline.cabal
+++ b/polysemy-readline.cabal
@@ -1,8 +1,8 @@
-cabal-version: 2.2
+cabal-version: 3.0
 
 name:           polysemy-readline
 synopsis:       Readline effect for polysemy.
-version:        0.2.0.0
+version:        0.3.0.0
 category:       User Interfaces, Effect
 homepage:       https://github.com/lehmacdj/polysemy-readline#readme
 bug-reports:    https://github.com/lehmacdj/polysemy-readline/issues
@@ -12,7 +12,10 @@
 license:        BSD-2-Clause
 license-file:   LICENSE
 build-type:     Simple
-extra-source-files:
+tested-with:    GHC == 9.2.5
+              , GHC == 9.10.1
+              , GHC == 9.12.1
+extra-doc-files:
     README.md
     ChangeLog.md
 description:
@@ -31,11 +34,11 @@
                -flate-specialise
                -fspecialise-aggressively
   build-depends:
-      base >=4.12 && <4.15
+      base >=4.12 && <5
     , exceptions >=0.10.4 && <0.11
     , haskeline >=0.8.1 && <0.9.0
-    , polysemy >=1.5.0 && <1.6
-    , polysemy-plugin >=0.3.0 && <0.4
+    , polysemy >=1.5.0 && <1.10
+    , polysemy-plugin >=0.3.0 && <0.5
   default-language: Haskell2010
 
 common exe-options
diff --git a/src/Polysemy/Readline.hs b/src/Polysemy/Readline.hs
--- a/src/Polysemy/Readline.hs
+++ b/src/Polysemy/Readline.hs
@@ -20,70 +20,64 @@
     waitForAnyKey,
     outputStr,
     outputStrLn,
+    withInterrupt,
+    handleInterrupt,
 
     -- * Interpreters
     runReadline,
     runReadlineFinal,
-    interpretReadlineAsInputT,
 
     -- * Re-exports from @haskeline@
     H.Settings (..),
     H.defaultSettings,
     H.runInputT,
+
+    -- * Deprecated
+    interpretReadlineAsInputT,
   )
 where
 
 import Control.Monad.Catch
 import Control.Monad.IO.Class
 import Polysemy
-import Polysemy.Embed
+import Polysemy.Final
 import qualified System.Console.Haskeline as H
 
 -- | For documentation on actions see haskeline's functions with the same name
 -- and similar type signatures.
-data Readline (m :: * -> *) a where
+data Readline m a where
   GetInputLine :: String -> Readline m (Maybe String)
   GetInputLineWithInitial :: String -> (String, String) -> Readline m (Maybe String)
   GetInputChar :: String -> Readline m (Maybe Char)
   GetPassword :: Maybe Char -> String -> Readline m (Maybe String)
   WaitForAnyKey :: String -> Readline m Bool
   OutputStr :: String -> Readline m ()
-
--- TODO(Devin): add these two values as well
--- WithInterrupt :: m a -> Readline m a
--- HandleInterrupt :: m a -> m a -> Readline m a
+  WithInterrupt :: m a -> Readline m a
+  HandleInterrupt :: m a -> m a -> Readline m a
 
 makeSem ''Readline
 
-outputStrLn :: Member Readline r => String -> Sem r ()
+outputStrLn :: (Member Readline r) => String -> Sem r ()
 outputStrLn str = outputStr (str <> "\n")
 
--- | The simplest way to run a Readline effect. Immediately eliminates the
--- resulting 'H.InputT'. There is one problem with this approach however.
--- Internal details of polysemy cause 'H.runInputT' to be run once per effect
--- call (e.g. @getInputLine "> " >> getInputLine "> "@ will result in two calls
--- to 'H.runInputT'), and the History state of consecutive runs is not
--- preserved unless there is a history file. If you want history for your repl
--- there are therefore two recommended approaches:
+-- | The simplest way to run a Readline effect. Runs the entire Readline
+-- computation in a single 'H.runInputT' call, preserving history state between
+-- effect calls. This is the recommended approach for most use cases.
 --
--- * Provide a history file in the settings you specify. e.g.
--- @runReadline ('H.defaultSettings' {historyFile = Just ".repl_history"})@.
--- This is the easiest approach but technically suboptimal because the history
--- file will be read between every different primitive effect call.
--- * Use 'interpretReadlineAsInputT' or 'runReadlineFinal' and keep the
--- `H.InputT` around until after using 'runFinal' to escape polysemy land. This
--- way state can be preserved between effect calls. For an example using this
--- see @examples/Echo.hs@.
+-- For more control over when 'H.runInputT' is invoked, use 'runReadlineFinal'
+-- directly and manage the 'H.InputT' context yourself. See @examples/Echo.hs@
+-- for an example.
 runReadline ::
-  forall m r a.
-  (MonadIO m, MonadMask m, Member (Embed m) r) =>
+  forall m a.
+  (MonadIO m, MonadMask m) =>
   H.Settings m ->
-  Sem (Readline : r) a ->
-  Sem r a
-runReadline settings =
-  runEmbedded (H.runInputT settings)
-    . interpretReadlineAsInputT
-    . raiseUnder @(Embed (H.InputT m))
+  Sem '[Readline] a ->
+  m a
+runReadline settings sem =
+  H.runInputT settings $
+    runFinal $
+      runReadlineFinal $
+        raiseUnder @(Final (H.InputT m)) sem
 
 -- | Interpreter for the somewhat common case of wanting to keep InputT around
 -- until after 'runFinal' to ensure that state is preserved between subsequent
@@ -93,22 +87,38 @@
   (MonadIO m, MonadMask m, Member (Final (H.InputT m)) r) =>
   Sem (Readline : r) a ->
   Sem r a
-runReadlineFinal =
-  embedToFinal
-    . interpretReadlineAsInputT
-    . raiseUnder @(Embed (H.InputT m))
+runReadlineFinal = interpretFinal $ \case
+  GetInputLine prompt -> liftS $ H.getInputLine prompt
+  GetInputLineWithInitial prompt initial -> liftS $ H.getInputLineWithInitial prompt initial
+  GetInputChar prompt -> liftS $ H.getInputChar prompt
+  GetPassword maskChar prompt -> liftS $ H.getPassword maskChar prompt
+  WaitForAnyKey prompt -> liftS $ H.waitForAnyKey prompt
+  OutputStr str -> liftS $ H.outputStr str
+  WithInterrupt action -> do
+    action' <- runS action
+    pure $ H.withInterrupt action'
+  HandleInterrupt handler action -> do
+    handler' <- runS handler
+    action' <- runS action
+    pure $ H.handleInterrupt handler' action'
 
 -- | Interpret in terms of an embedded 'H.InputT' stack.
+--
+-- Using this is deprecated in favor of 'runReadlineFinal'. Errors when
+-- handling 'WithInterrupt' and 'HandleInterrupt', they are not supported.
 interpretReadlineAsInputT ::
   forall m r a.
   (MonadIO m, MonadMask m, Member (Embed (H.InputT m)) r) =>
   Sem (Readline : r) a ->
   Sem r a
-interpretReadlineAsInputT = interpret $ \case
-  GetInputLine prompt -> embed $ H.getInputLine prompt
+interpretReadlineAsInputT = interpretH $ \case
+  GetInputLine prompt -> pureT =<< embed (H.getInputLine prompt)
   GetInputLineWithInitial prompt initial ->
-    embed $ H.getInputLineWithInitial prompt initial
-  GetInputChar prompt -> embed $ H.getInputChar prompt
-  GetPassword maskChar prompt -> embed $ H.getPassword maskChar prompt
-  WaitForAnyKey prompt -> embed $ H.waitForAnyKey prompt
-  OutputStr str -> embed $ H.outputStr str
+    pureT =<< embed (H.getInputLineWithInitial prompt initial)
+  GetInputChar prompt -> pureT =<< embed (H.getInputChar prompt)
+  GetPassword maskChar prompt -> pureT =<< embed (H.getPassword maskChar prompt)
+  WaitForAnyKey prompt -> pureT =<< embed (H.waitForAnyKey prompt)
+  OutputStr str -> pureT =<< embed (H.outputStr str)
+  WithInterrupt _ -> error "interpretReadlineAsInputT: WithInterrupt not supported"
+  HandleInterrupt _ _ -> error "interpretReadlineAsInputT: HandleInterrupt not supported"
+{-# DEPRECATED interpretReadlineAsInputT "Use runReadlineFinal instead" #-}
