diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Revision history for quickcheck-lockstep
 
+## 0.2.1 -- 2022-12-06
+
+* Expose necessary definitions for custom `Operation` instances (Joris Dral)
+
 ## 0.2.0 -- 2022-11-11
 
 * Relax bounds on `base` (support up to ghc 9.4)
diff --git a/quickcheck-lockstep.cabal b/quickcheck-lockstep.cabal
--- a/quickcheck-lockstep.cabal
+++ b/quickcheck-lockstep.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.4
 name:               quickcheck-lockstep
-version:            0.2.0
+version:            0.2.1
 license:            BSD-3-Clause
 license-file:       LICENSE
 author:             Edsko de Vries
@@ -13,6 +13,7 @@
                     APIs calls, then execute them both against the system under
                     test and against a model, and compare responses up to some
                     notion of observability.
+tested-with:        GHC ==8.10.7 || ==9.0.2 || ==9.2.4 || ==9.4.2
 
 source-repository head
   type:     git
@@ -48,6 +49,7 @@
   exposed-modules:
       Test.QuickCheck.StateModel.Lockstep
       Test.QuickCheck.StateModel.Lockstep.Defaults
+      Test.QuickCheck.StateModel.Lockstep.Op
       Test.QuickCheck.StateModel.Lockstep.Op.Identity
       Test.QuickCheck.StateModel.Lockstep.Op.SumProd
       Test.QuickCheck.StateModel.Lockstep.Run
@@ -55,7 +57,6 @@
       Test.QuickCheck.StateModel.Lockstep.API
       Test.QuickCheck.StateModel.Lockstep.EnvF
       Test.QuickCheck.StateModel.Lockstep.GVar
-      Test.QuickCheck.StateModel.Lockstep.Op
   build-depends:
       -- quickcheck-dynamic requires ghc 8.10 minimum
       base               >= 4.14 && < 4.18
diff --git a/src/Test/QuickCheck/StateModel/Lockstep/Op.hs b/src/Test/QuickCheck/StateModel/Lockstep/Op.hs
--- a/src/Test/QuickCheck/StateModel/Lockstep/Op.hs
+++ b/src/Test/QuickCheck/StateModel/Lockstep/Op.hs
@@ -3,6 +3,7 @@
   , InterpretOp(..)
   , WrapRealized(..)
   , intOpRealizedId
+  , intOpTransformer
   ) where
 
 import Test.QuickCheck.StateModel (Realized)
@@ -27,8 +28,8 @@
 {-------------------------------------------------------------------------------
   Interop with 'Realized'
 
-  We want to execute operations against 'Realized' values, but since that is
-  a newtype, we need to wrap and unwrap in order to be able to give the
+  We want to execute operations against 'Realized' values, but since that is a
+  type family, we need to wrap and unwrap in order to be able to give the
   appropriate 'InterpretOp' instances.
 -------------------------------------------------------------------------------}
 
@@ -44,3 +45,22 @@
   => (op a b -> a -> Maybe b)
   -> op a b -> WrapRealized m a -> Maybe (WrapRealized m b)
 intOpRealizedId = coerce
+
+-- | Convenience function for defining 'InterpretOp' instances for monad
+-- transformer stacks.
+intOpTransformer ::
+     forall t m a b op.
+     ( Realized (t m) a ~ Realized m a
+     , Realized (t m) b ~ Realized m b
+     , InterpretOp op (WrapRealized m)
+     )
+  => op a b
+  -> WrapRealized (t m) a
+  -> Maybe (WrapRealized (t m) b)
+intOpTransformer op wr = coerceOut <$> intOp op (coerceIn wr)
+  where
+    coerceIn :: WrapRealized (t m) a -> WrapRealized m a
+    coerceIn = coerce
+
+    coerceOut :: WrapRealized m b -> WrapRealized (t m) b
+    coerceOut = coerce
diff --git a/src/Test/QuickCheck/StateModel/Lockstep/Op/SumProd.hs b/src/Test/QuickCheck/StateModel/Lockstep/Op/SumProd.hs
--- a/src/Test/QuickCheck/StateModel/Lockstep/Op/SumProd.hs
+++ b/src/Test/QuickCheck/StateModel/Lockstep/Op/SumProd.hs
@@ -1,5 +1,4 @@
-{-# LANGUAGE TypeOperators #-}
-module Test.QuickCheck.StateModel.Lockstep.Op.SumProd (Op(..)) where
+module Test.QuickCheck.StateModel.Lockstep.Op.SumProd (Op(..), intOpId) where
 
 import Control.Monad.Reader (ReaderT)
 import Control.Monad.State
@@ -47,11 +46,13 @@
 instance InterpretOp Op (WrapRealized IO) where
   intOp = intOpRealizedId intOpId
 
-instance InterpretOp Op (WrapRealized (ReaderT r IO)) where
-  intOp = intOpRealizedId intOpId
+instance InterpretOp Op (WrapRealized m)
+      => InterpretOp Op (WrapRealized (StateT s m)) where
+  intOp = intOpTransformer
 
-instance InterpretOp Op (WrapRealized (StateT s IO)) where
-  intOp = intOpRealizedId intOpId
+instance InterpretOp Op (WrapRealized m)
+      => InterpretOp Op (WrapRealized (ReaderT r m)) where
+  intOp = intOpTransformer
 
 {-------------------------------------------------------------------------------
   'Show' and 'Eq' instances
diff --git a/test/Test/MockFS.hs b/test/Test/MockFS.hs
--- a/test/Test/MockFS.hs
+++ b/test/Test/MockFS.hs
@@ -25,7 +25,7 @@
 import System.Directory (removeDirectoryRecursive)
 import System.Directory qualified as IO
 import System.IO qualified as IO
-import System.IO.Temp (createTempDirectory)
+import System.IO.Temp (createTempDirectory, getCanonicalTemporaryDirectory)
 import Test.Tasty (TestTree, testGroup)
 import Test.Tasty.HUnit
 import Test.Tasty.QuickCheck (testProperty)
@@ -367,11 +367,12 @@
         QC.labelledExamples $ Lockstep.tagActions (Proxy @FsState)
     , testProperty "propLockstep" $
         Lockstep.runActionsBracket (Proxy @FsState)
-          (createTempDirectory tmpDir "QSM")
+          (createSystemTempDirectory "QSM")
           removeDirectoryRecursive
           runReaderT
     ]
-  where
-    -- TODO: tmpDir should really be a parameter to the test suite
-    tmpDir :: FilePath
-    tmpDir = "./tmp"
+
+createSystemTempDirectory :: [Char] -> IO FilePath
+createSystemTempDirectory prefix = do
+    systemTempDir <- getCanonicalTemporaryDirectory
+    createTempDirectory systemTempDir prefix
