diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,8 @@
-# 0.2.1.0
+# 0.2.4.0
+
+* Add `Bluefin.CloneHandle` and `Bluefin.IO.withEffToIOCloneHandle`
+
+# 0.2.3.0
 
 * Add `Bluefin.DslBuilder`
 
diff --git a/bluefin.cabal b/bluefin.cabal
--- a/bluefin.cabal
+++ b/bluefin.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               bluefin
-version:            0.2.3.0
+version:            0.2.4.0
 license:            MIT
 license-file:       LICENSE
 author:             Tom Ellis
@@ -24,6 +24,7 @@
       Bluefin.Compound,
       Bluefin.Consume,
       Bluefin.Coroutine,
+      Bluefin.CloneableHandle,
       Bluefin.DslBuilder,
       Bluefin.EarlyReturn,
       Bluefin.Eff,
@@ -40,6 +41,6 @@
       Bluefin.System.IO,
       Bluefin.Writer,
     build-depends:
-      bluefin-internal >= 0.3.1.0 && < 0.4
+      bluefin-internal >= 0.3.2.0 && < 0.4
     hs-source-dirs:   src
     default-language: Haskell2010
diff --git a/src/Bluefin/CloneableHandle.hs b/src/Bluefin/CloneableHandle.hs
new file mode 100644
--- /dev/null
+++ b/src/Bluefin/CloneableHandle.hs
@@ -0,0 +1,92 @@
+-- | @Bluefin.CloneableHandle@ defines the 'CloneableHandle' class,
+-- whose purpose is to support 'withEffToIOCloneHandle'.
+
+module Bluefin.CloneableHandle
+  ( -- | 'withEffToIOCloneHandle' is an @IO@ unlifting function that
+    -- clones its handle each time it runs @Eff@ in @IO@.  This is
+    -- convenient when the unlifting function is being used to fork
+    -- threads, since Bluefin state is not threadsafe.  Be careful
+    -- when you use it, because it can be used to throw away the
+    -- effect tag on a Bluefin @Eff@ action due to this part of
+    -- its type (here throwing away @e@):
+    --
+    -- @
+    -- (forall e. IOE e -> h e -> Eff e r) -> IO r
+    -- @
+    --
+    -- It is only safely used when you do not allow any effects to
+    -- escape their scope, so we suggest that you use it sparingly to
+    -- define reusable combinators which themselves are safe.  For
+    -- example, here is how you could write an equivalent of @async@'s
+    -- @race@ primitive:
+    --
+    -- @
+    -- bluefinRace ::
+    --   ('CloneableHandle' h, e1 :> es) =>
+    --   t'Bluefin.IO.IOE' e1 ->
+    --   h es ->
+    --   (forall e. IOE e -> h e -> t'Bluefin.Eff.Eff' e r) ->
+    --   (forall e. IOE e -> h e -> Eff e r) ->
+    --   Eff es r
+    -- bluefinRace io h m1 m2 = withEffToIOCloneHandle io h $ \\runInIO -> do
+    --  either id id
+    --    \<$\> Control.Concurrent.Async.race
+    --      (runInIO $ \\io' h' -> m1 io' h')
+    --      (runInIO $ \\io' h' -> m2 io' h')
+    -- @
+    --
+    -- Then you can safely use it to race Bluefin @Eff@ actions:
+    --
+    -- @
+    -- example :: IO ()
+    -- example = 'Bluefin.Eff.runEff_' $ \\io -> 'Bluefin.State.evalState' 0 $ \\st -> do
+    --   r \<- 'Bluefin.Exception.try' $ \\ex -> do
+    --     bluefinRace
+    --       io
+    --       (MkMyHandle ('Bluefin.Handle.mapHandle' ex) (mapHandle st))
+    --       ( \\_ (MkMyHandle ex' st') -> do
+    --           'Bluefin.State.modify' st' (subtract 2000)
+    --           'Bluefin.Exception.throw' ex' "Aborting from branch 1"
+    --       )
+    --       ( \\_ (MkMyHandle _ st') -> do
+    --           modify st' (+ 3000)
+    --           pure (2 :: Int)
+    --       )
+    --
+    --   s <- 'Bluefin.State.get' st
+    --   'Bluefin.IO.effIO' io (print r)
+    --   effIO io (putStrLn ("State started at 0 and was cloned. Now: " <> show s))
+    -- @
+    --
+    -- You can see from the output that the actions were raced as
+    -- expected, and the @State@ was cloned so that changes to it in
+    -- the branches of @race@ did not affect the original @State@.
+    --
+    -- @
+    -- -- Run one time (the first thread was faster)
+    -- ghci> example
+    -- Right 2
+    -- State started at 0 and was cloned. Now: 0
+    -- -- Run another time (the second thread was faster)
+    -- ghci> example
+    -- Left "Aborting from branch 1"
+    -- State started at 0 and was cloned. Now: 0
+    -- @
+    --
+    -- Note that @withEffToIOCloneHandle@ only allows access to /one/
+    -- external @Handle@ within it, so if you have several you'd like
+    -- to use you'll have to define a new handle that bundles them
+    -- together.
+    withEffToIOCloneHandle,
+
+    -- * @CloneableHandle@
+    CloneableHandle,
+    GenericCloneableHandle(MkGenericCloneableHandle),
+    GCloneableHandle,
+
+    -- * @GHC.Generics@ re-exports
+    Generic1,
+  )
+where
+
+import Bluefin.Internal.CloneableHandle
diff --git a/src/Bluefin/IO.hs b/src/Bluefin/IO.hs
--- a/src/Bluefin/IO.hs
+++ b/src/Bluefin/IO.hs
@@ -12,6 +12,7 @@
     -- * IO type classes
     withMonadIO,
     withEffToIO_,
+    withEffToIOCloneHandle,
     -- ** @EffReader@
     EffReader,
     effReader,
@@ -22,3 +23,4 @@
 where
 
 import Bluefin.Internal
+import Bluefin.Internal.CloneableHandle
