diff --git a/box.cabal b/box.cabal
--- a/box.cabal
+++ b/box.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.4
 name:               box
-version:            0.8.1
+version:            0.9.0
 synopsis:           boxes
 description:        A profunctor effect
 category:           project
@@ -12,7 +12,7 @@
 license:            BSD-3-Clause
 license-file:       LICENSE
 build-type:         Simple
-tested-with:        GHC ==8.8.4 || ==8.10.7 || ==9.2.1
+tested-with:        GHC ==8.8.4 || ==8.10.7 || ==9.2.2
 extra-source-files: ChangeLog.md
 
 source-repository head
@@ -41,8 +41,9 @@
     -Wunused-packages
 
   build-depends:
+    , async           ^>= 2.2
     , base            >=4.7   && <5
-    , concurrency     ^>=1.11
+    , bytestring      ^>=0.11
     , containers      ^>=0.6
     , contravariant   ^>=1.5
     , dlist           ^>=1.0
@@ -51,7 +52,8 @@
     , mtl             ^>=2.2.2
     , profunctors     ^>=5.6
     , semigroupoids   ^>=5.3
-    , text            ^>=1.2
+    , stm             ^>= 2.5.1
+    , text            >=1.2 && < 2.1
     , time            ^>=1.9
     , transformers    ^>=0.5
 
diff --git a/src/Box/Box.hs b/src/Box/Box.hs
--- a/src/Box/Box.hs
+++ b/src/Box/Box.hs
@@ -17,6 +17,8 @@
     foistb,
     glue,
     glueN,
+    glueES,
+    glueS,
     fuse,
     Divap (..),
     DecAlt (..),
@@ -52,6 +54,7 @@
 -- >>> import Box
 -- >>> import Data.Text (pack)
 -- >>> import Data.Bool
+-- >>> import Control.Monad.State.Lazy
 
 -- | A Box is a product of a 'Committer' and an 'Emitter'.
 --
@@ -89,6 +92,22 @@
 -- 3
 glue :: (Monad m) => Committer m a -> Emitter m a -> m ()
 glue c e = fix $ \rec -> emit e >>= maybe (pure False) (commit c) >>= bool (pure ()) rec
+
+-- | Connect a Stateful emitter to a (non-stateful) committer of the same type, supplying initial state.
+--
+-- >>> glueES 0 (showStdout) <$|> (takeE 2 <$> qList [1..3])
+-- 1
+-- 2
+glueES :: (Monad m) => s -> Committer m a -> Emitter (StateT s m) a -> m ()
+glueES s c e = flip evalStateT s $ glue (foist lift c) e
+
+-- | Connect a Stateful emitter to a (similarly-stateful) committer of the same type, supplying initial state.
+--
+-- >>> glueS 0 (foist lift showStdout) <$|> (takeE 2 <$> qList [1..3])
+-- 1
+-- 2
+glueS :: (Monad m) => s -> Committer (StateT s m) a -> Emitter (StateT s m) a -> m ()
+glueS s c e = flip evalStateT s $ glue c e
 
 -- | Glues a committer and emitter, and takes n emits
 --
diff --git a/src/Box/Codensity.hs b/src/Box/Codensity.hs
--- a/src/Box/Codensity.hs
+++ b/src/Box/Codensity.hs
@@ -51,7 +51,7 @@
 process :: forall a m r. (a -> m r) -> Codensity m a -> m r
 process f k = runCodensity k f
 
-infixr 3 <$|>
+infixr 0 <$|>
 
 -- | fmap then close over a Codensity
 --
diff --git a/src/Box/Committer.hs b/src/Box/Committer.hs
--- a/src/Box/Committer.hs
+++ b/src/Box/Committer.hs
@@ -103,8 +103,7 @@
 -- 2
 -- 3
 listC :: (Monad m) => Committer m a -> Committer m [a]
-listC c = Committer $ \as ->
-  or <$> sequence (commit c <$> as)
+listC c = Committer $ fmap or . mapM (commit c)
 
 -- | Push to a state sequence.
 --
diff --git a/src/Box/Connectors.hs b/src/Box/Connectors.hs
--- a/src/Box/Connectors.hs
+++ b/src/Box/Connectors.hs
@@ -10,16 +10,22 @@
 -- | Various ways to connect things up.
 module Box.Connectors
   ( qList,
+    qListWith,
     popList,
     pushList,
     pushListN,
     sink,
+    sinkWith,
     source,
+    sourceWith,
     forkEmit,
     bufferCommitter,
     bufferEmitter,
     concurrentE,
     concurrentC,
+    takeQ,
+    evalEmitter,
+    evalEmitterWith,
   )
 where
 
@@ -29,8 +35,7 @@
 import Box.Emitter
 import Box.Functor
 import Box.Queue
-import Control.Concurrent.Classy.Async as C
-import Control.Monad.Conc.Class (MonadConc)
+import Control.Concurrent.Async
 import Control.Monad.State.Lazy
 import Data.Foldable
 import qualified Data.Sequence as Seq
@@ -43,13 +48,20 @@
 -- >>> import Data.Bool
 -- >>> import Control.Monad
 
--- | Queue a list.
+-- | Queue a list Unbounded.
 --
 -- >>> pushList <$|> qList [1,2,3]
 -- [1,2,3]
