diff --git a/NEWS.md b/NEWS.md
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,6 +1,11 @@
 Changes
 =======
 
+0.2.0 (15 Sep 2013)
+-------------------
+
+ - update to work with pipes-4.0.0; thank you, @gabriel439
+
 0.1.2 (05 Apr 2013)
 -------------------
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -94,6 +94,6 @@
 See also
 --------
 
- - `pipes` [The Pipes Tutorial](http://hackage.haskell.org/packages/archive/pipes/latest/doc/html/Control-Pipe-Tutorial.html)
+ - `pipes` [The Pipes Tutorial](http://hackage.haskell.org/packages/archive/pipes/3.2.0/doc/html/Control-Proxy-Tutorial.html)
 
  - `C3` [Wikipedia](https://en.wikipedia.org/wiki/Command,_control,_and_communications#Command.2C_control_and_communications)
diff --git a/daemons.cabal b/daemons.cabal
--- a/daemons.cabal
+++ b/daemons.cabal
@@ -1,5 +1,5 @@
 Name:           daemons
-Version:        0.1.2
+Version:        0.2.0
 Cabal-Version:  >= 1.8
 License:        GPL-3
 License-File:   LICENSE
@@ -39,7 +39,7 @@
   Hs-Source-Dirs:       src
   Build-depends:        base >= 4 && < 5, bytestring, cereal,
                         data-default, directory, filepath, ghc-prim, network,
-                        pipes >= 3.1,
+                        pipes >= 4.0.0,
                         transformers, unix
   Ghc-options:          -Wall
   Exposed-modules:      Control.Pipe.C3,
@@ -63,7 +63,7 @@
 Executable queue
   Build-depends:        base >= 4 && < 5, bytestring, cereal, containers,
                         daemons, data-default, ghc-prim, network,
-                        pipes >= 3.1, transformers
+                        pipes >= 4.0.0, transformers
   Main-Is:              examples/Queue.hs
   Ghc-options:          -Wall
 
diff --git a/examples/Queue.hs b/examples/Queue.hs
--- a/examples/Queue.hs
+++ b/examples/Queue.hs
@@ -6,7 +6,6 @@
 import Control.Concurrent.MVar ( MVar, newMVar, modifyMVar )
 import Control.Monad ( forever )
 import Control.Monad.Trans.Class ( lift )
-import Control.Pipe ( runPipe, (<+<), await, yield )
 import Control.Pipe.Serialize ( serializer, deserializer )
 import Control.Pipe.Socket ( Handler )
 import Data.ByteString.Char8 ( ByteString )
@@ -18,6 +17,7 @@
 import qualified Data.Map as M
 import GHC.Generics
 import Network.Socket ( withSocketsDo )
+import Pipes ( runEffect, (<-<), await, yield )
 import System.Environment ( getArgs )
 import System.Daemon
 import System.IO ( hPutStrLn, stderr )
@@ -37,10 +37,8 @@
 type Registry = M.Map ByteString (Chan ByteString)
 
 handleCommands :: MVar Registry -> Handler ()
-handleCommands registryVar reader writer = do
-    runPipe (writer <+< serializer
-             <+< commandExecuter
-             <+< deserializer <+< reader)
+handleCommands registryVar reader writer = runEffect $
+    writer <-< serializer <-< commandExecuter <-< deserializer <-< reader
   where
     commandExecuter = forever $ do
         comm <- await
@@ -86,10 +84,11 @@
           res <- runClient "localhost" 7857 (Push key value)
           printResult res
       ["consume", key] -> do
-          runClientWithHandler "localhost" 7857 $ \reader writer -> do
-              runPipe (writer <+< serializer <+< yield (Consume key))
-              runPipe ((forever $ await >>= \res -> lift (printResult (Just res)))
-                       <+< deserializer <+< reader)
+          runClientWithHandler "localhost" 7857 $ \reader writer ->
+              runEffect $ do
+                  writer <-< serializer <-< yield (Consume key)
+                  (forever $ await >>= \res -> lift (printResult (Just res)))
+                       <-< deserializer <-< reader
       _ -> do
           error "invalid command"
   where
diff --git a/src/Control/Pipe/C3.hs b/src/Control/Pipe/C3.hs
--- a/src/Control/Pipe/C3.hs
+++ b/src/Control/Pipe/C3.hs
@@ -5,20 +5,20 @@
 
 import Control.Monad ( forever )
 import Control.Monad.Trans.Class ( lift )
-import Control.Pipe ( runPipe, await, yield, (<+<) )
 import Control.Pipe.Serialize ( serializer, deserializer )
 import Control.Pipe.Socket ( Handler )
 import Data.Serialize ( Serialize )
+import Pipes ( runEffect, await, yield, (<-<) )
 
 -- | Send a single command over the outgoing pipe and wait for a
 -- response.  If the incoming pipe is closed before a response
 -- arrives, returns @Nothing@.
 commandSender :: (Serialize a, Serialize b) => a -> Handler (Maybe b)
-commandSender command reader writer = do
-    runPipe (writer <+< serializer <+< sendCommand)
-    runPipe (receiveResponse
-             <+< (deserializer >> return Nothing)
-             <+< (reader >> return Nothing))
+commandSender command reader writer = runEffect $ do
+    writer <-< serializer <-< sendCommand
+    receiveResponse
+        <-< (deserializer >> return Nothing)
+        <-< (reader >> return Nothing)
   where
     sendCommand = do
         yield command
@@ -30,10 +30,8 @@
 -- | Wait for commands on the incoming pipe, handle them, and send the
 -- reponses over the outgoing pipe.
 commandReceiver :: (Serialize a, Serialize b) => (a -> IO b) -> Handler ()
-commandReceiver executeCommand reader writer = do
-    runPipe (writer <+< serializer
-             <+< commandExecuter
-             <+< deserializer <+< reader)
+commandReceiver executeCommand reader writer = runEffect $
+    writer <-< serializer <-< commandExecuter <-< deserializer <-< reader
   where
     commandExecuter = forever $ do
         comm <- await
diff --git a/src/Control/Pipe/Serialize.hs b/src/Control/Pipe/Serialize.hs
--- a/src/Control/Pipe/Serialize.hs
+++ b/src/Control/Pipe/Serialize.hs
@@ -24,7 +24,7 @@
 import Data.ByteString.Char8 ( ByteString )
 import Data.Serialize ( Serialize, get, encode
                       , Result(..), runGetPartial )
-import Control.Pipe ( Pipe, await, yield )
+import Pipes ( Pipe, await, yield )
 import Control.Monad ( forever )
 
 -- | De-serialize data from strict 'ByteString's.  Uses @cereal@'s
diff --git a/src/Control/Pipe/Socket.hs b/src/Control/Pipe/Socket.hs
--- a/src/Control/Pipe/Socket.hs
+++ b/src/Control/Pipe/Socket.hs
@@ -16,12 +16,12 @@
 import Control.Monad ( forever, unless )
 import Control.Monad.IO.Class ( MonadIO(..) )
 import Control.Monad.Trans.Class ( lift )
-import Control.Pipe ( Consumer, Producer, await, yield )
 import Data.ByteString.Char8 ( ByteString )
 import qualified Data.ByteString.Char8 as B
 import Network.Socket ( Socket )
 import qualified Network.Socket as NS
 import Network.Socket.ByteString ( sendAll, recv )
+import Pipes ( Consumer, Producer, await, yield )
 
 -- | Stream data from the socket.
 socketReader :: (MonadIO m) => Socket -> Producer ByteString m ()
diff --git a/src/System/Posix/Daemon.hs b/src/System/Posix/Daemon.hs
--- a/src/System/Posix/Daemon.hs
+++ b/src/System/Posix/Daemon.hs
@@ -33,8 +33,8 @@
 --
 -- > kill "diydns.pid"
 --
--- To stop a job and wait for it to close (release its pidfile), 
--- such as when restarting it), use killAndWait:
+-- To stop a job and wait for it to close (and release its pidfile), use
+-- 'killAndWait':
 --
 -- > killAndWait "diydns.pid" >> doSomething
 --
@@ -187,10 +187,10 @@
 -- | Kill a process and wait for it to release its pidfile
 killAndWait :: FilePath -> IO ()
 killAndWait pidFile = do
-  signalProcessByFilePath sigQUIT pidFile
-  fd <- openFd pidFile ReadWrite Nothing defaultFileFlags
-  waitToSetLock fd (WriteLock, AbsoluteSeek, 0, 0)
-  closeFd fd
+    signalProcessByFilePath sigQUIT pidFile
+    fd <- openFd pidFile ReadWrite Nothing defaultFileFlags
+    waitToSetLock fd (WriteLock, AbsoluteSeek, 0, 0)
+    closeFd fd
 
 -- | Send 'sigKILL' to the process recorded in the pidfile.  This
 -- immediately kills the process.
