diff --git a/Shakefile.hs b/Shakefile.hs
new file mode 100644
--- /dev/null
+++ b/Shakefile.hs
@@ -0,0 +1,315 @@
+#!/usr/bin/env stack
+{- stack
+    runghc
+    --package basic-prelude
+    --package directory
+    --package shake
+ -}
+
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- | Shake makefile for project.
+--
+import BasicPrelude
+import Data.Char
+import Development.Shake
+import Development.Shake.FilePath
+import System.Directory
+
+-- | This file used for version change detection.
+--
+thisFile :: FilePath
+thisFile = "Shakefile.hs"
+
+-- | Location of build supporting files.
+--
+buildDir :: FilePath
+buildDir = ".build"
+
+-- | Location of stack's work files.
+--
+stackDir :: FilePath
+stackDir = ".stack-work"
+
+-- | Build directory where "touch" files are kept.
+--
+fakeDir :: FilePath
+fakeDir = buildDir </> "fake"
+
+-- | Build directory where docker files are kept.
+--
+dockerDir :: Action FilePath
+dockerDir = do
+  dir <- liftIO getCurrentDirectory
+  return $ buildDir </> takeFileName dir
+
+-- | Fake directory path builder.
+--
+fd :: FilePath -> FilePath
+fd = (fakeDir </>)
+
+-- | Remove right excess on string.
+--
+rstrip :: String -> String
+rstrip = reverse . dropWhile isSpace . reverse
+
+-- | Typeful command args with return string.
+--
+cmdArgs :: String -> [String] -> Action String
+cmdArgs c as = rstrip . fromStdout <$> cmd c as
+
+-- | Typeful command args with no return.
+--
+cmdArgs_ :: String -> [String] -> Action ()
+cmdArgs_ c as = unit $ cmd c as
+
+-- | Run commands in a dir with return string.
+--
+_cmdArgsDir :: FilePath -> String -> [String] -> Action String
+_cmdArgsDir d c as = rstrip . fromStdout <$> cmd (Cwd d) c as
+
+-- | Run commands in a dir with no return.
+--
+cmdArgsDir_ :: FilePath -> String -> [String] -> Action ()
+cmdArgsDir_ d c as = unit $ cmd (Cwd d) c as
+
+-- | Run docker command in docker dir.
+--
+docker :: [String] -> Action ()
+docker args = do
+  dir <- dockerDir
+  cmdArgsDir_ dir "docker" args
+
+-- | Stack command.
+--
+stack :: [String] -> Action ()
+stack = cmdArgs_ "stack"
+
+-- | Stack exec command.
+--
+_stackExec :: String -> [String] -> Action ()
+_stackExec cmd' args = stack $ "exec" : cmd' : "--" : args
+
+-- | Sylish command.
+--
+stylish :: [String] -> Action ()
+stylish = cmdArgs_ "stylish-haskell"
+
+-- | Lint command.
+--
+lint :: [String] -> Action ()
+lint = cmdArgs_ "hlint"
+
+-- | Git command.
+--
+git :: [String] -> Action String
+git = cmdArgs "git"
+
+-- | m4 command.
+--
+m4 :: [String] -> Action String
+m4 = cmdArgs "m4"
+
+-- | Version.
+--
+version :: Action String
+version = git [ "describe", "--tags", "--abbrev=0" ]
+
+-- | Touch a file for fake files.
+--
+touchFile :: FilePath -> Action ()
+touchFile = flip writeFile' mempty
+
+-- | Copy a file if changed, creating parent directories.
+--
+copyFileChanged' :: FilePath -> FilePath -> Action ()
+copyFileChanged' a b = do
+  liftIO $ createDirectoryIfMissing True $ dropFileName b
+  copyFileChanged a b
+
+-- | Preprocess a file with m4
+--
+preprocess :: FilePattern -> FilePath -> Action [(String, String)] -> Rules ()
+preprocess target file macros =
+  target %> \out -> do
+    need [ file ]
+    let f k v = "-D" <> k <> "=" <> v
+    macros' <- macros
+    content <- m4 $ file : (uncurry f <$> macros')
+    writeFileChanged out content
+
+-- | Use a fake file to keep track of the last time an file-free action ran.
+--
+fake :: [FilePattern] -> String -> ([FilePath] -> Action ()) -> Rules ()
+fake pats target act = do
+  fd target %> \out -> do
+    files <- getDirectoryFiles "." pats
+    need files
+    act files
+    touchFile out
+
+  phony target $
+    need [ fd target ]
+
+-- | Global rules
+--
+globalRules :: Rules ()
+globalRules = do
+  let pats =
+        [ "stack.yaml"
+        , "Shakefile.hs"
+        , "main//*.hs"
+        , "src//*.hs"
+        , "test//*.hs"
+        ]
+
+  -- | wolf.cabal
+  --
+  preprocess "wolf.cabal" "wolf.cabal.m4" $ do
+    v <- version
+    return [ ("VERSION", v) ]
+
+  -- | build
+  --
+  fake pats "build" $ \_files -> do
+    stack [ "build", "--fast" ]
+
+  -- | build-error
+  --
+  fake pats "build-error" $ \_files -> do
+    need [ "wolf.cabal" ]
+    stack [ "build", "--fast", "--ghc-options=-Werror" ]
+
+  -- | build-tests
+  --
+  fake pats "build-tests" $ \_files -> do
+    need [ "wolf.cabal" ]
+    stack [ "build", "--fast", "--test", "--no-run-tests" ]
+
+  -- | build-tests-error
+  --
+  fake pats "build-tests-error" $ \_files -> do
+    need [ "wolf.cabal" ]
+    stack [ "build", "--fast", "--test", "--no-run-tests", "--ghc-options=-Werror" ]
+
+  -- | tests
+  --
+  phony "tests" $ do
+    need [ "wolf.cabal" ]
+    stack [ "build", "--fast", "--test" ]
+
+  -- | tests-error
+  --
+  phony "tests-error" $ do
+    need [ "wolf.cabal" ]
+    stack [ "build", "--fast", "--test", "--ghc-options=-Werror" ]
+
+  -- | ghci
+  --
+  phony "ghci" $ do
+    need [ "wolf.cabal" ]
+    stack [ "ghci", "--fast" ]
+
+  -- | ghci-tests
+  --
+  phony "ghci-tests" $ do
+    need [ "wolf.cabal" ]
+    stack [ "ghci", "--fast", "--test" ]
+
+  -- | install
+  --
+  fake pats "install" $ \_files -> do
+    need [ "wolf.cabal" ]
+    stack [ "build", "--fast", "--copy-bins" ]
+
+  -- | publish
+  --
+  phony "publish" $ do
+    need [ "wolf.cabal" ]
+    stack [ "sdist" ]
+    stack [ "upload", "." ]
+
+  -- | clean
+  --
+  phony "clean" $ do
+    need [ "wolf.cabal" ]
+    stack [ "clean" ]
+    removeFilesAfter buildDir [ "//*" ]
+
+  -- | clear
+  --
+  phony "clear" $
+    forM_ [ fakeDir ] $ \dir ->
+      removeFilesAfter dir [ "//*" ]
+
+  -- | wipe
+  --
+  phony "wipe" $ do
+    removeFilesAfter buildDir [ "//*" ]
+    removeFilesAfter stackDir [ "//*" ]
+
+  -- | sanity
+  --
+  phony "sanity" $
+    need [ "tests-error", "lint" ]
+
+-- | Haskell source rules
+--
+hsRules :: Rules ()
+hsRules = do
+  let pats =
+        [ "Shakefile.hs"
+        , "main//*.hs"
+        , "src//*.hs"
+        , "test//*.hs"
+        ]
+
+  -- | format
+  --
+  fake pats "format" $ \files -> do
+    need [ ".stylish-haskell.yaml" ]
+    stylish $ [ "-c", ".stylish-haskell.yaml", "-i" ] <> files
+
+  -- | lint
+  --
+  fake pats "lint" $ \files ->
+    lint files
+
+-- | Docker rules
+--
+dockerRules :: Rules ()
+dockerRules = do
+  let pats =
+        [ "Dockerfile"
+        , "Shakefile.hs"
+        , ".dockerignore"
+        , "stack.yaml"
+        , "registrar.cabal"
+        , "main//*.hs"
+        , "src//*.hs"
+        ]
+
+  -- | docker:setup
+  --
+  fake pats "docker:setup" $ \files ->
+    forM_ files $ \file -> do
+      dir <- dockerDir
+      copyFileChanged' file $ dir </> file
+
+  -- | docker:build
+  --
+  phony "docker:build" $ do
+    need [ "docker:setup" ]
+    docker [ "build", "." ]
+
+-- | Main entry point
+--
+main :: IO ()
+main = do
+  v <- getHashedShakeVersion [thisFile]
+  shakeArgs shakeOptions { shakeFiles = buildDir, shakeVersion = v } $ do
+    want [ "tests-error", "lint", "format" ]
+    globalRules
+    hsRules
+    dockerRules
diff --git a/main/Act.hs b/main/Act.hs
--- a/main/Act.hs
+++ b/main/Act.hs
@@ -1,25 +1,27 @@
-{-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE FlexibleContexts    #-}
+{-# LANGUAGE RecordWildCards     #-}
+{-# LANGUAGE ScopedTypeVariables #-}
 module Act
   ( main
   ) where
 
-import           BasicPrelude hiding ( ByteString, (</>), (<.>), hash, length, readFile, find )
+import           BasicPrelude                 hiding (ByteString, find, hash, length,
+                                               readFile, (<.>), (</>))
 import           Codec.Compression.GZip
 import           Control.Monad.Trans.Resource
 import           Data.Aeson.Encode
-import           Data.ByteString ( length )
-import qualified Data.ByteString.Lazy as BL
-import           Data.Text ( pack, strip )
-import           Data.Text.Lazy ( toStrict )
-import           Data.Text.Lazy.Builder hiding ( fromText )
-import           Data.Yaml hiding ( Parser )
-import           Filesystem.Path ( (<.>), dropExtension )
+import           Data.ByteString              (length)
+import qualified Data.ByteString.Lazy         as BL
+import           Data.Text                    (pack, strip)
+import           Data.Text.Lazy               (toStrict)
+import           Data.Text.Lazy.Builder       hiding (fromText)
+import           Data.Yaml                    hiding (Parser)
+import           Filesystem.Path              (dropExtension, (<.>))
 import           Network.AWS.Data.Crypto
 import           Network.AWS.Flow
 import           Options
-import           Options.Applicative hiding ( action )
-import           Shelly hiding ( FilePath, (<.>), bash )
+import           Options.Applicative          hiding (action)
+import           Shelly                       hiding (FilePath, bash, (<.>))
 
 data Args = Args
   { aConfig        :: FilePath
@@ -124,10 +126,10 @@
       dataOutput file =
         catch_sh_maybe (readfile file) where
           catch_sh_maybe action =
-            catch_sh (liftM Just action) $ \(_ :: SomeException) -> return Nothing
+            catch_sh (Just <$> action) $ \(_ :: SomeException) -> return Nothing
       storeInput dir =
         forM_ blobs $ \(key, blob) -> do
-          paths <- liftM strip $ run "dirname" [key]
+          paths <- strip <$> run "dirname" [key]
           mkdir_p $ dir </> paths
           writeArtifact (dir </> key) blob
       storeOutput dir = do
@@ -136,7 +138,7 @@
       docker dataDir storeDir Container{..} =
         handler $ do
           devices <- forM cDevices $ \device ->
-            liftM strip $ run "readlink" ["-f", device]
+            strip <$> run "readlink" ["-f", device]
           run_ "docker" $ concat
             [["run"]
             , concatMap (("--device" :)  . return) devices
diff --git a/main/Act2.hs b/main/Act2.hs
--- a/main/Act2.hs
+++ b/main/Act2.hs
@@ -1,25 +1,27 @@
-{-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE FlexibleContexts    #-}
+{-# LANGUAGE RecordWildCards     #-}
+{-# LANGUAGE ScopedTypeVariables #-}
 module Act2
   ( main
   ) where
 
-import           BasicPrelude hiding ( ByteString, (</>), (<.>), hash, length, readFile, find )
+import           BasicPrelude                 hiding (ByteString, find, hash, length,
+                                               readFile, (<.>), (</>))
 import           Codec.Compression.GZip
 import           Control.Monad.Trans.Resource
 import           Data.Aeson.Encode
-import           Data.ByteString ( length )
-import qualified Data.ByteString.Lazy as BL
-import           Data.Text ( pack, strip )
-import           Data.Text.Lazy ( toStrict )
-import           Data.Text.Lazy.Builder hiding ( fromText )
-import           Data.Yaml hiding ( Parser )
-import           Filesystem.Path ( (<.>), dropExtension )
+import           Data.ByteString              (length)
+import qualified Data.ByteString.Lazy         as BL
+import           Data.Text                    (pack, strip)
+import           Data.Text.Lazy               (toStrict)
+import           Data.Text.Lazy.Builder       hiding (fromText)
+import           Data.Yaml                    hiding (Parser)
+import           Filesystem.Path              (dropExtension, (<.>))
 import           Network.AWS.Data.Crypto
 import           Network.AWS.Flow
 import           Options
-import           Options.Applicative hiding ( action )
-import           Shelly hiding ( FilePath, (<.>), bash )
+import           Options.Applicative          hiding (action)
+import           Shelly                       hiding (FilePath, bash, (<.>))
 
 data Args = Args
   { aConfig      :: FilePath
@@ -101,10 +103,10 @@
       dataOutput file =
         catch_sh_maybe (readfile file) where
           catch_sh_maybe action =
-            catch_sh (liftM Just action) $ \(_ :: SomeException) -> return Nothing
+            catch_sh (Just <$> action) $ \(_ :: SomeException) -> return Nothing
       storeInput dir =
         forM_ blobs $ \(key, blob) -> do
-          paths <- liftM strip $ run "dirname" [key]
+          paths <- strip <$> run "dirname" [key]
           mkdir_p $ dir </> paths
           writeArtifact (dir </> key) blob
       storeOutput dir = do
diff --git a/main/Decide.hs b/main/Decide.hs
--- a/main/Decide.hs
+++ b/main/Decide.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE RecordWildCards #-}
+
 module Decide
   ( main
   ) where
@@ -5,7 +7,7 @@
 import BasicPrelude
 import Control.Concurrent.Async
 import Control.Monad.Trans.Resource
-import Data.Yaml hiding ( Parser )
+import Data.Yaml                    hiding (Parser)
 import Network.AWS.Flow
 import Options
 import Options.Applicative
diff --git a/main/Execute.hs b/main/Execute.hs
--- a/main/Execute.hs
+++ b/main/Execute.hs
@@ -1,12 +1,14 @@
+{-# LANGUAGE RecordWildCards #-}
+
 module Execute
   ( main
   ) where
 
-import BasicPrelude hiding ( readFile )
+import BasicPrelude                 hiding (readFile)
 import Control.Concurrent.Async
 import Control.Monad.Trans.Resource
 import Data.Text.IO
-import Data.Yaml hiding ( Parser )
+import Data.Yaml                    hiding (Parser)
 import Network.AWS.Flow
 import Options
 import Options.Applicative
diff --git a/main/Register.hs b/main/Register.hs
--- a/main/Register.hs
+++ b/main/Register.hs
@@ -1,11 +1,13 @@
+{-# LANGUAGE RecordWildCards #-}
+
 module Register
   ( main
   ) where
 
 import BasicPrelude
 import Control.Concurrent.Async
-import Control.Monad.Trans.Resource hiding ( register )
-import Data.Yaml hiding ( Parser )
+import Control.Monad.Trans.Resource hiding (register)
+import Data.Yaml                    hiding (Parser)
 import Network.AWS.Flow
 import Options
 import Options.Applicative
diff --git a/main/actor.hs b/main/actor.hs
new file mode 100644
--- /dev/null
+++ b/main/actor.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE DeriveGeneric     #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- | Run actor.
+--
+import Network.AWS.Wolf
+import Options.Generic
+
+-- | Args
+--
+-- Program arguments.
+--
+data Args = Args
+  { config  :: FilePath
+    -- ^ Configuration file.
+  , queue   :: Text
+    -- ^ Queue to listen to act on.
+  , command :: String
+    -- ^ Command to run.
+  } deriving (Show, Generic)
+
+instance ParseRecord Args
+
+-- | Run actor.
+--
+main :: IO ()
+main = do
+  args <- getRecord "Actor"
+  runResourceT $ actMain
+    (config args)
+    (queue args)
+    (command args)
diff --git a/main/decider.hs b/main/decider.hs
new file mode 100644
--- /dev/null
+++ b/main/decider.hs
@@ -0,0 +1,30 @@
+{-# LANGUAGE DeriveGeneric     #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- | Run actor.
+--
+import Network.AWS.Wolf
+import Options.Generic
+
+-- | Args
+--
+-- Program arguments.
+--
+data Args = Args
+  { config :: FilePath
+    -- ^ Configuration file.
+  , plan   :: FilePath
+    -- ^ Plan file to decide on.
+  } deriving (Show, Generic)
+
+instance ParseRecord Args
+
+-- | Run decider.
+--
+main :: IO ()
+main = do
+  args <- getRecord "Decider"
+  runResourceT $ decideMain
+    (config args)
+    (plan args)
diff --git a/src/Network/AWS/Flow.hs b/src/Network/AWS/Flow.hs
--- a/src/Network/AWS/Flow.hs
+++ b/src/Network/AWS/Flow.hs
@@ -1,4 +1,5 @@
-{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE LambdaCase      #-}
+{-# LANGUAGE RecordWildCards #-}
 
 module Network.AWS.Flow
   ( register
@@ -26,17 +27,17 @@
 
 import           Network.AWS.Flow.Env
 import           Network.AWS.Flow.Logger
+import           Network.AWS.Flow.Prelude hiding (ByteString, Metadata, handle)
 import           Network.AWS.Flow.S3
 import           Network.AWS.Flow.SWF
 import           Network.AWS.Flow.Types
 import           Network.AWS.Flow.Uid
-import           Network.AWS.Flow.Prelude hiding ( ByteString, Metadata, handle )
 
 import           Control.Monad.Catch
 import           Data.Char
-import qualified Data.HashMap.Strict as Map
-import           Data.Text ( pack )
-import           Formatting hiding ( string )
+import qualified Data.HashMap.Strict      as Map
+import           Data.Text                (pack)
+import           Formatting               hiding (string)
 import           Network.AWS.SWF
 import           Network.HTTP.Types
 import           Safe
@@ -85,7 +86,7 @@
 actException :: MonadFlow m => Token -> SomeException -> m ()
 actException token e = do
   logError' $ sformat ("event=act-exception " % stext) $ show e
-  maybe' ((textToString $ show e) =~ exitCode) (respondActivityTaskFailedAction token) $ \code -> do
+  maybe' (textToString (show e) =~ exitCode) (respondActivityTaskFailedAction token) $ \code ->
     if code == 255 then respondActivityTaskCanceledAction token else
       respondActivityTaskFailedAction token
 
diff --git a/src/Network/AWS/Flow/Env.hs b/src/Network/AWS/Flow/Env.hs
--- a/src/Network/AWS/Flow/Env.hs
+++ b/src/Network/AWS/Flow/Env.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE RecordWildCards #-}
+
 module Network.AWS.Flow.Env
   ( flowEnv
   ) where
diff --git a/src/Network/AWS/Flow/Logger.hs b/src/Network/AWS/Flow/Logger.hs
--- a/src/Network/AWS/Flow/Logger.hs
+++ b/src/Network/AWS/Flow/Logger.hs
@@ -12,7 +12,7 @@
 import Data.Time.Clock
 import Data.Time.Format
 import Data.Version
-import Formatting hiding ( now )
+import Formatting               hiding (now)
 import Paths_wolf
 import System.Log.FastLogger
 
diff --git a/src/Network/AWS/Flow/Prelude.hs b/src/Network/AWS/Flow/Prelude.hs
--- a/src/Network/AWS/Flow/Prelude.hs
+++ b/src/Network/AWS/Flow/Prelude.hs
@@ -7,10 +7,12 @@
   , maybe'
   ) where
 
-import BasicPrelude hiding ( (<.>), uncons )
+import BasicPrelude            hiding (uncons, (<.>))
 import Control.Lens
 import Control.Monad.Reader
 import Control.Monad.Trans.AWS
+
+{-# ANN module ("HLint: ignore Use import/export shortcut"::String) #-}
 
 maybe_ :: Monad m => Maybe a -> (a -> m ()) -> m ()
 maybe_ m f = maybe (return ()) f m
diff --git a/src/Network/AWS/Flow/S3.hs b/src/Network/AWS/Flow/S3.hs
--- a/src/Network/AWS/Flow/S3.hs
+++ b/src/Network/AWS/Flow/S3.hs
@@ -4,16 +4,18 @@
   , putObjectAction
   ) where
 
-import Network.AWS.Flow.Prelude hiding ( ByteString, hash, stripPrefix )
+import Network.AWS.Flow.Prelude hiding (ByteString, hash, stripPrefix)
 import Network.AWS.Flow.Types
 
 import Data.Conduit
-import Data.Conduit.List hiding ( concatMap, map, mapMaybe )
 import Data.Conduit.Binary
-import Data.Text hiding ( concatMap, map )
+import Data.Conduit.List        hiding (concatMap, map, mapMaybe)
+import Data.Text                hiding (concatMap, map)
 import Network.AWS.Data.Body
 import Network.AWS.Data.Text
 import Network.AWS.S3
+
+{-# ANN module ("HLint: ignore Reduce duplication"::String) #-}
 
 -- Actions
 
diff --git a/src/Network/AWS/Flow/SWF.hs b/src/Network/AWS/Flow/SWF.hs
--- a/src/Network/AWS/Flow/SWF.hs
+++ b/src/Network/AWS/Flow/SWF.hs
@@ -18,11 +18,11 @@
   , startChildWorkflowExecutionDecision
   ) where
 
-import Network.AWS.Flow.Prelude hiding ( Metadata )
+import Network.AWS.Flow.Prelude hiding (Metadata)
 import Network.AWS.Flow.Types
 
 import Data.Conduit
-import Data.Conduit.List hiding ( concatMap )
+import Data.Conduit.List        hiding (concatMap)
 import Network.AWS.SWF
 import Safe
 
diff --git a/src/Network/AWS/Flow/Types.hs b/src/Network/AWS/Flow/Types.hs
--- a/src/Network/AWS/Flow/Types.hs
+++ b/src/Network/AWS/Flow/Types.hs
@@ -1,14 +1,14 @@
 {-# OPTIONS  -fno-warn-orphans          #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE ConstraintKinds            #-}
 {-# LANGUAGE FlexibleInstances          #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE MultiParamTypeClasses      #-}
-{-# LANGUAGE UndecidableInstances       #-}
 {-# LANGUAGE TypeFamilies               #-}
+{-# LANGUAGE UndecidableInstances       #-}
 
 module Network.AWS.Flow.Types where
 
-import Network.AWS.Flow.Prelude hiding ( ByteString, catch )
+import Network.AWS.Flow.Prelude     hiding (ByteString, catch)
 
 import Control.Monad.Base
 import Control.Monad.Catch
diff --git a/src/Network/AWS/Wolf.hs b/src/Network/AWS/Wolf.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/AWS/Wolf.hs
@@ -0,0 +1,9 @@
+-- | Public Module
+--
+module Network.AWS.Wolf
+  ( module Exports
+  ) where
+
+import Network.AWS.Wolf.Act     as Exports
+import Network.AWS.Wolf.Decide  as Exports
+import Network.AWS.Wolf.Prelude as Exports
diff --git a/src/Network/AWS/Wolf/Act.hs b/src/Network/AWS/Wolf/Act.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/AWS/Wolf/Act.hs
@@ -0,0 +1,91 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | SWF Actor logic.
+--
+module Network.AWS.Wolf.Act
+  ( act
+  , actMain
+  ) where
+
+import Data.Aeson
+import Network.AWS.Wolf.Ctx
+import Network.AWS.Wolf.File
+import Network.AWS.Wolf.Prelude
+import Network.AWS.Wolf.S3
+import Network.AWS.Wolf.SWF
+import Network.AWS.Wolf.Trace
+import Network.AWS.Wolf.Types
+import System.Process
+
+-- | Download artifacts to the store input directory.
+--
+download :: MonadAmazonStore c m => FilePath -> m ()
+download dir = do
+  ks <- listArtifacts
+  forM_ ks $ \k -> do
+    traceInfo "get-artifact" [ "key" .= k ]
+    getArtifact (dir </> textToString k) k
+
+-- | Upload artifacts from the store output directory.
+--
+upload :: MonadAmazonStore c m => FilePath -> m ()
+upload dir = do
+  fs <- findRegularFiles dir
+  forM_ fs $ \f -> do
+    let k = stripPrefix' (textFromString dir) (textFromString f)
+    traceInfo "put-artifact" [ "key" .= k ]
+    traverse (putArtifact f) k
+
+-- | callCommand wrapper that maybe returns an exception.
+--
+callCommand' :: MonadMain m => String -> m (Maybe SomeException)
+callCommand' command =
+  handle (return . Just) $ do
+    liftIO $ callCommand command
+    return Nothing
+
+-- | Run command and maybe returns an exception.
+--
+run :: MonadCtx c m => String -> m (Maybe SomeException)
+run command =
+  preCtx [ "command" .= command ] $ do
+    traceInfo "begin" mempty
+    e <- callCommand' command
+    traceInfo "end" [ "exception" .= (displayException <$> e) ]
+    return e
+
+-- | Actor logic - poll for work, download artifacts, run command, upload artifacts.
+--
+act :: MonadConf c m => Text -> String -> m ()
+act queue command =
+  preConfCtx [ "label" .= LabelAct ] $
+    runAmazonCtx $
+      runAmazonWorkCtx queue $ do
+        traceInfo "poll" mempty
+        (token, uid, input) <- pollActivity
+        maybe_ token $ \token' ->
+          maybe_ uid $ \uid' ->
+            withCurrentWorkDirectory uid' $ \wd ->
+              runAmazonStoreCtx uid' $ do
+                traceInfo "start" [ "input" .= input, "dir" .= wd ]
+                dd  <- dataDirectory wd
+                sd  <- storeDirectory wd
+                isd <- inputDirectory sd
+                osd <- outputDirectory sd
+                writeJson (dd </> "control.json") (Control uid')
+                writeText (dd </> "input.json") input
+                download isd
+                e <- run command
+                upload osd
+                output <- readText (dd </> "output.json")
+                maybe (completeActivity token' output) (const $ failActivity token') e
+                traceInfo "finish" [ "output" .= output ]
+
+-- | Run actor from main with config file.
+--
+actMain :: MonadMain m => FilePath -> Text -> String -> m ()
+actMain cf queue command =
+  runCtx $ do
+    conf <- readYaml cf
+    runConfCtx conf $
+      forever $ act queue command
diff --git a/src/Network/AWS/Wolf/Aeson.hs b/src/Network/AWS/Wolf/Aeson.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/AWS/Wolf/Aeson.hs
@@ -0,0 +1,47 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Aeson generic deriving options.
+--
+module Network.AWS.Wolf.Aeson
+  ( camelOptions
+  , snakeOptions
+  , spinalOptions
+  ) where
+
+import Data.Aeson.Types
+import Data.Char
+import Data.Text
+import Data.Text.Manipulate
+import Network.AWS.Wolf.Prelude hiding (dropWhile)
+
+-- | Remove characters up to the first upper case character.
+--
+unprefix :: Text -> Text
+unprefix = dropWhile (not . isUpper)
+
+-- | Derive fields with camelCase.
+--
+camelOptions :: Options
+camelOptions = defaultOptions
+  { fieldLabelModifier     = unpack . toCamel . unprefix . pack
+  , constructorTagModifier = unpack . toCamel . unprefix . lowerHead . pack
+  , omitNothingFields      = True
+  }
+
+-- | Derive fields with snake_case.
+--
+snakeOptions :: Options
+snakeOptions = defaultOptions
+  { fieldLabelModifier     = unpack . toSnake . unprefix . pack
+  , constructorTagModifier = unpack . toSnake . unprefix . lowerHead . pack
+  , omitNothingFields      = True
+  }
+
+-- | Derive fields with spinal-case.
+--
+spinalOptions :: Options
+spinalOptions = defaultOptions
+  { fieldLabelModifier     = unpack . toSpinal . unprefix . pack
+  , constructorTagModifier = unpack . toSpinal . unprefix . lowerHead . pack
+  , omitNothingFields      = True
+  }
diff --git a/src/Network/AWS/Wolf/Ctx.hs b/src/Network/AWS/Wolf/Ctx.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/AWS/Wolf/Ctx.hs
@@ -0,0 +1,108 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.AWS.Wolf.Ctx
+  ( runCtx
+  , preCtx
+  , runConfCtx
+  , preConfCtx
+  , runAmazonCtx
+  , preAmazonCtx
+  , runAmazonStoreCtx
+  , preAmazonStoreCtx
+  , runAmazonWorkCtx
+  , preAmazonWorkCtx
+  ) where
+
+import Control.Monad.Logger
+import Control.Monad.Reader
+import Control.Monad.Trans.AWS
+import Data.Aeson
+import Network.AWS.Wolf.Prelude
+import Network.AWS.Wolf.Trace
+import Network.AWS.Wolf.Types
+
+-- | Run monad transformer, picking up logger from context.
+--
+runTransT :: HasCtx c => c -> TransT c m a -> m a
+runTransT c m =
+  runReaderT (runLoggingT (unTransT m) (c ^. cTrace)) c
+
+-- | Run base context.
+--
+runCtx :: MonadIO m => TransT Ctx m a -> m a
+runCtx action = do
+  t <- liftIO $ newStderrTrace LevelInfo
+  runTransT (Ctx mempty t) action
+
+-- | Update base context's preamble.
+--
+preCtx :: MonadCtx c m => Pairs -> TransT Ctx m a -> m a
+preCtx preamble action = do
+  c <- view ctx <&> cPreamble <>~ preamble
+  runTransT c action
+
+-- | Run configuration context.
+--
+runConfCtx :: MonadCtx c m => Conf -> TransT ConfCtx m a -> m a
+runConfCtx conf action = do
+  let preamble =
+        [ "domain" .= (conf ^. cDomain)
+        , "bucket" .= (conf ^. cBucket)
+        , "prefix" .= (conf ^. cPrefix)
+        ]
+  c <- view ctx <&> cPreamble <>~ preamble
+  runTransT (ConfCtx c conf) action
+
+-- | Update configuration context's preamble.
+--
+preConfCtx :: MonadConf c m => Pairs -> TransT ConfCtx m a -> m a
+preConfCtx preamble action = do
+  c <- view confCtx <&> cPreamble <>~ preamble
+  runTransT c action
+
+-- | Run amazon context.
+--
+runAmazonCtx :: MonadConf c m => TransT AmazonCtx m a -> m a
+runAmazonCtx action = do
+  c <- view confCtx
+  e <- newEnv Oregon $ FromEnv "AWS_ACCESS_KEY_ID" "AWS_SECRET_ACCESS_KEY" mempty
+  runTransT (AmazonCtx c e) action
+
+-- | Update amazon context's preamble.
+--
+preAmazonCtx :: MonadAmazon c m => Pairs -> TransT AmazonCtx m a -> m a
+preAmazonCtx preamble action = do
+  c <- view amazonCtx <&> cPreamble <>~ preamble
+  runTransT c action
+
+-- | Run amazon store context.
+--
+runAmazonStoreCtx :: MonadAmazon c m => Text -> TransT AmazonStoreCtx m a -> m a
+runAmazonStoreCtx uid action = do
+  let preamble = [ "uid" .= uid ]
+  c <- view amazonCtx <&> cPreamble <>~ preamble
+  p <- (<\> uid) . view cPrefix <$> view ccConf
+  runTransT (AmazonStoreCtx c uid p) action
+
+-- | Update amazon context's preamble.
+--
+preAmazonStoreCtx :: MonadAmazonStore c m => Pairs -> TransT AmazonStoreCtx m a -> m a
+preAmazonStoreCtx preamble action = do
+  c <- view amazonStoreCtx <&> cPreamble <>~ preamble
+  runTransT c action
+
+-- | Run amazon work context.
+--
+runAmazonWorkCtx :: MonadAmazon c m => Text -> TransT AmazonWorkCtx m a -> m a
+runAmazonWorkCtx queue action = do
+  let preamble = [ "queue" .= queue ]
+  c <- view amazonCtx <&> cPreamble <>~ preamble
+  runTransT (AmazonWorkCtx c queue) action
+
+-- | Update amazon context's preamble.
+--
+preAmazonWorkCtx :: MonadAmazonWork c m => Pairs -> TransT AmazonWorkCtx m a -> m a
+preAmazonWorkCtx preamble action = do
+  c <- view amazonWorkCtx <&> cPreamble <>~ preamble
+  runTransT c action
diff --git a/src/Network/AWS/Wolf/Decide.hs b/src/Network/AWS/Wolf/Decide.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/AWS/Wolf/Decide.hs
@@ -0,0 +1,40 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | SWF Decider logic.
+--
+module Network.AWS.Wolf.Decide
+  ( decide
+  , decideMain
+  ) where
+
+import Data.Aeson
+import Network.AWS.Wolf.Ctx
+import Network.AWS.Wolf.File
+import Network.AWS.Wolf.Prelude
+import Network.AWS.Wolf.SWF
+import Network.AWS.Wolf.Trace
+import Network.AWS.Wolf.Types
+
+-- | Decider logic - poll for decisions, make decisions.
+--
+decide :: MonadConf c m => Plan -> m ()
+decide plan =
+  preConfCtx [ "label" .= LabelDecide ] $
+    runAmazonCtx $
+      runAmazonWorkCtx (plan ^. pStart ^. ptQueue) $ do
+        traceInfo "poll" mempty
+        (token, _events) <- pollDecision
+        maybe_ token $ \_token' -> do
+          traceInfo "start" mempty
+          traceInfo "finish" mempty
+
+-- | Run decider from main with config file.
+--
+decideMain :: MonadMain m => FilePath -> FilePath -> m ()
+decideMain cf pf =
+  runCtx $ do
+    conf <- readYaml cf
+    runConfCtx conf $ do
+      plans <- readYaml pf
+      runConcurrent $
+        (forever . decide) <$> plans
diff --git a/src/Network/AWS/Wolf/File.hs b/src/Network/AWS/Wolf/File.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/AWS/Wolf/File.hs
@@ -0,0 +1,143 @@
+{-# LANGUAGE FlexibleContexts  #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Files, directories, and encoding / decoding file functions.
+--
+module Network.AWS.Wolf.File
+  ( findRegularFiles
+  , dataDirectory
+  , storeDirectory
+  , inputDirectory
+  , outputDirectory
+  , writeText
+  , readText
+  , writeJson
+  , readYaml
+  , withCurrentWorkDirectory
+  ) where
+
+import           Data.Aeson
+import qualified Data.ByteString          as BS
+import qualified Data.ByteString.Lazy     as LBS
+import           Data.Time
+import           Data.Yaml                hiding (encode)
+import           Network.AWS.Wolf.Prelude hiding (find)
+import           System.Directory
+import           System.FilePath.Find
+import           System.IO                hiding (readFile, writeFile)
+
+-- | Recursively find all files under a directory.
+--
+findRegularFiles :: MonadIO m => FilePath -> m [FilePath]
+findRegularFiles =
+  liftIO . find always (fileType ==? RegularFile)
+
+-- | Determine path to data directory and create it.
+--
+dataDirectory :: MonadIO m => FilePath -> m FilePath
+dataDirectory dir = do
+  let dir' = dir </> "data"
+  liftIO $ createDirectoryIfMissing True dir'
+  return dir'
+
+-- | Determine path to store directory and create it.
+--
+storeDirectory :: MonadIO m => FilePath -> m FilePath
+storeDirectory dir = do
+  let dir' = dir </> "store"
+  liftIO $ createDirectoryIfMissing True dir'
+  return dir'
+
+-- | Determine path to store input directory and create it.
+--
+inputDirectory :: MonadIO m => FilePath -> m FilePath
+inputDirectory dir = do
+  let dir' = dir </> "input"
+  liftIO $ createDirectoryIfMissing True dir'
+  return dir'
+
+-- | Determine path to store output directory and create it.
+--
+outputDirectory :: MonadIO m => FilePath -> m FilePath
+outputDirectory dir = do
+  let dir' = dir </> "output"
+  liftIO $ createDirectoryIfMissing True dir'
+  return dir'
+
+-- | Maybe write text to a file.
+--
+writeText :: MonadIO m => FilePath -> Maybe Text -> m ()
+writeText file contents =
+  liftIO $ void $ traverse (writeFile file) contents
+
+-- | Maybe read text from a file.
+--
+readText :: MonadIO m => FilePath -> m (Maybe Text)
+readText file =
+ liftIO $ do
+   b <- doesFileExist file
+   if not b then return mempty else
+     return <$> readFile file
+
+-- | Encode from JSON and write file.
+--
+writeJson :: (MonadIO m, ToJSON a) => FilePath -> a -> m ()
+writeJson file item =
+  liftIO $ withFile file WriteMode $ \h ->
+    BS.hPut h $ LBS.toStrict $ encode item
+
+-- | Read file and decode it from YAML.
+--
+readYaml :: (MonadIO m, FromJSON a) => FilePath -> m a
+readYaml file =
+  liftIO $ withFile file ReadMode $ \h -> do
+    body <- BS.hGetContents h
+    eitherThrowIO $ decodeEither body
+
+-- | Get a temporary timestamped work directory.
+--
+getWorkDirectory :: MonadIO m => Text -> m FilePath
+getWorkDirectory uid =
+  liftIO $ do
+    td   <- getTemporaryDirectory
+    time <- getCurrentTime
+    let dir = td </> formatTime defaultTimeLocale "%FT%T%z" time </> textToString uid
+    createDirectoryIfMissing True dir
+    return dir
+
+-- | Copy directory contents recursively.
+--
+copyDirectoryRecursive :: MonadIO m => FilePath -> FilePath -> m ()
+copyDirectoryRecursive fd td =
+  liftIO $ do
+    createDirectoryIfMissing True td
+    cs <- filter (`notElem` [".", ".."]) <$> getDirectoryContents fd
+    forM_ cs $ \c -> do
+      let fc = fd </> c
+          tc = td </> c
+      e <- doesDirectoryExist fc
+      bool (copyFile fc tc) (copyDirectoryRecursive fc tc) e
+
+-- | Setup a temporary work directory.
+--
+withWorkDirectory :: MonadBaseControlIO m => Text -> (FilePath -> m a) -> m a
+withWorkDirectory uid =
+  bracket (getWorkDirectory uid) (liftIO . removeDirectoryRecursive)
+
+-- | Change to directory and then return to current directory.
+--
+withCurrentDirectory :: MonadBaseControlIO m => FilePath -> (FilePath -> m a) -> m a
+withCurrentDirectory wd action =
+  bracket (liftIO getCurrentDirectory) (liftIO . setCurrentDirectory) $ \cd -> do
+    liftIO $ setCurrentDirectory wd
+    action cd
+
+-- | Setup a temporary work directory and copy current directory files to it.
+--
+withCurrentWorkDirectory :: MonadBaseControlIO m => Text -> (FilePath -> m a) -> m a
+withCurrentWorkDirectory uid action =
+  withWorkDirectory uid $ \wd ->
+    withCurrentDirectory wd $ \cd -> do
+      copyDirectoryRecursive cd wd
+      action wd
+
diff --git a/src/Network/AWS/Wolf/Lens.hs b/src/Network/AWS/Wolf/Lens.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/AWS/Wolf/Lens.hs
@@ -0,0 +1,39 @@
+{-# LANGUAGE LambdaCase        #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- | makeClassy with constraints.
+--
+module Network.AWS.Wolf.Lens
+  ( makeClassyConstraints
+  ) where
+
+import Language.Haskell.TH
+import Network.AWS.Wolf.Prelude
+
+-- | makeClassy with list of class constraints.
+--
+makeClassyConstraints :: Name -> [Name] -> DecsQ
+makeClassyConstraints name names = do
+  decls <- makeClassy name
+  return $ addConstraints names decls
+
+-- | Add constaints to a class.
+--
+addConstraints :: [Name] -> [Dec] -> [Dec]
+addConstraints names = \case
+  ClassD cs n tvs f d : ds ->
+    ClassD (newConstraints names tvs ++ cs) n tvs f d : ds
+  ds -> ds
+
+-- | Create new constaints.
+--
+newConstraints :: [Name] -> [TyVarBndr] -> [Type]
+newConstraints ns =
+  loop where
+    loop = \case
+      PlainTV name : _tvs ->
+        flip map ns $ \n ->
+          AppT (ConT n) (VarT name)
+      _tv : tvs -> loop tvs
+      [] -> []
diff --git a/src/Network/AWS/Wolf/Prelude.hs b/src/Network/AWS/Wolf/Prelude.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/AWS/Wolf/Prelude.hs
@@ -0,0 +1,68 @@
+{-# LANGUAGE ConstraintKinds   #-}
+{-# LANGUAGE FlexibleContexts  #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Local Prelude.
+--
+module Network.AWS.Wolf.Prelude
+  ( module Exports
+  , runResourceT
+  , maybe_
+  , eitherThrowIO
+  , runConcurrent
+  , textFromString
+  , stripPrefix'
+  , (<\>)
+  , MonadBaseControlIO
+  , MonadMain
+  ) where
+
+import BasicPrelude                    as Exports hiding (stripPrefix)
+import Control.Concurrent.Async.Lifted
+import Control.Lens                    as Exports hiding (uncons, (.=), (<.>))
+import Control.Monad.Catch
+import Control.Monad.Trans.Control
+import Control.Monad.Trans.Resource
+import Data.Text                       hiding (map)
+
+-- | Maybe that returns () if Nothing
+--
+maybe_ :: Monad m => Maybe a -> (a -> m ()) -> m ()
+maybe_ = flip $ maybe $ return ()
+
+-- | Throw userError on either error.
+--
+eitherThrowIO :: MonadIO m => Either String a -> m a
+eitherThrowIO = either (liftIO . throwIO . userError) return
+
+-- | Run a list of actions concurrently.
+--
+runConcurrent :: MonadBaseControl IO m => [m a] -> m ()
+runConcurrent = void . runConcurrently . sequenceA . map Concurrently
+
+-- | Reverse of textToString
+--
+textFromString :: String -> Text
+textFromString = pack
+
+-- | Strip the prefix with a '/' tacked on to the prefix.
+--
+stripPrefix' :: Text -> Text -> Maybe Text
+stripPrefix' prefix =
+  stripPrefix (prefix <\> mempty)
+
+-- | </> for Text.
+--
+(<\>) :: Text -> Text -> Text
+(<\>) = (<>) . (<> "/")
+
+type MonadBaseControlIO m =
+  ( MonadBaseControl IO m
+  , MonadIO m
+  )
+
+type MonadMain m =
+  ( MonadBaseControlIO m
+  , MonadResource m
+  , MonadCatch m
+  )
diff --git a/src/Network/AWS/Wolf/S3.hs b/src/Network/AWS/Wolf/S3.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/AWS/Wolf/S3.hs
@@ -0,0 +1,49 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | S3 Calls.
+--
+module Network.AWS.Wolf.S3
+  ( listArtifacts
+  , getArtifact
+  , putArtifact
+  ) where
+
+import Control.Monad.Trans.AWS
+import Data.Conduit
+import Data.Conduit.Binary
+import Data.Conduit.Combinators hiding (sinkFile, sourceFile)
+import Data.Conduit.List        hiding (map, mapMaybe)
+import Data.Text
+import Network.AWS.Data.Body
+import Network.AWS.Data.Text
+import Network.AWS.S3           hiding (cBucket)
+import Network.AWS.Wolf.Prelude
+import Network.AWS.Wolf.Types
+
+-- | List keys in bucket with prefix.
+--
+listArtifacts :: MonadAmazonStore c m => m [Text]
+listArtifacts = do
+  b    <- view cBucket <$> view ccConf
+  p    <- view ascPrefix
+  lors <- paginate (set loPrefix (return p) $ listObjects (BucketName b)) $$ consume
+  return $ mapMaybe (stripPrefix' p) $ toText . view oKey <$> join (view lorsContents <$> lors)
+
+-- | Get object in bucket with key.
+--
+getArtifact :: MonadAmazonStore c m => FilePath -> Text -> m ()
+getArtifact file key = do
+  b    <- view cBucket <$> view ccConf
+  p    <- view ascPrefix
+  gors <- send $ getObject (BucketName b) (ObjectKey (p <\> key))
+  sinkBody (gors ^. gorsBody) (sinkFile file)
+
+-- | Put object in bucket with key.
+--
+putArtifact :: MonadAmazonStore c m => FilePath -> Text -> m ()
+putArtifact file key = do
+  b          <- view cBucket <$> view ccConf
+  p          <- view ascPrefix
+  (sha, len) <- sourceFile file $$ getZipSink $ (,) <$> ZipSink sinkSHA256 <*> ZipSink lengthE
+  void $ send $ putObject (BucketName b) (ObjectKey (p <\> key)) $
+    Hashed $ HashedStream sha len $ sourceFile file
diff --git a/src/Network/AWS/Wolf/SWF.hs b/src/Network/AWS/Wolf/SWF.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/AWS/Wolf/SWF.hs
@@ -0,0 +1,54 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | SWF Calls.
+--
+module Network.AWS.Wolf.SWF
+  ( pollActivity
+  , pollDecision
+  , completeActivity
+  , failActivity
+  ) where
+
+import Control.Monad.Trans.AWS
+import Data.Conduit
+import Data.Conduit.List        hiding (concatMap, map)
+import Network.AWS.SWF
+import Network.AWS.Wolf.Prelude
+import Network.AWS.Wolf.Types
+
+-- | Poll for activities.
+--
+pollActivity :: MonadAmazonWork c m => m (Maybe Text, Maybe Text, Maybe Text)
+pollActivity = do
+  d      <- view cDomain <$> view ccConf
+  tl     <- taskList <$> view awcQueue
+  pfatrs <- send (pollForActivityTask d tl)
+  return
+    ( pfatrs ^. pfatrsTaskToken
+    , view weWorkflowId <$> pfatrs ^. pfatrsWorkflowExecution
+    , pfatrs ^. pfatrsInput
+    )
+
+-- | Poll for decisions.
+--
+pollDecision :: MonadAmazonWork c m => m (Maybe Text, [HistoryEvent])
+pollDecision = do
+  d      <- view cDomain <$> view ccConf
+  tl     <- taskList <$> view awcQueue
+  pfdtrs <- paginate (pollForDecisionTask d tl) $$ consume
+  return
+    ( join $ listToMaybe $ map (view pfdtrsTaskToken) pfdtrs
+    , reverse $ concatMap (view pfdtrsEvents) pfdtrs
+    )
+
+-- | Successfull job completion.
+--
+completeActivity :: MonadAmazon c m => Text -> Maybe Text -> m ()
+completeActivity token output =
+  void $ send $ set ratcResult output $ respondActivityTaskCompleted token
+
+-- | Job failure.
+--
+failActivity :: MonadAmazon c m => Text -> m ()
+failActivity token =
+  void $ send $ respondActivityTaskFailed token
diff --git a/src/Network/AWS/Wolf/Trace.hs b/src/Network/AWS/Wolf/Trace.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/AWS/Wolf/Trace.hs
@@ -0,0 +1,89 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- | Tracing functionality around MonadLogger.
+--
+module Network.AWS.Wolf.Trace
+  ( newStderrTrace
+  , newStdoutTrace
+  , nullTrace
+
+  , traceDebug
+  , traceInfo
+  , traceWarn
+  , traceError
+  ) where
+
+import           Control.Monad.Logger
+import           Data.Aeson
+import           Data.Aeson.Encode
+import qualified Data.HashMap.Strict      as M
+import qualified Data.Text.Lazy           as LT
+import           Data.Text.Lazy.Builder
+import           Data.Time
+import           Data.Version
+import           Network.AWS.Wolf.Prelude
+import           Network.AWS.Wolf.Types
+import           Paths_wolf
+import           System.Log.FastLogger
+
+-- | Trace out only if gte configured level.
+--
+levelTrace :: LogLevel -> LoggerSet -> Trace
+levelTrace level ls _loc _source level' s =
+  unless (level' < level) $ do
+    pushLogStr ls s
+    flushLogStr ls
+
+-- | New logger to stderr.
+--
+newStderrTrace :: MonadIO m => LogLevel -> m Trace
+newStderrTrace level =
+  liftIO $ levelTrace level <$> newStderrLoggerSet defaultBufSize
+
+-- | New logger to stdout.
+--
+newStdoutTrace :: MonadIO m => LogLevel -> m Trace
+newStdoutTrace level =
+  liftIO $ levelTrace level <$> newStdoutLoggerSet defaultBufSize
+
+-- | Logger to nowhere.
+--
+nullTrace :: Trace
+nullTrace _loc _source _level _s =
+  return ()
+
+-- | Trace out event with preamble and timestamp.
+--
+trace :: MonadCtx c m => (Text -> m ()) -> Text -> Pairs -> m ()
+trace logN e ps = do
+  t <- liftIO getCurrentTime
+  let preamble =
+        [ "version" .= showVersion version
+        , "run"     .= LabelWolf
+        , "event"   .= e
+        , "time"    .= t
+        ]
+  p <- view cPreamble
+  logN $ LT.toStrict $ toLazyText $ (<> singleton '\n') $
+    encodeToTextBuilder $ Object $ M.fromList $ preamble <> p <> ps
+
+-- | Debug tracing.
+--
+traceDebug :: MonadCtx c m => Text -> Pairs -> m ()
+traceDebug = trace logDebugN
+
+-- | Info tracing.
+--
+traceInfo :: MonadCtx c m => Text -> Pairs -> m ()
+traceInfo = trace logInfoN
+
+-- | Warn tracing.
+--
+traceWarn :: MonadCtx c m => Text -> Pairs -> m ()
+traceWarn = trace logWarnN
+
+-- | Error tracing.
+--
+traceError :: MonadCtx c m => Text -> Pairs -> m ()
+traceError = trace logErrorN
diff --git a/src/Network/AWS/Wolf/Types.hs b/src/Network/AWS/Wolf/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/AWS/Wolf/Types.hs
@@ -0,0 +1,12 @@
+-- | Export all the modules in the Types directory.
+--
+module Network.AWS.Wolf.Types
+  ( module Exports
+  ) where
+
+import Network.AWS.Wolf.Types.Alias   as Exports
+import Network.AWS.Wolf.Types.Ctx     as Exports
+import Network.AWS.Wolf.Types.Plan    as Exports
+import Network.AWS.Wolf.Types.Product as Exports
+import Network.AWS.Wolf.Types.Sum     as Exports
+import Network.AWS.Wolf.Types.Trans   as Exports
diff --git a/src/Network/AWS/Wolf/Types/Alias.hs b/src/Network/AWS/Wolf/Types/Alias.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/AWS/Wolf/Types/Alias.hs
@@ -0,0 +1,17 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Various alias types.
+--
+module Network.AWS.Wolf.Types.Alias where
+
+import Control.Monad.Logger
+import Data.Aeson
+import Network.AWS.Wolf.Prelude
+
+-- | Pairs
+--
+type Pairs = [(Text, Value)]
+
+-- | Trace
+--
+type Trace = Loc -> LogSource -> LogLevel -> LogStr -> IO ()
diff --git a/src/Network/AWS/Wolf/Types/Ctx.hs b/src/Network/AWS/Wolf/Types/Ctx.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/AWS/Wolf/Types/Ctx.hs
@@ -0,0 +1,150 @@
+{-# LANGUAGE ConstraintKinds   #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE TemplateHaskell   #-}
+
+-- | Context objects for monad transformers.
+--
+module Network.AWS.Wolf.Types.Ctx where
+
+import Control.Monad.Catch
+import Control.Monad.Logger
+import Control.Monad.Reader
+import Control.Monad.Trans.AWS
+import Control.Monad.Trans.Resource
+import Network.AWS.Wolf.Lens
+import Network.AWS.Wolf.Prelude
+import Network.AWS.Wolf.Types.Alias
+import Network.AWS.Wolf.Types.Product
+
+-- | Ctx
+--
+-- Base context, supports tracing.
+--
+data Ctx = Ctx
+  { _cPreamble :: Pairs
+    -- ^ Object to encode on every trace line.
+  , _cTrace    :: Trace
+    -- ^ Configurable tracing function.
+  }
+
+$(makeClassy ''Ctx)
+
+type MonadCtx c m =
+  ( MonadBaseControlIO m
+  , MonadReader c m
+  , MonadLogger m
+  , MonadCatch m
+  , MonadResource m
+  , HasCtx c
+  )
+
+-- | ConfCtx
+--
+-- Configuration context.
+--
+data ConfCtx = ConfCtx
+  { _ccCtx  :: Ctx
+    -- ^ Parent context.
+  , _ccConf :: Conf
+    -- ^ Configuration parameters.
+  }
+
+$(makeClassyConstraints ''ConfCtx [''HasCtx])
+
+instance HasCtx ConfCtx where
+  ctx = ccCtx
+
+type MonadConf c m =
+  ( MonadCtx c m
+  , HasConfCtx c
+  )
+
+-- | AmazonCtx
+--
+-- Amazon context.
+--
+data AmazonCtx = AmazonCtx
+  { _acConfCtx :: ConfCtx
+    -- ^ Parent context.
+  , _acEnv     :: Env
+    -- ^ Amazon environment.
+  }
+
+$(makeClassyConstraints ''AmazonCtx [''HasConfCtx, ''HasEnv])
+
+instance HasConfCtx AmazonCtx where
+  confCtx = acConfCtx
+
+instance HasCtx AmazonCtx where
+  ctx = confCtx . ccCtx
+
+instance HasEnv AmazonCtx where
+  environment = acEnv
+
+type MonadAmazon c m =
+  ( MonadConf c m
+  , HasAmazonCtx c
+  , AWSConstraint c m
+  )
+
+-- | AmazonStoreCtx
+--
+-- Amazon store context.
+--
+data AmazonStoreCtx = AmazonStoreCtx
+  { _ascAmazonCtx :: AmazonCtx
+    -- ^ Parent context.
+  , _ascUid       :: Text
+    -- ^ Workflow uid.
+  , _ascPrefix    :: Text
+    -- ^ Object prefix.
+  }
+
+$(makeClassyConstraints ''AmazonStoreCtx [''HasAmazonCtx])
+
+instance HasAmazonCtx AmazonStoreCtx where
+  amazonCtx = ascAmazonCtx
+
+instance HasConfCtx AmazonStoreCtx where
+   confCtx = amazonCtx . acConfCtx
+
+instance HasCtx AmazonStoreCtx where
+   ctx = confCtx . ccCtx
+
+instance HasEnv AmazonStoreCtx where
+   environment = amazonCtx . acEnv
+
+type MonadAmazonStore c m =
+   ( MonadAmazon c m
+   , HasAmazonStoreCtx c
+   )
+
+-- | AmazonStoreCtx
+--
+-- Amazon work context.
+--
+data AmazonWorkCtx = AmazonWorkCtx
+  { _awcAmazonCtx :: AmazonCtx
+    -- ^ Parent context.
+  , _awcQueue     :: Text
+    -- ^ Workflow queue.
+  }
+
+$(makeClassyConstraints ''AmazonWorkCtx [''HasAmazonCtx])
+
+instance HasAmazonCtx AmazonWorkCtx where
+  amazonCtx = awcAmazonCtx
+
+instance HasConfCtx AmazonWorkCtx where
+   confCtx = amazonCtx . acConfCtx
+
+instance HasCtx AmazonWorkCtx where
+   ctx = confCtx . ccCtx
+
+instance HasEnv AmazonWorkCtx where
+   environment = amazonCtx . acEnv
+
+type MonadAmazonWork c m =
+   ( MonadAmazon c m
+   , HasAmazonWorkCtx c
+   )
diff --git a/src/Network/AWS/Wolf/Types/Plan.hs b/src/Network/AWS/Wolf/Types/Plan.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/AWS/Wolf/Types/Plan.hs
@@ -0,0 +1,42 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE TemplateHaskell   #-}
+
+-- | Specifications for plans.
+--
+module Network.AWS.Wolf.Types.Plan where
+
+import Data.Aeson.TH
+import Network.AWS.Wolf.Aeson
+import Network.AWS.Wolf.Prelude
+
+-- | Plan Task
+--
+-- Work task.
+--
+data PlanTask = PlanTask
+  { _ptName    :: Text
+    -- ^ Name of task.
+  , _ptVersion :: Text
+    -- ^ Version of task.
+  , _ptQueue   :: Text
+    -- ^ Queue for task.
+  , _ptTimeout :: Text
+    -- ^ Timeout for task.
+  } deriving (Show, Eq)
+
+$(makeLenses ''PlanTask)
+$(deriveJSON spinalOptions ''PlanTask)
+
+-- | Plan
+--
+-- Group of tasks.
+--
+data Plan = Plan
+  { _pStart :: PlanTask
+    -- ^ Flow task.
+  , _pTasks :: [PlanTask]
+    -- ^ Worker tasks.
+  } deriving (Show, Eq)
+
+$(makeLenses ''Plan)
+$(deriveJSON spinalOptions ''Plan)
diff --git a/src/Network/AWS/Wolf/Types/Product.hs b/src/Network/AWS/Wolf/Types/Product.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/AWS/Wolf/Types/Product.hs
@@ -0,0 +1,40 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE TemplateHaskell   #-}
+
+-- | Various product types.
+--
+module Network.AWS.Wolf.Types.Product where
+
+import Data.Aeson.TH
+import Network.AWS.Wolf.Aeson
+import Network.AWS.Wolf.Prelude
+
+-- | Conf
+--
+-- SWF and S3 configuration parameters.
+--
+data Conf = Conf
+  { _cTimeout     :: Int
+    -- ^ SWF regular timeout.
+  , _cPollTimeout :: Int
+    -- ^ SWF polling timeout.
+  , _cDomain      :: Text
+    -- ^ SWF domain.
+  , _cBucket      :: Text
+    -- ^ S3 bucket.
+  , _cPrefix      :: Text
+    -- ^ S3 prefix.
+  } deriving (Show, Eq)
+
+$(makeLenses ''Conf)
+$(deriveJSON spinalOptions ''Conf)
+
+-- | Control
+--
+data Control = Control
+  { _cRunUid :: Text
+    -- ^ Run uid of workflow.
+  } deriving (Show, Eq)
+
+$(makeLenses ''Control)
+$(deriveJSON spinalOptions ''Control)
diff --git a/src/Network/AWS/Wolf/Types/Sum.hs b/src/Network/AWS/Wolf/Types/Sum.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/AWS/Wolf/Types/Sum.hs
@@ -0,0 +1,22 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE TemplateHaskell   #-}
+
+-- | Various sum types.
+--
+module Network.AWS.Wolf.Types.Sum where
+
+import Data.Aeson.TH
+import Network.AWS.Wolf.Aeson
+import Network.AWS.Wolf.Prelude
+
+-- | LabelType
+--
+-- Tags for referencing workers.
+--
+data LabelType
+  = LabelWolf
+  | LabelAct
+  | LabelDecide
+  deriving (Show, Eq)
+
+$(deriveJSON spinalOptions ''LabelType)
diff --git a/src/Network/AWS/Wolf/Types/Trans.hs b/src/Network/AWS/Wolf/Types/Trans.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/AWS/Wolf/Types/Trans.hs
@@ -0,0 +1,47 @@
+{-# LANGUAGE FlexibleInstances          #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE MultiParamTypeClasses      #-}
+{-# LANGUAGE NoImplicitPrelude          #-}
+{-# LANGUAGE TypeFamilies               #-}
+{-# LANGUAGE UndecidableInstances       #-}
+
+-- | Monad transformer.
+--
+module Network.AWS.Wolf.Types.Trans where
+
+import Control.Monad.Base
+import Control.Monad.Catch
+import Control.Monad.Logger
+import Control.Monad.Reader
+import Control.Monad.Trans.Control
+import Control.Monad.Trans.Resource
+import Network.AWS.Wolf.Prelude
+
+-- | Monad transformer for reading and logging.
+--
+newtype TransT c m a = TransT
+  { unTransT :: LoggingT (ReaderT c m) a
+    -- ^ LoggingT and ReaderT transformer.
+  } deriving (Functor, Applicative, Monad, MonadLogger, MonadReader c, MonadIO, MonadThrow, MonadCatch, MonadMask)
+
+instance MonadBase b m => MonadBase b (TransT c m) where
+  liftBase = liftBaseDefault
+
+instance MonadBaseControl b m => MonadBaseControl b (TransT c m) where
+    type StM (TransT c m) a = ComposeSt (TransT c) m a
+    liftBaseWith = defaultLiftBaseWith
+    restoreM = defaultRestoreM
+
+instance MonadTransControl (TransT c) where
+  type StT (TransT c) a = StT (ReaderT c) a
+  liftWith f = TransT $
+    liftWith $ \g ->
+      liftWith $ \h ->
+        f (h . g . unTransT)
+  restoreT = TransT . restoreT . restoreT
+
+instance MonadTrans (TransT c) where
+  lift = TransT . lift . lift
+
+instance MonadResource m => MonadResource (TransT c m) where
+  liftResourceT = lift . liftResourceT
diff --git a/test/Test/Network/AWS/Flow.hs b/test/Test/Network/AWS/Flow.hs
--- a/test/Test/Network/AWS/Flow.hs
+++ b/test/Test/Network/AWS/Flow.hs
@@ -11,7 +11,7 @@
 assertUserError :: String -> IO a -> IO ()
 assertUserError s action =
   handleJust check (const $ return ()) $ do
-    void $ action
+    void action
     assertFailure $ "missed user error: " ++ s where
       check e = guard $ userError s == e
 
diff --git a/wolf.cabal b/wolf.cabal
--- a/wolf.cabal
+++ b/wolf.cabal
@@ -1,5 +1,5 @@
 name:                wolf
-version:             0.2.14
+version:             0.3.0
 synopsis:            Amazon Simple Workflow Service Wrapper.
 homepage:            https://github.com/swift-nav/wolf
 license:             MIT
@@ -21,6 +21,7 @@
 
 library
   exposed-modules:     Network.AWS.Flow
+                     , Network.AWS.Wolf
   other-modules:       Network.AWS.Flow.Env
                      , Network.AWS.Flow.Logger
                      , Network.AWS.Flow.Prelude
@@ -28,6 +29,23 @@
                      , Network.AWS.Flow.SWF
                      , Network.AWS.Flow.Types
                      , Network.AWS.Flow.Uid
+                     , Network.AWS.Wolf.Act
+                     , Network.AWS.Wolf.Aeson
+                     , Network.AWS.Wolf.Ctx
+                     , Network.AWS.Wolf.Decide
+                     , Network.AWS.Wolf.File
+                     , Network.AWS.Wolf.Lens
+                     , Network.AWS.Wolf.Prelude
+                     , Network.AWS.Wolf.S3
+                     , Network.AWS.Wolf.SWF
+                     , Network.AWS.Wolf.Trace
+                     , Network.AWS.Wolf.Types
+                     , Network.AWS.Wolf.Types.Alias
+                     , Network.AWS.Wolf.Types.Ctx
+                     , Network.AWS.Wolf.Types.Plan
+                     , Network.AWS.Wolf.Types.Product
+                     , Network.AWS.Wolf.Types.Sum
+                     , Network.AWS.Wolf.Types.Trans
                      , Paths_wolf
   default-language:    Haskell2010
   hs-source-dirs:      src
@@ -41,23 +59,31 @@
                      , basic-prelude
                      , bytestring
                      , conduit
+                     , conduit-combinators
                      , conduit-extra
+                     , directory
                      , exceptions
                      , fast-logger
+                     , filemanip
                      , formatting
                      , http-conduit
                      , http-types
                      , lens
+                     , lifted-async
+                     , lifted-base
                      , monad-control
                      , monad-logger
                      , mtl
                      , mtl-compat
                      , optparse-applicative
+                     , process
                      , regex-applicative
                      , regex-compat
                      , resourcet
                      , safe
+                     , template-haskell
                      , text
+                     , text-manipulate
                      , time
                      , transformers
                      , transformers-base
@@ -185,3 +211,30 @@
   default-extensions:  OverloadedStrings
                        RecordWildCards
                        NoImplicitPrelude
+
+executable wolf-actor
+  hs-source-dirs:      main
+  main-is:             actor.hs
+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N -Wall -O2
+  build-depends:       base
+                     , wolf
+                     , optparse-generic
+  default-language:    Haskell2010
+
+executable wolf-decider
+  hs-source-dirs:      main
+  main-is:             decider.hs
+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N -Wall -O2
+  build-depends:       base
+                     , wolf
+                     , optparse-generic
+  default-language:    Haskell2010
+
+executable shake-wolf
+  main-is:             Shakefile.hs
+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N -Wall -O2
+  build-depends:       base >= 4.7 && < 5
+                     , basic-prelude
+                     , directory
+                     , shake
+  default-language:    Haskell2010