-qList :: (MonadConc m) => [a] -> CoEmitter m a
-qList xs = emitQ Unbounded (\c -> fmap and (traverse (commit c) xs))
+qList :: [a] -> CoEmitter IO a
+qList xs = qListWith Unbounded xs
 
+-- | Queue a list with an explicit 'Queue'.
+--
+-- >>> pushList <$|> qListWith Single [1,2,3]
+-- [1,2,3]
+qListWith :: Queue a -> [a] -> CoEmitter IO a
+qListWith q xs = emitQ q (\c -> fmap and (traverse (commit c) xs))
+
 -- | Directly supply a list to a committer action, via pop.
 --
 -- >>> popList [1..3] showStdout
@@ -57,7 +69,7 @@
 -- 2
 -- 3
 popList :: Monad m => [a] -> Committer m a -> m ()
-popList xs c = flip evalStateT (Seq.fromList xs) $ glue (foist lift c) pop
+popList xs c = glueES (Seq.fromList xs) c pop
 
 -- | Push an Emitter into a list, via push.
 --
@@ -81,28 +93,42 @@
     Nothing -> pure ()
     Just a' -> f a'
 
--- | Create a finite Committer.
+-- FIXME: This doctest sometimes fails with the last value not being printed. Hypothesis: the pipe collapses before the console print effect happens.
+
+-- | Create a finite Committer Unbounded Queue.
 --
--- >>> glue <$> sink 2 print <*|> qList [1..3]
+-- > glue <$> sink 2 print <*|> qList [1..3]
 -- 1
 -- 2
-sink :: (MonadConc m) => Int -> (a -> m ()) -> CoCommitter m a
-sink n f = commitQ Unbounded $ replicateM_ n . sink1 f
+sink :: Int -> (a -> IO ()) -> CoCommitter IO a
+sink n f = sinkWith Unbounded n f
 
+-- | Create a finite Committer Queue.
+sinkWith :: Queue a -> Int -> (a -> IO ()) -> CoCommitter IO a
+sinkWith q n f = commitQ q $ replicateM_ n . sink1 f
+
 -- singleton source
 source1 :: (Monad m) => m a -> Committer m a -> m ()
 source1 a c = do
   a' <- a
   void $ commit c a'
 
--- | Create a finite Emitter.
+-- | Create a finite (Co)Emitter Unbounded Queue.
 --
 -- >>> glue toStdout <$|> source 2 (pure "hi")
 -- hi
 -- hi
-source :: (MonadConc m) => Int -> m a -> CoEmitter m a
-source n f = emitQ Unbounded $ replicateM_ n . source1 f
+source :: Int -> IO a -> CoEmitter IO a
+source n f = sourceWith Unbounded n f
 
+-- | Create a finite (Co)Emitter Unbounded Queue.
+--
+-- >>> glue toStdout <$|> sourceWith Single 2 (pure "hi")
+-- hi
+-- hi
+sourceWith :: Queue a -> Int -> IO a -> CoEmitter IO a
+sourceWith q n f = emitQ q $ replicateM_ n . source1 f
+
 -- | Glues an emitter to a committer, then resupplies the emitter.
 --
 -- >>> (c1,l1) <- refCommitter :: IO (Committer IO Int, IO [Int])
@@ -119,11 +145,11 @@
     pure a
 
 -- | Buffer a committer.
-bufferCommitter :: (MonadConc m) => Committer m a -> CoCommitter m a
+bufferCommitter :: Committer IO a -> CoCommitter IO a
 bufferCommitter c = Codensity $ \caction -> queueL Unbounded caction (glue c)
 
 -- | Buffer an emitter.
-bufferEmitter :: (MonadConc m) => Emitter m a -> CoEmitter m a
+bufferEmitter :: Emitter IO a -> CoEmitter IO a
 bufferEmitter e = Codensity $ \eaction -> queueR Unbounded (`glue` e) eaction
 
 -- | Concurrently run two emitters.
@@ -140,13 +166,12 @@
 -- > (c,l) <- refCommitter :: IO (Committer IO Int, IO [Int])
 -- > close $ glue c <$> (join $ concurrentE Single <$> qList [1..30] <*> qList [40..60])
 concurrentE ::
-  MonadConc f =>
   Queue a ->
-  Emitter f a ->
-  Emitter f a ->
-  CoEmitter f a
+  Emitter IO a ->
+  Emitter IO a ->
+  CoEmitter IO a
 concurrentE q e e' =
-  Codensity $ \eaction -> snd . fst <$> C.concurrently (queue q (`glue` e) eaction) (queue q (`glue` e') eaction)
+  Codensity $ \eaction -> snd . fst <$> concurrently (queue q (`glue` e) eaction) (queue q (`glue` e') eaction)
 
 -- | Concurrently run two committers.
 --
@@ -161,26 +186,45 @@
 -- slow: 1
 -- slow: 2
 -- slow: 3
-concurrentC :: (MonadConc m) => Queue a -> Committer m a -> Committer m a -> CoCommitter m a
+concurrentC :: Queue a -> Committer IO a -> Committer IO a -> CoCommitter IO a
 concurrentC q c c' = mergeC <$> eitherC q c c'
 
 eitherC ::
-  (MonadConc m) =>
   Queue a ->
-  Committer m a ->
-  Committer m a ->
-  Codensity m (Either (Committer m a) (Committer m a))
+  Committer IO a ->
+  Committer IO a ->
+  Codensity IO (Either (Committer IO a) (Committer IO a))
 eitherC q cl cr =
   Codensity $
     \kk ->
       fst
-        <$> C.concurrently
+        <$> concurrently
           (queueL q (kk . Left) (glue cl))
           (queueL q (kk . Right) (glue cr))
 
-mergeC :: Either (Committer m a) (Committer m a) -> Committer m a
+mergeC :: Either (Committer IO a) (Committer IO a) -> Committer IO a
 mergeC ec =
   Committer $ \a ->
     case ec of
       Left lc -> commit lc a
       Right rc -> commit rc a
+
+-- | Take and queue n emits.
+--
+-- >>> import Control.Monad.State.Lazy
+-- >>> toListM <$|> (takeQ Single 4 =<< qList [0..])
+-- [0,1,2,3]
+takeQ :: Queue a -> Int -> Emitter IO a -> CoEmitter IO a
+takeQ q n e = emitQ q $ \c -> glueES 0 c (takeE n e)
+
+-- | queue a stateful emitter, supplying initial state
+--
+-- >>> import Control.Monad.State.Lazy
+-- >>> toListM <$|> (evalEmitter 0 <$> takeE 4 =<< qList [0..])
+-- [0,1,2,3]
+evalEmitter :: s -> Emitter (StateT s IO) a -> CoEmitter IO a
+evalEmitter s e = evalEmitterWith Unbounded s e
+
+-- | queue a stateful emitter, supplying initial state
+evalEmitterWith :: Queue a -> s -> Emitter (StateT s IO) a -> CoEmitter IO a
+evalEmitterWith q s e = emitQ q $ \c -> glueES s c e
diff --git a/src/Box/Emitter.hs b/src/Box/Emitter.hs
--- a/src/Box/Emitter.hs
+++ b/src/Box/Emitter.hs
@@ -7,6 +7,7 @@
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE UndecidableInstances #-}
 {-# OPTIONS_GHC -Wall #-}
+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
 {-# OPTIONS_GHC -fno-warn-type-defaults #-}
 
 -- | `emit`
@@ -19,6 +20,7 @@
     unlistE,
     takeE,
     takeUntilE,
+    dropE,
     pop,
   )
 where
@@ -181,6 +183,16 @@
 takeE :: (Monad m) => Int -> Emitter m a -> Emitter (StateT Int m) a
 takeE n (Emitter e) =
   Emitter $ get >>= \n' -> bool (pure Nothing) (put (n' + 1) >> lift e) (n' < n)
+
+-- | Drop n emits.
+--
+-- >>> import Control.Monad.State.Lazy
+-- >>> toListM <$|> (dropE 2 =<< qList [0..3])
+-- [2,3]
+dropE :: (Monad m) => Int -> Emitter m a -> CoEmitter m a
+dropE n e = Codensity $ \k -> do
+  replicateM_ n (emit e)
+  k e
 
 -- | Take from an emitter until a predicate.
 --
diff --git a/src/Box/IO.hs b/src/Box/IO.hs
--- a/src/Box/IO.hs
+++ b/src/Box/IO.hs
@@ -16,13 +16,22 @@
     toStdoutN,
     readStdin,
     showStdout,
-    handleE,
-    handleC,
     refCommitter,
     refEmitter,
+    handleE,
+    handleC,
     fileE,
-    fileWriteC,
-    fileAppendC,
+    fileC,
+    fileEText,
+    fileEBS,
+    fileCText,
+    fileCBS,
+    logConsoleC,
+    logConsoleE,
+    pauser,
+    changer,
+    quit,
+    restart,
   )
 where
 
@@ -30,14 +39,18 @@
 import Box.Committer
 import Box.Connectors
 import Box.Emitter
-import qualified Control.Concurrent.Classy.IORef as C
+import Control.Concurrent.Async
 import Control.Exception
-import qualified Control.Monad.Conc.Class as C
+import Control.Monad.State.Lazy
 import Data.Bool
+import Data.ByteString.Char8 as Char8
 import Data.Foldable
+import Data.Function
 import Data.Functor.Contravariant
+import Data.IORef
 import qualified Data.Sequence as Seq
-import Data.Text as Text
+import Data.String
+import Data.Text as Text hiding (null)
 import Data.Text.IO as Text
 import System.IO as IO
 import Prelude
@@ -81,9 +94,11 @@
 fromStdinN :: Int -> CoEmitter IO Text
 fromStdinN n = source n Text.getLine
 
+-- FIXME: This doctest sometimes fails with the last value not being printed. Hypothesis: the pipe collapses before the console print effect happens.
+
 -- | Finite console committer
 --
--- >>> glue <$> contramap (pack . show) <$> (toStdoutN 2) <*|> qList [1..3]
+-- > glue <$> contramap (pack . show) <$> (toStdoutN 2) <*|> qList [1..3]
 -- 1
 -- 2
 toStdoutN :: Int -> CoCommitter IO Text
@@ -96,6 +111,7 @@
 -- 1
 -- hippo
 -- 2
+-- 2
 readStdin :: Read a => Emitter IO a
 readStdin = witherE (pure . either (const Nothing) Just) . readE $ fromStdin
 
@@ -109,44 +125,77 @@
 showStdout = contramap (Text.pack . show) toStdout
 
 -- | Emits lines of Text from a handle.
-handleE :: Handle -> Emitter IO Text
-handleE h = Emitter $ do
-  l :: (Either IOException Text) <- try (Text.hGetLine h)
+-- handleEText = handleE Text.hGetLine
+
+-- handleEBS = handleE Char8.hGetLine
+
+-- | Emits lines of Text from a handle.
+handleE :: (IsString a, Eq a) => (Handle -> IO a) -> Handle -> Emitter IO a
+handleE action h = Emitter $ do
+  l :: (Either IOException a) <- try (action h)
   pure $ case l of
     Left _ -> Nothing
     Right a -> bool (Just a) Nothing (a == "")
 
 -- | Commit lines of Text to a handle.
-handleC :: Handle -> Committer IO Text
-handleC h = Committer $ \a -> do
-  Text.hPutStrLn h a
+handleC :: (Handle -> a -> IO ()) -> Handle -> Committer IO a
+handleC action h = Committer $ \a -> do
+  action h a
   pure True
 
+-- | Commit lines of Text to a handle.
+-- handleCBS = handleC Char8.hPutStrLn
+
+-- | Emits lines of Text from a handle.
+-- handleCText = handleC Text.hPutStrLn
+
 -- | Emit lines of Text from a file.
-fileE :: FilePath -> CoEmitter IO Text
-fileE fp = Codensity $ \eio -> withFile fp ReadMode (eio . handleE)
+fileE :: FilePath -> BufferMode -> IOMode -> (Handle -> Emitter IO a) -> CoEmitter IO a
+fileE fp b m action = Codensity $ \eio ->
+  withFile
+    fp
+    m
+    ( \h -> do
+        hSetBuffering h b
+        eio (action h)
+    )
 
+fileEText :: FilePath -> BufferMode -> CoEmitter IO Text
+fileEText fp b = fileE fp b ReadMode (handleE Text.hGetLine)
+
+fileEBS :: FilePath -> BufferMode -> CoEmitter IO ByteString
+fileEBS fp b = fileE fp b ReadMode (handleE Char8.hGetLine)
+
 -- | Commit lines of Text to a file.
-fileWriteC :: FilePath -> CoCommitter IO Text
-fileWriteC fp = Codensity $ \cio -> withFile fp WriteMode (cio . handleC)
+fileC :: FilePath -> IOMode -> BufferMode -> (Handle -> Committer IO a) -> CoCommitter IO a
+fileC fp m b action = Codensity $ \cio ->
+  withFile
+    fp
+    m
+    ( \h -> do
+        hSetBuffering h b
+        cio (action h)
+    )
 
--- | Commit lines of Text, appending to a file.
-fileAppendC :: FilePath -> CoCommitter IO Text
-fileAppendC fp = Codensity $ \cio -> withFile fp AppendMode (cio . handleC)
+fileCText :: FilePath -> BufferMode -> IOMode -> CoCommitter IO Text
+fileCText fp m b = fileC fp b m (handleC Text.hPutStrLn)
 
+fileCBS :: FilePath -> BufferMode -> IOMode -> CoCommitter IO ByteString
+fileCBS fp m b = fileC fp b m (handleC Char8.hPutStrLn)
+
 -- | Commit to an IORef
 --
 -- >>> (c1,l1) <- refCommitter :: IO (Committer IO Int, IO [Int])
 -- >>> glue c1 <$|> qList [1..3]
 -- >>> l1
 -- [1,2,3]
-refCommitter :: (C.MonadConc m) => m (Committer m a, m [a])
+refCommitter :: IO (Committer IO a, IO [a])
 refCommitter = do
-  ref <- C.newIORef Seq.empty
+  ref <- newIORef Seq.empty
   let c = Committer $ \a -> do
-        C.modifyIORef ref (Seq.:|> a)
+        modifyIORef ref (Seq.:|> a)
         pure True
-  let res = toList <$> C.readIORef ref
+  let res = toList <$> readIORef ref
   pure (c, res)
 
 -- | Emit from a list IORef
@@ -154,14 +203,77 @@
 -- >>> e <- refEmitter [1..3]
 -- >>> toListM e
 -- [1,2,3]
-refEmitter :: (C.MonadConc m) => [a] -> m (Emitter m a)
+refEmitter :: [a] -> IO (Emitter IO a)
 refEmitter xs = do
-  ref <- C.newIORef xs
+  ref <- newIORef xs
   let e = Emitter $ do
-        as <- C.readIORef ref
+        as <- readIORef ref
         case as of
           [] -> pure Nothing
           (x : xs') -> do
-            C.writeIORef ref xs'
+            writeIORef ref xs'
             pure $ Just x
   pure e
+
+-- | simple console logger for rough testing
+logConsoleE :: Show a => String -> Emitter IO a -> Emitter IO a
+logConsoleE label e = Emitter $ do
+  a <- emit e
+  Prelude.putStrLn (label <> show a)
+  pure a
+
+-- | simple console logger for rough testing
+logConsoleC :: Show a => String -> Committer IO a -> Committer IO a
+logConsoleC label c = Committer $ \a -> do
+  Prelude.putStrLn (label <> show a)
+  commit c a
+
+-- | Pause an emitter based on a Bool emitter
+pauser :: Emitter IO Bool -> Emitter IO a -> Emitter IO a
+pauser b e = Emitter $ fix $ \rec -> do
+  b' <- emit b
+  case b' of
+    Nothing -> pure Nothing
+    Just False -> emit e
+    Just True -> rec
+
+-- | Create an emitter that indicates when another emitter has changed.
+changer :: (Eq a) => a -> Emitter IO a -> CoEmitter IO Bool
+changer a0 e = evalEmitter a0 $ Emitter $ do
+  r <- lift $ emit e
+  case r of
+    Nothing -> pure Nothing
+    Just r' -> do
+      r'' <- get
+      put r'
+      pure (Just (r' == r''))
+
+-- | quit a process based on a Bool emitter
+--
+-- > quit <$> speedEffect (pure 2) <$> (resetGap 5) <*|> pure io
+-- 0
+-- 1
+-- 2
+-- 3
+-- 4
+-- Left True
+quit :: Emitter IO Bool -> IO a -> IO (Either Bool a)
+quit flag io = race (checkE flag) io
+
+checkE :: Emitter IO Bool -> IO Bool
+checkE e = fix $ \rec -> do
+  a <- emit e
+  -- atomically $ check (a == Just False)
+  case a of
+    Nothing -> pure False
+    Just True -> pure True
+    Just False -> rec
+
+-- | restart a process if flagged by a Bool emitter
+restart :: Emitter IO Bool -> IO a -> IO (Either Bool a)
+restart flag io = fix $ \rec -> do
+  res <- quit flag io
+  case res of
+    Left True -> rec
+    Left False -> pure (Left False)
+    Right r -> pure (Right r)
diff --git a/src/Box/Queue.hs b/src/Box/Queue.hs
--- a/src/Box/Queue.hs
+++ b/src/Box/Queue.hs
@@ -1,7 +1,6 @@
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE StrictData #-}
@@ -18,6 +17,9 @@
     fromAction,
     emitQ,
     commitQ,
+    fromActionWith,
+    toBoxM,
+    toBoxSTM,
   )
 where
 
@@ -27,10 +29,9 @@
 import Box.Emitter
 import Box.Functor
 import Control.Applicative
-import Control.Concurrent.Classy.Async as C
-import Control.Concurrent.Classy.STM as C
+import Control.Concurrent.Async
+import Control.Concurrent.STM
 import Control.Monad.Catch as C
-import Control.Monad.Conc.Class as C
 import Prelude
 
 -- $setup
@@ -48,7 +49,7 @@
   | New
 
 -- | create a queue, supplying the ends and a sealing function.
-ends :: MonadSTM stm => Queue a -> stm (a -> stm (), stm a)
+ends :: Queue a -> STM (a -> STM (), STM a)
 ends qu =
   case qu of
     Bounded n -> do
@@ -72,7 +73,7 @@
       pure (write, readTBQueue q)
 
 -- | write to a queue, checking the seal
-writeCheck :: (MonadSTM stm) => TVar stm Bool -> (a -> stm ()) -> a -> stm Bool
+writeCheck :: TVar Bool -> (a -> STM ()) -> a -> STM Bool
 writeCheck sealed i a = do
   b <- readTVar sealed
   if b
@@ -82,23 +83,22 @@
       pure True
 
 -- | read from a queue, and retry if not sealed
-readCheck :: MonadSTM stm => TVar stm Bool -> stm a -> stm (Maybe a)
+readCheck :: TVar Bool -> STM a -> STM (Maybe a)
 readCheck sealed o =
   (Just <$> o)
     <|> ( do
             b <- readTVar sealed
-            C.check b
+            check b
             pure Nothing
         )
 
 -- | turn a queue into a box (and a seal)
 toBoxSTM ::
-  (MonadSTM stm) =>
   Queue a ->
-  stm (Box stm a a, stm ())
+  STM (Box STM a a, STM ())
 toBoxSTM q = do
   (i, o) <- ends q
-  sealed <- newTVarN "sealed" False
+  sealed <- newTVar False
   let seal = writeTVar sealed True
   pure
     ( Box
@@ -109,90 +109,85 @@
 
 -- | turn a queue into a box (and a seal), and lift from stm to the underlying monad.
 toBoxM ::
-  (MonadConc m) =>
   Queue a ->
-  m (Box m a a, m ())
+  IO (Box IO a a, IO ())
 toBoxM q = do
   (b, s) <- atomically $ toBoxSTM q
   pure (liftB b, atomically s)
 
 -- | run two actions concurrently, but wait and return on the left result.
-concurrentlyLeft :: MonadConc m => m a -> m b -> m a
+concurrentlyLeft :: IO a -> IO b -> IO a
 concurrentlyLeft left right =
-  C.withAsync left $ \a ->
-    C.withAsync right $ \_ ->
-      C.wait a
+  withAsync left $ \a ->
+    withAsync right $ \_ ->
+      wait a
 
 -- | run two actions concurrently, but wait and return on the right result.
-concurrentlyRight :: MonadConc m => m a -> m b -> m b
+concurrentlyRight :: IO a -> IO b -> IO b
 concurrentlyRight left right =
-  C.withAsync left $ \_ ->
-    C.withAsync right $ \b ->
-      C.wait b
+  withAsync left $ \_ ->
+    withAsync right $ \b ->
+      wait b
 
 -- | connect a committer and emitter action via spawning a queue, and wait for the Committer action to complete.
 withQL ::
-  (MonadConc m) =>
   Queue a ->
-  (Queue a -> m (Box m a a, m ())) ->
-  (Committer m a -> m l) ->
-  (Emitter m a -> m r) ->
-  m l
+  (Queue a -> IO (Box IO a a, IO ())) ->
+  (Committer IO a -> IO l) ->
+  (Emitter IO a -> IO r) ->
+  IO l
 withQL q spawner cio eio =
-  C.bracket
+  bracket
     (spawner q)
     snd
     ( \(box, seal) ->
         concurrentlyLeft
-          (cio (committer box) `C.finally` seal)
-          (eio (emitter box) `C.finally` seal)
+          (cio (committer box) `finally` seal)
+          (eio (emitter box) `finally` seal)
     )
 
 -- | connect a committer and emitter action via spawning a queue, and wait for the Emitter action to complete.
 withQR ::
-  (MonadConc m) =>
   Queue a ->
-  (Queue a -> m (Box m a a, m ())) ->
-  (Committer m a -> m l) ->
-  (Emitter m a -> m r) ->
-  m r
+  (Queue a -> IO (Box IO a a, IO ())) ->
+  (Committer IO a -> IO l) ->
+  (Emitter IO a -> IO r) ->
+  IO r
 withQR q spawner cio eio =
-  C.bracket
+  bracket
     (spawner q)
     snd
     ( \(box, seal) ->
         concurrentlyRight
-          (cio (committer box) `C.finally` seal)
-          (eio (emitter box) `C.finally` seal)
+          (cio (committer box) `finally` seal)
+          (eio (emitter box) `finally` seal)
     )
 
 -- | connect a committer and emitter action via spawning a queue, and wait for both to complete.
 withQ ::
-  (MonadConc m) =>
   Queue a ->
-  (Queue a -> m (Box m a a, m ())) ->
-  (Committer m a -> m l) ->
-  (Emitter m a -> m r) ->
-  m (l, r)
+  (Queue a -> IO (Box IO a a, IO ())) ->
+  (Committer IO a -> IO l) ->
+  (Emitter IO a -> IO r) ->
+  IO (l, r)
 withQ q spawner cio eio =
-  C.bracket
+  bracket
     (spawner q)
     snd
     ( \(box, seal) ->
         concurrently
-          (cio (committer box) `C.finally` seal)
-          (eio (emitter box) `C.finally` seal)
+          (cio (committer box) `finally` seal)
+          (eio (emitter box) `finally` seal)
     )
 
 -- | Create an unbounded queue, returning the result from the Committer action.
 --
 -- >>> queueL New (\c -> glue c <$|> qList [1..3]) toListM
 queueL ::
-  (MonadConc m) =>
   Queue a ->
-  (Committer m a -> m l) ->
-  (Emitter m a -> m r) ->
-  m l
+  (Committer IO a -> IO l) ->
+  (Emitter IO a -> IO r) ->
+  IO l
 queueL q cm em = withQL q toBoxM cm em
 
 -- | Create an unbounded queue, returning the result from the Emitter action.
@@ -200,11 +195,10 @@
 -- >>> queueR New (\c -> glue c <$|> qList [1..3]) toListM
 -- [3]
 queueR ::
-  (MonadConc m) =>
   Queue a ->
-  (Committer m a -> m l) ->
-  (Emitter m a -> m r) ->
-  m r
+  (Committer IO a -> IO l) ->
+  (Emitter IO a -> IO r) ->
+  IO r
 queueR q cm em = withQR q toBoxM cm em
 
 -- | Create an unbounded queue, returning both results.
@@ -212,32 +206,42 @@
 -- >>> queue Unbounded (\c -> glue c <$|> qList [1..3]) toListM
 -- ((),[1,2,3])
 queue ::
-  (MonadConc m) =>
   Queue a ->
-  (Committer m a -> m l) ->
-  (Emitter m a -> m r) ->
-  m (l, r)
+  (Committer IO a -> IO l) ->
+  (Emitter IO a -> IO r) ->
+  IO (l, r)
 queue q cm em = withQ q toBoxM cm em
 
 -- | lift a box from STM
-liftB :: (MonadConc m) => Box (STM m) a b -> Box m a b
+liftB :: Box STM a b -> Box IO a b
 liftB (Box c e) = Box (foist atomically c) (foist atomically e)
 
 -- | Turn a box action into a box continuation
-fromAction :: (MonadConc m) => (Box m a b -> m r) -> CoBox m b a
+fromAction :: (Box IO a b -> IO r) -> CoBox IO b a
 fromAction baction = Codensity $ fuseActions baction
 
--- | Connect up two box actions via two queues
-fuseActions :: (MonadConc m) => (Box m a b -> m r) -> (Box m b a -> m r') -> m r'
+-- | Turn a box action into a box continuation
+fromActionWith :: Queue a -> Queue b -> (Box IO a b -> IO r) -> CoBox IO b a
+fromActionWith qa qb baction = Codensity $ fuseActionsWith qa qb baction
+
+-- | Connect up two box actions via two Unbounded queues
+fuseActions :: (Box IO a b -> IO r) -> (Box IO b a -> IO r') -> IO r'
 fuseActions abm bam = do
   (Box ca ea, _) <- toBoxM Unbounded
   (Box cb eb, _) <- toBoxM Unbounded
   concurrentlyRight (abm (Box ca eb)) (bam (Box cb ea))
 
+-- | Connect up two box actions via two queues
+fuseActionsWith :: Queue a -> Queue b -> (Box IO a b -> IO r) -> (Box IO b a -> IO r') -> IO r'
+fuseActionsWith qa qb abm bam = do
+  (Box ca ea, _) <- toBoxM qa
+  (Box cb eb, _) <- toBoxM qb
+  concurrentlyRight (abm (Box ca eb)) (bam (Box cb ea))
+
 -- | Hook a committer action to a queue, creating an emitter continuation.
-emitQ :: (MonadConc m) => Queue a -> (Committer m a -> m r) -> CoEmitter m a
+emitQ :: Queue a -> (Committer IO a -> IO r) -> CoEmitter IO a
 emitQ q cio = Codensity $ \eio -> queueR q cio eio
 
 -- | Hook a committer action to a queue, creating an emitter continuation.
-commitQ :: (MonadConc m) => Queue a -> (Emitter m a -> m r) -> CoCommitter m a
+commitQ :: Queue a -> (Emitter IO a -> IO r) -> CoCommitter IO a
 commitQ q eio = Codensity $ \cio -> queueL q cio eio
diff --git a/src/Box/Time.hs b/src/Box/Time.hs
--- a/src/Box/Time.hs
+++ b/src/Box/Time.hs
@@ -10,19 +10,28 @@
 -- | Timing effects.
 module Box.Time
   ( sleep,
-    Stamped (..),
     stampNow,
     stampE,
-    emitIn,
+    Gap,
+    gaps,
+    fromGaps,
+    fromGapsNow,
+    gapEffect,
+    skip,
     replay,
+    gapSkipEffect,
+    speedEffect,
+    speedSkipEffect,
   )
 where
 
-import Box.Codensity
+import Box.Connectors
 import Box.Emitter
 import Control.Applicative
-import Control.Monad.Conc.Class as C
-import Control.Monad.IO.Class
+import Control.Concurrent
+import Control.Monad.State.Lazy
+import Data.Bifunctor
+import Data.Bool
 import Data.Fixed (Fixed (MkFixed))
 import Data.Time
 import Prelude
@@ -33,8 +42,8 @@
 -- >>> import Prelude
 
 -- | Sleep for x seconds.
-sleep :: (MonadConc m) => Double -> m ()
-sleep x = C.threadDelay (floor $ x * 1e6)
+sleep :: Double -> IO ()
+sleep x = threadDelay (floor $ x * 1e6)
 
 -- | convenience conversion to Double
 fromNominalDiffTime :: NominalDiffTime -> Double
@@ -52,58 +61,136 @@
       t1 = UTCTime (addDays days d0) (picosecondsToDiffTime $ floor (secs / 1.0e-12))
    in diffUTCTime t1 t0
 
--- | A value with a UTCTime annotation.
-data Stamped a = Stamped
-  { stamp :: !UTCTime,
-    value :: !a
-  }
-  deriving (Eq, Show, Read)
-
 -- | Add the current time
-stampNow :: (MonadConc m, MonadIO m) => a -> m (LocalTime, a)
+stampNow :: a -> IO (LocalTime, a)
 stampNow a = do
-  t <- liftIO getCurrentTime
+  t <- getCurrentTime
   pure (utcToLocalTime utc t, a)
 
 -- | Add the current time stamp.
 --
 -- @
--- > process (toListM . stampE) (qList [1..3])
--- [(2022-02-09 01:18:00.293883,1),(2022-02-09 01:18:00.293899,2),(2022-02-09 01:18:00.293903,3)]
+-- > toListM . stampE <$|> (qList [1..3])
+-- [(2022-08-30 01:55:16.517127,1),(2022-08-30 01:55:16.517132,2),(2022-08-30 01:55:16.517135,3)]
 -- @
 stampE ::
-  (MonadConc m, MonadIO m) =>
-  Emitter m a ->
-  Emitter m (LocalTime, a)
+  Emitter IO a ->
+  Emitter IO (LocalTime, a)
 stampE = witherE (fmap Just . stampNow)
 
--- | Wait s seconds before emitting
-emitIn ::
-  Emitter IO (Double, a) ->
-  Emitter IO a
-emitIn =
-  witherE
-    ( \(s, a) -> do
-        sleep s
-        pure $ Just a
-    )
+type Gap = Double
 
--- | Convert emitter stamps to adjusted speed delays
-delay :: (Monad m, Alternative m) => Double -> Emitter m (LocalTime, b) -> m (Emitter m (Double, b))
-delay speed e = do
-  r <- emit e
+-- | Convert stamped emitter to gap between emits in seconds
+--
+-- > toListM <$|> (gaps =<< (fromGapsNow =<< (qList (zip (0:repeat 1) [1..4]))))
+-- [(0.0,1),(1.0,2),(1.0,3),(1.0,4)]
+gaps :: Emitter IO (LocalTime, a) -> CoEmitter IO (Gap, a)
+gaps e = evalEmitter Nothing $ Emitter $ do
+  r <- lift $ emit e
   case r of
-    Nothing -> pure mempty
-    Just (t0, _) -> do
-      let delta u = fromNominalDiffTime $ diffLocalTime u t0 * toNominalDiffTime speed
-      pure (witherE (\(l, a) -> pure (Just (delta l, a))) e)
+    Nothing -> pure Nothing
+    Just a' -> do
+      t' <- get
+      let delta u = maybe 0 (fromNominalDiffTime . diffLocalTime u) t'
+      put (Just $ fst a')
+      pure $ Just $ first delta a'
 
+-- | Convert gaps in seconds to stamps starting from an initial supplied 'LocalTime'
+fromGaps :: LocalTime -> Emitter IO (Gap, a) -> CoEmitter IO (LocalTime, a)
+fromGaps t0 e = evalEmitter t0 $ Emitter $ do
+  r <- lift $ emit e
+  case r of
+    Nothing -> pure Nothing
+    Just a' -> do
+      t' <- get
+      let t'' = addLocalTime (toNominalDiffTime (fst a')) t'
+      put t''
+      pure $ Just (t'', snd a')
+
+-- | Convert gaps in seconds to stamps starting with current time
+--
+-- > toListM <$|> (fromGapsNow =<< (qList (zip (0:repeat 1) [1..4])))
+-- [(2022-08-30 22:57:33.835228,1),(2022-08-30 22:57:34.835228,2),(2022-08-30 22:57:35.835228,3),(2022-08-30 22:57:36.835228,4)]
+fromGapsNow :: Emitter IO (Gap, a) -> CoEmitter IO (LocalTime, a)
+fromGapsNow e = do
+  t0 <- liftIO getCurrentTime
+  fromGaps (utcToLocalTime utc t0) e
+
+-- | Convert a (Gap,a) emitter to an a emitter, with delays between emits of the gap.
+gapEffect ::
+  Emitter IO (Gap, a) ->
+  Emitter IO a
+gapEffect as =
+  Emitter $ do
+    a <- emit as
+    case a of
+      (Just (s, a')) -> sleep s >> pure (Just a')
+      _ -> pure Nothing
+
+speedEffect ::
+  Emitter IO Gap ->
+  Emitter IO (Gap, a) ->
+  Emitter IO a
+speedEffect speeds as =
+  Emitter $ do
+    s <- emit speeds
+    a <- emit as
+    case (s, a) of
+      (Just s', Just (g, a')) -> sleep (g / s') >> pure (Just a')
+      _ -> pure Nothing
+
+-- | Only add a Gap effect if greater than the Int emitter
+--
+-- effect is similar to a fast-forward of the first n emits
+gapSkipEffect ::
+  Emitter IO Int ->
+  Emitter IO Gap ->
+  CoEmitter IO Gap
+gapSkipEffect n e = evalEmitter 0 $ Emitter $ do
+  n' <- lift $ emit n
+  e' <- lift $ emit e
+  count <- get
+  modify (1 +)
+  case (n', e') of
+    (_, Nothing) -> pure Nothing
+    (Nothing, _) -> pure Nothing
+    (Just n'', Just e'') ->
+      pure $ Just (bool e'' 0 (n'' >= count))
+
+-- | Only add a Gap if greater than the Int emitter
+--
+-- effect is similar to a fast-forward of the first n emits
+speedSkipEffect ::
+  Emitter IO (Int, Gap) ->
+  Emitter IO (Gap, a) ->
+  CoEmitter IO a
+speedSkipEffect p e = evalEmitter 0 $ Emitter $ do
+  p' <- lift $ emit p
+  e' <- lift $ emit e
+  count <- get
+  modify (1 +)
+  case (p', e') of
+    (_, Nothing) -> pure Nothing
+    (Nothing, _) -> pure Nothing
+    (Just (n, s), Just (g, a)) ->
+      lift $ sleep (bool (g / s) 0 (n >= count)) >> pure (Just a)
+
+skip :: Int -> Emitter IO (Gap, a) -> CoEmitter IO (Gap, a)
+skip sk e = evalEmitter (sk + 1) $ Emitter $ do
+  skip' <- get
+  e' <- lift $ emit e
+  case e' of
+    Nothing -> pure Nothing
+    Just (secs, a) -> do
+      case skip' of
+        0 -> pure (Just (secs, a))
+        _ -> do
+          put (skip' - 1)
+          pure (Just (0, a))
+
 -- | Replay a stamped emitter, adjusting the speed of the replay.
 --
--- @
--- > glueN 4 showStdout <$|> replay 1 (Emitter $ sleep 0.1 >> Just <$> stampNow ())
--- @
-replay :: Double -> Emitter IO (LocalTime, a) -> CoEmitter IO a
-replay speed e = Codensity $ \eaction -> do
-  e' <- delay speed e
-  eaction (emitIn e')
+-- > toListM . stampE <$|> (replay 0.1 1 =<< (fromGapsNow =<< (qList (zip (0:repeat 1) [1..4]))))
+-- [(2022-08-31 02:29:39.643831,1),(2022-08-31 02:29:39.643841,2),(2022-08-31 02:29:39.746998,3),(2022-08-31 02:29:39.849615,4)]
+replay :: Double -> Int -> Emitter IO (LocalTime, a) -> CoEmitter IO a
+replay speed sk e = gapEffect . fmap (first (speed *)) <$> (skip sk =<< gaps e)
