diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -6,7 +6,9 @@
 [![Build status][4]][5]
 [![Dependency status][6]][7]
 
-Wolf is a wrapper around Amazon Simple Workflow Service.
+Wolf is a wrapper around Amazon Simple Workflow Service. More
+documentation is coming, but in the meantime see the examples
+directory.
 
 [1]: https://github.com/swift-nav/wolf
 [2]: https://img.shields.io/hackage/v/wolf.svg?style=flat
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,30 +1,15 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE FlexibleContexts  #-}
-{-# LANGUAGE ConstraintKinds   #-}
-{-# LANGUAGE RecordWildCards   #-}
-
 module Network.AWS.Flow
   ( register
   , execute
   , act
   , decide
+  , flowEnv
   , runFlowT
-  , throwStringError
-  , hoistStringEither
-  , maybeToFlowError
+  , maybeThrow
   , Uid
-  , Name
-  , Version
   , Queue
-  , Token
-  , Timeout
   , Metadata
   , Artifact
-  , FlowConfig (..)
-  , FlowEnv (..)
-  , FlowError
-  , FlowT
-  , MonadFlow
   , Task (..)
   , Timer (..)
   , Start (..)
@@ -33,27 +18,30 @@
   , Plan (..)
   ) where
 
-import Control.Lens              ( (^.) )
-import Control.Monad             ( foldM, forM_ )
-import Control.Monad.Reader      ( asks )
-import Data.List                 ( find )
-import Network.AWS.SWF
-import Network.AWS.Flow.Internal
-import Network.AWS.Flow.S3
-import Network.AWS.Flow.SWF
-import Network.AWS.Flow.Types
-import Safe                      ( headMay, tailMay )
+import           Control.Lens
+import           Control.Monad.Catch
+import           Control.Monad.Reader
+import qualified Data.HashMap.Strict as Map
+import           Data.List
+import           Data.UUID
+import           Data.UUID.V4
+import           Network.AWS.SWF
+import           Network.AWS.Flow.Env
+import           Network.AWS.Flow.S3
+import           Network.AWS.Flow.SWF
+import           Network.AWS.Flow.Types
+import           Safe
 
 -- Interface
 
-register :: MonadFlow m => Plan -> m [()]
+register :: MonadFlow m => Plan -> m ()
 register Plan{..} = do
   r <- registerDomainAction
   s <- registerWorkflowTypeAction
          (tskName $ strtTask plnStart)
          (tskVersion $ strtTask plnStart)
          (tskTimeout $ strtTask plnStart)
-  foldM go [s, r] plnSpecs where
+  foldM_ go [s, r] plnSpecs where
     go rs Work{..} = do
       r <- registerActivityTypeAction
              (tskName wrkTask)
@@ -64,7 +52,7 @@
 
 execute :: MonadFlow m => Task -> Metadata -> m ()
 execute Task{..} input = do
-  uid <- newUid
+  uid <- liftIO $ newUid
   startWorkflowExecutionAction uid tskName tskVersion tskQueue input
 
 act :: MonadFlow m => Queue -> (Uid -> Metadata -> m (Metadata, [Artifact])) -> m ()
@@ -76,18 +64,26 @@
 
 decide :: MonadFlow m => Plan -> m ()
 decide plan@Plan{..} = do
-   (token', events) <- pollForDecisionTaskAction (tskQueue $ strtTask plnStart)
-   token <- maybeToFlowError "No Token" token'
-   logger <- asks feLogger
-   decisions <- runDecide logger plan events select
-   respondDecisionTaskCompletedAction token decisions
+  (token', events) <- pollForDecisionTaskAction (tskQueue $ strtTask plnStart)
+  token <- maybeThrow (userError "No Token") token'
+  logger <- asks feLogger
+  decisions <- runDecide logger plan events select
+  respondDecisionTaskCompletedAction token decisions
 
--- Helpers
+-- Decisions
 
+runDecide :: Log -> Plan -> [HistoryEvent] -> DecideT m a -> m a
+runDecide logger plan events action =
+  runDecideT env action where
+    env = DecideEnv logger plan events findEvent where
+      findEvent =
+        flip Map.lookup $ Map.fromList $ flip map events $ \e ->
+          (e ^. heEventId, e)
+
 nextEvent :: MonadDecide m => [EventType] -> m HistoryEvent
 nextEvent ets = do
   events <- asks deEvents
-  maybeToFlowError "No Next Event" $ flip find events $ \e ->
+  maybeThrow (userError "No Next Event") $ flip find events $ \e ->
     e ^. heEventType `elem` ets
 
 workNext :: MonadDecide m => Name -> m (Maybe Spec)
@@ -115,11 +111,11 @@
     ActivityTaskCompleted                -> completed event
     TimerFired                           -> timer event
     StartChildWorkflowExecutionInitiated -> child
-    _                                    -> throwStringError "Unknown Select Event"
+    _                                    -> throwM (userError "Unknown Select Event")
 
 start :: MonadDecide m => HistoryEvent -> m [Decision]
 start event = do
-  input <- maybeToFlowError "No Start Information" $ do
+  input <- maybeThrow (userError "No Start Information") $ do
     attrs <- event ^. heWorkflowExecutionStartedEventAttributes
     return $ attrs ^. weseaInput
   specs <- asks (plnSpecs . dePlan)
@@ -128,7 +124,7 @@
 completed :: MonadDecide m => HistoryEvent -> m [Decision]
 completed event = do
   findEvent <- asks deFindEvent
-  (input, name) <- maybeToFlowError "No Completed Information" $ do
+  (input, name) <- maybeThrow (userError "No Completed Information") $ do
     attrs <- event ^. heActivityTaskCompletedEventAttributes
     event' <- findEvent $ attrs ^. atceaScheduledEventId
     attrs' <- event' ^. heActivityTaskScheduledEventAttributes
@@ -139,7 +135,7 @@
 timer :: MonadDecide m => HistoryEvent -> m [Decision]
 timer event = do
   findEvent <- asks deFindEvent
-  name <- maybeToFlowError "No Timer Information" $ do
+  name <- maybeThrow (userError "No Timer Information") $ do
     attrs <- event ^. heTimerFiredEventAttributes
     event' <- findEvent $ attrs ^. tfeaStartedEventId
     attrs' <- event' ^. heTimerStartedEventAttributes
@@ -148,11 +144,11 @@
   case event' ^. heEventType of
     WorkflowExecutionStarted -> timerStart event' name
     ActivityTaskCompleted    -> timerCompleted event' name
-    _                        -> throwStringError "Unknown Timer Event"
+    _                        -> throwM (userError "Unknown Timer Event")
 
 timerStart :: MonadDecide m => HistoryEvent -> Name -> m [Decision]
 timerStart event name = do
-  input <- maybeToFlowError "No Timer Start Information" $ do
+  input <- maybeThrow (userError "No Timer Start Information") $ do
     attrs <- event ^. heWorkflowExecutionStartedEventAttributes
     return $ attrs ^. weseaInput
   next <- sleepNext name
@@ -160,7 +156,7 @@
 
 timerCompleted :: MonadDecide m => HistoryEvent -> Name -> m [Decision]
 timerCompleted event name = do
-  input <- maybeToFlowError "No Timer Completed Information" $ do
+  input <- maybeThrow (userError "No Timer Completed Information") $ do
     attrs <- event ^. heActivityTaskCompletedEventAttributes
     return $ attrs ^. atceaResult
   next <- sleepNext name
@@ -171,7 +167,7 @@
 
 scheduleSpec :: MonadDecide m => Metadata -> Spec -> m [Decision]
 scheduleSpec input spec = do
-  uid <- newUid
+  uid <- liftIO $ newUid
   case spec of
     Work{..} ->
       return [scheduleActivityTaskDecision uid
@@ -194,10 +190,10 @@
 scheduleContinue :: MonadDecide m => m [Decision]
 scheduleContinue = do
   event <- nextEvent [WorkflowExecutionStarted]
-  input <- maybeToFlowError "No Continue Start Information" $ do
+  input <- maybeThrow (userError "No Continue Start Information") $ do
     attrs <- event ^. heWorkflowExecutionStartedEventAttributes
     return $ attrs ^. weseaInput
-  uid <- newUid
+  uid <- liftIO $ newUid
   task <- asks (strtTask . plnStart . dePlan)
   return [startChildWorkflowExecutionDecision uid
            (tskName task)
@@ -211,19 +207,26 @@
   case event ^. heEventType of
     WorkflowExecutionStarted -> childStart event
     ActivityTaskCompleted    -> childCompleted event
-    _                        -> throwStringError "Unknown Child Event"
+    _                        -> throwM (userError "Unknown Child Event")
 
 childStart :: MonadDecide m => HistoryEvent -> m [Decision]
 childStart event = do
-  input <- maybeToFlowError "No Child Start Information" $ do
+  input <- maybeThrow (userError "No Child Start Information") $ do
     attrs <- event ^. heWorkflowExecutionStartedEventAttributes
     return $ attrs ^. weseaInput
   return [completeWorkflowExecutionDecision input]
 
 childCompleted :: MonadDecide m => HistoryEvent -> m [Decision]
 childCompleted event = do
-  input <- maybeToFlowError "No Child Completed Information" $ do
+  input <- maybeThrow (userError "No Child Completed Information") $ do
     attrs <- event ^. heActivityTaskCompletedEventAttributes
     return $ attrs ^. atceaResult
   return [completeWorkflowExecutionDecision input]
 
+-- Helpers
+
+maybeThrow :: (MonadThrow m, Exception e) => e -> Maybe a -> m a
+maybeThrow e = maybe (throwM e) return
+
+newUid :: IO Uid
+newUid = toText <$> nextRandom
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,75 +1,18 @@
-{-# LANGUAGE RecordWildCards   #-}
-{-# LANGUAGE FlexibleContexts  #-}
-{-# LANGUAGE ConstraintKinds   #-}
-{-# LANGUAGE OverloadedStrings #-}
-
 module Network.AWS.Flow.Env
   ( flowEnv
   ) where
 
-import Control.Applicative     ( (<$>), (<*>) )
-import Control.Lens            ( (.~), (<&>) )
-import Control.Monad           ( mzero )
-import Control.Monad.Except    ( runExceptT )
+import Control.Lens
 import Control.Monad.Trans.AWS
-import Data.Aeson
-import Network.AWS.Flow
-import Network.HTTP.Conduit    ( conduitManagerSettings
-                               , managerResponseTimeout
-                               , newManager )
-import System.Log.FastLogger   ( defaultBufSize
-                               , flushLogStr
-                               , newStderrLoggerSet
-                               , pushLogStr )
-import System.IO               ( stderr )
-
-instance FromJSON Region where
-  parseJSON (String v)
-    | v == "eu-west-1"          = return Ireland
-    | v == "eu-central-1"       = return Frankfurt
-    | v == "ap-northeast-1"     = return Tokyo
-    | v == "ap-southeast-1"     = return Singapore
-    | v == "ap-southeast-2"     = return Sydney
-    | v == "cn-north-1"         = return Beijing
-    | v == "us-east-1"          = return NorthVirginia
-    | v == "us-west-1"          = return NorthCalifornia
-    | v == "us-west-2"          = return Oregon
-    | v == "us-gov-west-1"      = return GovCloud
-    | v == "fips-us-gov-west-1" = return GovCloudFIPS
-    | v == "sa-east-1"          = return SaoPaulo
-    | otherwise = mzero
-  parseJSON _ = mzero
-
-instance FromJSON Credentials where
-  parseJSON (Object v) =
-    FromEnv                     <$>
-      v .: "access-key-env-var" <*>
-      v .: "secret-key-env-var"
-  parseJSON _ = mzero
-
-instance FromJSON FlowConfig where
-  parseJSON (Object v) =
-    FlowConfig            <$>
-      v .: "region"       <*>
-      v .: "credentials"  <*>
-      v .: "timeout"      <*>
-      v .: "poll-timeout" <*>
-      v .: "domain"       <*>
-      v .: "bucket"
-  parseJSON _ = mzero
+import Network.AWS.Flow.Types
+import System.Log.FastLogger
+import System.IO
 
 flowEnv :: FlowConfig -> IO FlowEnv
 flowEnv FlowConfig{..} = do
   loggerSet <- newStderrLoggerSet defaultBufSize
   logger <- newLogger Info stderr
-  manager <- newManager (managerSettings fcTimeout)
-  pollManager <- newManager (managerSettings fcPollTimeout)
-  env <- newEnv' manager <&> envLogger .~ logger
-  pollEnv <- newEnv' pollManager <&> envLogger .~ logger
-  return $ FlowEnv (logStrLn loggerSet) env pollEnv fcDomain fcBucket where
-    managerSettings timeout =
-      conduitManagerSettings { managerResponseTimeout = Just timeout }
-    newEnv' m =
-      runExceptT (newEnv fcRegion fcCredentials m) >>= either error return
+  env <- newEnv fcRegion fcCredentials <&> envLogger .~ logger
+  return $ FlowEnv (logStrLn loggerSet) env (fromIntegral fcTimeout) (fromIntegral fcPollTimeout) fcDomain fcBucket fcPrefix where
     logStrLn ls s =
       pushLogStr ls s >> flushLogStr ls
diff --git a/src/Network/AWS/Flow/Internal.hs b/src/Network/AWS/Flow/Internal.hs
deleted file mode 100644
--- a/src/Network/AWS/Flow/Internal.hs
+++ /dev/null
@@ -1,207 +0,0 @@
-{-# LANGUAGE OverloadedStrings     #-}
-{-# LANGUAGE FlexibleContexts      #-}
-{-# LANGUAGE FlexibleInstances     #-}
-{-# LANGUAGE UndecidableInstances  #-}
-{-# LANGUAGE TypeFamilies          #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE ConstraintKinds       #-}
-
-module Network.AWS.Flow.Internal
-  ( runAWS
-  , runFlowT
-  , runDecide
-  , throwStringError
-  , hoistStringEither
-  , maybeToFlowError
-  , newUid
-  ) where
-
-import Control.Applicative         ( (<$>), (<*>) )
-import Control.Lens                ( (^.) )
-import Control.Monad               ( msum, mzero )
-import Control.Monad.Base          ( MonadBase, liftBase, liftBaseDefault )
-import Control.Monad.Except        ( MonadError, ExceptT, runExceptT, throwError )
-import Control.Monad.IO.Class      ( MonadIO, liftIO )
-import Control.Monad.Logger        ( LogStr, runLoggingT )
-import Control.Monad.Reader        ( MonadReader, ReaderT, ask, asks, local, runReaderT )
-import Control.Monad.Trans.AWS     ( AWST, Env, Error, runAWST )
-import Control.Monad.Trans.Class   ( MonadTrans, lift )
-import Control.Monad.Trans.Control ( MonadBaseControl
-                                   , MonadTransControl
-                                   , StM
-                                   , StT
-                                   , ComposeSt
-                                   , liftBaseWith
-                                   , liftWith
-                                   , defaultLiftBaseWith
-                                   , defaultRestoreM
-                                   , restoreM
-                                   , restoreT )
-import Data.Aeson                  ( FromJSON, Value(..), parseJSON, (.:) )
-import Data.HashMap.Strict         ( fromList, lookup )
-import Data.Text                   ( pack )
-import Data.UUID                   ( toString )
-import Data.UUID.V4                ( nextRandom )
-import Network.AWS.SWF
-import Network.AWS.Flow.Types
-import Prelude              hiding ( lookup )
-
--- FlowT
-
-runFlowT :: MonadIO m => FlowEnv -> FlowT m a -> m (Either FlowError a)
-runFlowT e (FlowT k) = runExceptT (runReaderT (runLoggingT k l) e) where
-  l = const . const . const $ feLogger e
-
-instance MonadBase b m => MonadBase b (FlowT m) where
-    liftBase = liftBaseDefault
-
-instance MonadBaseControl b m => MonadBaseControl b (FlowT m) where
-    type StM (FlowT m) a = ComposeSt FlowT m a
-
-    liftBaseWith = defaultLiftBaseWith
-
-    restoreM = defaultRestoreM
-
-instance MonadTrans FlowT where
-    lift = FlowT . lift . lift . lift
-
-instance MonadTransControl FlowT where
-    type StT FlowT a =
-      StT (ExceptT FlowError) (StT (ReaderT FlowEnv) a)
-
-    liftWith f = FlowT $
-      liftWith $ \g ->
-        liftWith $ \h ->
-          liftWith $ \i ->
-            f (i . h . g . unFlowT)
-
-    restoreT = FlowT . restoreT . restoreT . restoreT
-
-instance Monad m => MonadReader FlowEnv (FlowT m) where
-  ask = FlowT ask
-  local f = FlowT . local f . unFlowT
-
--- DecideT
-
-runDecideT :: MonadIO m => DecideEnv -> DecideT m a -> m (Either FlowError a)
-runDecideT e (DecideT k) = runExceptT (runReaderT (runLoggingT k l) e) where
-  l = const . const . const $ deLogger e
-
-instance MonadBase b m => MonadBase b (DecideT m) where
-    liftBase = liftBaseDefault
-
-instance MonadBaseControl b m => MonadBaseControl b (DecideT m) where
-    type StM (DecideT m) a = ComposeSt DecideT m a
-
-    liftBaseWith = defaultLiftBaseWith
-
-    restoreM = defaultRestoreM
-
-instance MonadTrans DecideT where
-    lift = DecideT . lift . lift . lift
-
-instance MonadTransControl DecideT where
-    type StT DecideT a =
-      StT (ExceptT FlowError) (StT (ReaderT DecideEnv) a)
-
-    liftWith f = DecideT $
-      liftWith $ \g ->
-        liftWith $ \h ->
-          liftWith $ \i ->
-            f (i . h . g . unDecideT)
-
-    restoreT = DecideT . restoreT . restoreT . restoreT
-
-instance Monad m => MonadReader DecideEnv (DecideT m) where
-  ask = DecideT ask
-  local f = DecideT . local f . unDecideT
-
--- Planning
-
-instance FromJSON Plan where
-  parseJSON (Object v) =
-    Plan           <$>
-      v .: "start" <*>
-      v .: "specs" <*>
-      v .: "end"
-  parseJSON _ = mzero
-
-instance FromJSON Spec where
-  parseJSON (Object v) =
-    msum
-      [ Work           <$>
-          v .: "work"
-      , Sleep          <$>
-          v .: "sleep"
-      ]
-  parseJSON _ =
-    mzero
-
-instance FromJSON End where
-  parseJSON (String v)
-    | v == "stop"     = return Stop
-    | v == "continue" = return Continue
-    | otherwise = mzero
-  parseJSON _ = mzero
-
-instance FromJSON Start where
-  parseJSON (Object v) =
-    Start         <$>
-      v .: "flow"
-  parseJSON _ = mzero
-
-instance FromJSON Task where
-  parseJSON (Object v) =
-    Task             <$>
-      v .: "name"    <*>
-      v .: "version" <*>
-      v .: "queue"   <*>
-      v .: "timeout"
-  parseJSON _ = mzero
-
-instance FromJSON Timer where
-  parseJSON (Object v) =
-    Timer            <$>
-      v .: "name"    <*>
-      v .: "timeout"
-  parseJSON _ = mzero
-
--- Helpers
-
-hoistAWSEither :: MonadError FlowError m => Either Error a -> m a
-hoistAWSEither = either (throwError . AWSError) return
-
-runAWS :: MonadFlow m => (FlowEnv -> Env) -> AWST m a -> m a
-runAWS env action = do
-  e <- asks env
-  r <- runAWST e action
-  hoistAWSEither r
-
-throwStringError :: MonadError FlowError m => String -> m a
-throwStringError = throwError . FlowError
-
-hoistStringEither :: MonadError FlowError m => Either String a -> m a
-hoistStringEither = either throwStringError return
-
-runDecide :: (MonadError FlowError m, MonadIO m)
-          => (LogStr -> IO ()) -> Plan -> [HistoryEvent] -> DecideT m a -> m a
-runDecide logger plan events action =
-  runDecideT env action >>= hoistFlowEither
-  where
-    env = DecideEnv logger plan events findEvent where
-      findEvent =
-        flip lookup $ fromList $ flip map events $ \e ->
-          (e ^. heEventId, e)
-    hoistFlowEither = either throwError return
-
-maybeToEither :: e -> Maybe a -> Either e a
-maybeToEither e = maybe (Left e) Right
-
-maybeToFlowError :: MonadError FlowError m => String -> Maybe a -> m a
-maybeToFlowError e = hoistStringEither . maybeToEither e
-
-newUid :: MonadIO m => m Uid
-newUid =
-  liftIO $ do
-    r <- nextRandom
-    return $ pack $ toString r
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
@@ -1,22 +1,21 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE ConstraintKinds  #-}
-
 module Network.AWS.Flow.S3
   ( putObjectAction
   ) where
 
-import Control.Monad.Reader      ( asks )
-import Control.Monad.Trans.AWS   ( send_, sourceBody )
-import Data.Conduit.Binary       ( sourceLbs )
+import Network.AWS.Data.Body
+import Control.Monad.Reader
+import Control.Monad.Trans.AWS
+import Data.Conduit.Binary
+import Data.Monoid
 import Network.AWS.Flow.Types
-import Network.AWS.Flow.Internal ( runAWS )
-import Network.AWS.S3     hiding ( bucket )
+import Network.AWS.S3
 
 -- Actions
 
 putObjectAction :: MonadFlow m => Artifact -> m ()
 putObjectAction (key, hash, size, blob) = do
-  bucket <- asks feBucket
-  runAWS feEnv $
-    send_ $ putObject body bucket key where
-      body = sourceBody hash size $ sourceLbs blob
+  timeout' <- asks feTimeout
+  bucket' <- asks feBucket
+  prefix <- asks fePrefix
+  void $ timeout timeout' $
+    send $ putObject (BucketName $ bucket') (ObjectKey $ prefix <> "/" <> key) (Hashed $ hashedBody hash size $ sourceLbs blob)
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
@@ -1,7 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE FlexibleContexts  #-}
-{-# LANGUAGE ConstraintKinds   #-}
-
 module Network.AWS.Flow.SWF
   ( registerDomainAction
   , registerActivityTypeAction
@@ -19,90 +15,97 @@
   , startChildWorkflowExecutionDecision
   ) where
 
-import Control.Lens              ( (^.), (.~), (&) )
-import Control.Monad             ( liftM )
-import Control.Monad.Reader      ( asks )
-import Control.Monad.Trans.AWS   ( paginate, send, send_ )
-import Data.Conduit              ( ($$) )
-import Data.Conduit.List         ( consume )
+import Control.Lens
+import Control.Monad.Reader
+import Control.Monad.Trans.AWS hiding ( Metadata )
+import Data.Conduit
+import Data.Conduit.List hiding ( concatMap )
 import Network.AWS.Flow.Types
-import Network.AWS.Flow.Internal ( runAWS )
 import Network.AWS.SWF
-import Safe                      ( headMay )
+import Safe
 
 -- Actions
 
 registerDomainAction :: MonadFlow m => m ()
 registerDomainAction = do
+  timeout' <- asks feTimeout
   domain <- asks feDomain
-  runAWS feEnv $
-    send_ $ registerDomain domain "30"
+  void $ timeout timeout' $
+    send $ registerDomain domain "30"
 
 registerActivityTypeAction :: MonadFlow m => Name -> Version -> Timeout -> m ()
-registerActivityTypeAction name version timeout = do
+registerActivityTypeAction name version t = do
+  timeout' <- asks feTimeout
   domain <- asks feDomain
-  runAWS feEnv $
-    send_ $ registerActivityType domain name version &
+  void $ timeout timeout' $
+    send $ registerActivityType domain name version &
       ratDefaultTaskHeartbeatTimeout .~ Just "NONE" &
       ratDefaultTaskScheduleToCloseTimeout .~ Just "NONE" &
       ratDefaultTaskScheduleToStartTimeout .~ Just "60" &
-      ratDefaultTaskStartToCloseTimeout .~ Just timeout
+      ratDefaultTaskStartToCloseTimeout .~ Just t
 
 registerWorkflowTypeAction :: MonadFlow m => Name -> Version -> Timeout -> m ()
-registerWorkflowTypeAction name version timeout = do
+registerWorkflowTypeAction name version t = do
+  timeout' <- asks feTimeout
   domain <- asks feDomain
-  runAWS feEnv $
-    send_ $ registerWorkflowType domain name version &
+  void $ timeout timeout' $
+    send $ registerWorkflowType domain name version &
       rwtDefaultChildPolicy .~ Just Abandon &
-      rwtDefaultExecutionStartToCloseTimeout .~ Just timeout &
+      rwtDefaultExecutionStartToCloseTimeout .~ Just t &
       rwtDefaultTaskStartToCloseTimeout .~ Just "60"
 
 startWorkflowExecutionAction :: MonadFlow m
                              => Uid -> Name -> Version -> Queue -> Metadata -> m ()
 startWorkflowExecutionAction uid name version queue input = do
+  timeout' <- asks feTimeout
   domain <- asks feDomain
-  runAWS feEnv $
-    send_ $ startWorkflowExecution domain uid (workflowType name version) &
-      swe1TaskList .~ Just (taskList queue) &
-      swe1Input .~ input
+  void $ timeout timeout' $
+    send $ startWorkflowExecution domain uid (workflowType name version) &
+      sTaskList .~ Just (taskList queue) &
+      sInput .~ input
 
 pollForActivityTaskAction :: MonadFlow m => Queue -> m (Token, Uid, Metadata)
 pollForActivityTaskAction queue = do
+  timeout' <- asks fePollTimeout
   domain <- asks feDomain
-  runAWS fePollEnv $ do
+  timeout timeout' $ do
     r <- send $ pollForActivityTask domain (taskList queue)
     return
-      ( r ^. pfatrTaskToken
-      , r ^. pfatrWorkflowExecution ^. weWorkflowId
-      , r ^. pfatrInput )
+      ( r ^. pfatrsTaskToken
+      , r ^. pfatrsWorkflowExecution ^. weWorkflowId
+      , r ^. pfatrsInput )
 
 respondActivityTaskCompletedAction :: MonadFlow m => Token -> Metadata -> m ()
-respondActivityTaskCompletedAction token result =
-  runAWS feEnv $
-    send_ $ respondActivityTaskCompleted token &
+respondActivityTaskCompletedAction token result = do
+  timeout' <- asks feTimeout
+  void $ timeout timeout' $
+    send $ respondActivityTaskCompleted token &
       ratcResult .~ result
 
 respondActivityTaskFailedAction :: MonadFlow m => Token -> m ()
-respondActivityTaskFailedAction token =
-  runAWS feEnv $
-    send_ $ respondActivityTaskFailed token
+respondActivityTaskFailedAction token = do
+  timeout' <- asks feTimeout
+  void $ timeout timeout' $
+    send $ respondActivityTaskFailed token
 
 pollForDecisionTaskAction :: MonadFlow m => Queue -> m (Maybe Token, [HistoryEvent])
 pollForDecisionTaskAction queue = do
+  timeout' <- asks fePollTimeout
   domain <- asks feDomain
-  runAWS fePollEnv $ do
+  timeout timeout' $ do
     rs <- paginate (pollForDecisionTask domain (taskList queue) &
       pfdtReverseOrder .~ Just True &
       pfdtMaximumPageSize .~ Just 100)
         $$ consume
     return
-      ( liftM (^. pfdtrTaskToken) (headMay rs)
-      , concatMap (^. pfdtrEvents) rs)
+      ( liftM (^. pfdtrsTaskToken) (headMay rs)
+      , concatMap (^. pfdtrsEvents) rs)
 
 respondDecisionTaskCompletedAction :: MonadFlow m => Token -> [Decision] -> m ()
-respondDecisionTaskCompletedAction token decisions =
-  runAWS feEnv $
-    send_ $ respondDecisionTaskCompleted token &
+respondDecisionTaskCompletedAction token decisions = do
+  timeout' <- asks fePollTimeout
+  void $ timeout timeout' $
+    send $ respondDecisionTaskCompleted token &
       rdtcDecisions .~ decisions
 
 -- Decisions
@@ -123,10 +126,10 @@
         cwedaResult .~ result
 
 startTimerDecision :: Uid -> Name -> Timeout -> Decision
-startTimerDecision uid name timeout =
+startTimerDecision uid name t =
   decision StartTimer &
     dStartTimerDecisionAttributes .~ Just attrs where
-      attrs = startTimerDecisionAttributes uid timeout &
+      attrs = startTimerDecisionAttributes uid t &
         stdaControl .~ Just name
 
 continueAsNewWorkflowExecutionDecision :: Version -> Queue -> Metadata -> Decision
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,21 +1,27 @@
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE ConstraintKinds            #-}
+{-# LANGUAGE FlexibleInstances          #-}
+{-# LANGUAGE MultiParamTypeClasses      #-}
+{-# LANGUAGE UndecidableInstances       #-}
+{-# LANGUAGE TypeFamilies               #-}
 
 module Network.AWS.Flow.Types where
 
-import Control.Applicative         ( Applicative )
-import Control.Monad.Catch         ( MonadCatch, MonadThrow )
-import Control.Monad.IO.Class      ( MonadIO )
-import Control.Monad.Except        ( ExceptT, MonadError )
-import Control.Monad.Logger        ( LoggingT, MonadLogger, LogStr )
-import Control.Monad.Reader        ( ReaderT, MonadReader )
-import Control.Monad.Trans.AWS     ( Credentials, Env, Error, Region )
-import Control.Monad.Trans.Control ( MonadBaseControl )
-import Crypto.Hash                 ( Digest, SHA256 )
-import Data.ByteString.Lazy        ( ByteString )
-import Data.Int                    ( Int64 )
-import Data.Text                   ( Text )
-import Network.AWS.SWF.Types       ( HistoryEvent )
+import Control.Lens
+import Control.Monad.Base
+import Control.Monad.Catch
+import Control.Monad.Except
+import Control.Monad.Logger
+import Control.Monad.Reader
+import Control.Monad.Trans.Control
+import Control.Monad.Trans.Resource
+import Control.Monad.Trans.AWS
+import Data.Aeson
+import Data.ByteString.Lazy
+import Data.Conduit.Lazy
+import Data.Text
+import Network.AWS.Data.Crypto
+import Network.AWS.SWF.Types
 
 type Uid      = Text
 type Name     = Text
@@ -24,7 +30,8 @@
 type Token    = Text
 type Timeout  = Text
 type Metadata = Maybe Text
-type Artifact = (Text, Digest SHA256, Int64, ByteString)
+type Artifact = (Text, Digest SHA256, Integer, ByteString)
+type Log      = LogStr -> IO ()
 
 data FlowConfig = FlowConfig
   { fcRegion      :: Region
@@ -33,70 +40,172 @@
   , fcPollTimeout :: Int
   , fcDomain      :: Text
   , fcBucket      :: Text
+  , fcPrefix      :: Text
   }
 
+instance FromJSON Region where
+  parseJSON (String v)
+    | v == "eu-west-1"          = return Ireland
+    | v == "eu-central-1"       = return Frankfurt
+    | v == "ap-northeast-1"     = return Tokyo
+    | v == "ap-southeast-1"     = return Singapore
+    | v == "ap-southeast-2"     = return Sydney
+    | v == "cn-north-1"         = return Beijing
+    | v == "us-east-1"          = return NorthVirginia
+    | v == "us-west-1"          = return NorthCalifornia
+    | v == "us-west-2"          = return Oregon
+    | v == "us-gov-west-1"      = return GovCloud
+    | v == "fips-us-gov-west-1" = return GovCloudFIPS
+    | v == "sa-east-1"          = return SaoPaulo
+    | otherwise = mzero
+  parseJSON _ = mzero
+
+instance FromJSON Credentials where
+  parseJSON (Object v) =
+    FromEnv                     <$>
+      v .: "access-key-env-var" <*>
+      v .: "secret-key-env-var" <*>
+      pure Nothing
+  parseJSON _ = mzero
+
+instance FromJSON FlowConfig where
+  parseJSON (Object v) =
+    FlowConfig            <$>
+      v .: "region"       <*>
+      v .: "credentials"  <*>
+      v .: "timeout"      <*>
+      v .: "poll-timeout" <*>
+      v .: "domain"       <*>
+      v .: "bucket"       <*>
+      v .: "prefix"
+  parseJSON _ = mzero
+
 data FlowEnv = FlowEnv
-  { feLogger  :: LogStr -> IO ()
-  , feEnv     :: Env
-  , fePollEnv :: Env
-  , feDomain  :: Text
-  , feBucket  :: Text
+  { feLogger      :: Log
+  , feEnv         :: Env
+  , feTimeout     :: Seconds
+  , fePollTimeout :: Seconds
+  , feDomain      :: Text
+  , feBucket      :: Text
+  , fePrefix      :: Text
   }
 
-data FlowError
-  = FlowError String
-  | AWSError Error
-  deriving ( Show )
-
 newtype FlowT m a = FlowT
-  { unFlowT :: LoggingT (ReaderT FlowEnv (ExceptT FlowError m)) a
+  { unFlowT :: AWST' FlowEnv m a
   } deriving ( Functor
              , Applicative
              , Monad
              , MonadIO
-             , MonadLogger
-             , MonadCatch
-             , MonadThrow
-             , MonadError FlowError
+             , MonadActive
+             , MonadTrans
              )
 
 type MonadFlow m =
-  ( MonadBaseControl IO m
-  , MonadCatch m
-  , MonadIO m
-  , MonadLogger m
+  ( MonadCatch m
+  , MonadThrow m
+  , MonadResource m
   , MonadReader FlowEnv m
-  , MonadError FlowError m
   )
 
+instance MonadThrow m => MonadThrow (FlowT m) where
+    throwM = lift . throwM
+
+instance MonadCatch m => MonadCatch (FlowT m) where
+    catch (FlowT m) f = FlowT (catch m (unFlowT . f))
+
+instance MonadBase b m => MonadBase b (FlowT m) where
+    liftBase = liftBaseDefault
+
+instance MonadTransControl FlowT where
+    type StT FlowT a = StT (ReaderT FlowEnv) a
+
+    liftWith = defaultLiftWith FlowT unFlowT
+    restoreT = defaultRestoreT FlowT
+
+instance MonadBaseControl b m => MonadBaseControl b (FlowT m) where
+    type StM (FlowT m) a = ComposeSt FlowT m a
+
+    liftBaseWith = defaultLiftBaseWith
+    restoreM     = defaultRestoreM
+
+instance MonadResource m => MonadResource (FlowT m) where
+    liftResourceT = lift . liftResourceT
+
+instance MonadError e m => MonadError e (FlowT m) where
+    throwError     = lift . throwError
+    catchError m f = FlowT (catchError (unFlowT m) (unFlowT . f))
+
+instance Monad m => MonadReader FlowEnv (FlowT m) where
+    ask     = FlowT ask
+    local f = FlowT . local f . unFlowT
+    reader  = FlowT . reader
+
+instance HasEnv FlowEnv where
+  environment = lens feEnv (\s a -> s { feEnv = a })
+
+runFlowT :: FlowEnv -> FlowT m a -> m a
+runFlowT e (FlowT m) = runAWST e m
+
 data DecideEnv = DecideEnv
-  { deLogger    :: LogStr -> IO ()
+  { deLogger    :: Log
   , dePlan      :: Plan
   , deEvents    :: [HistoryEvent]
   , deFindEvent :: Integer -> Maybe HistoryEvent
   }
 
 newtype DecideT m a = DecideT
-  { unDecideT :: LoggingT (ReaderT DecideEnv (ExceptT FlowError m)) a
+  { unDecideT :: ReaderT DecideEnv m a
   } deriving ( Functor
              , Applicative
              , Monad
              , MonadIO
-             , MonadLogger
-             , MonadCatch
-             , MonadThrow
-             , MonadError FlowError
+             , MonadActive
+             , MonadTrans
              )
 
 type MonadDecide m =
-  ( MonadBaseControl IO m
-  , MonadCatch m
-  , MonadIO m
-  , MonadLogger m
+  ( MonadCatch m
+  , MonadThrow m
+  , MonadResource m
   , MonadReader DecideEnv m
-  , MonadError FlowError m
   )
 
+instance MonadThrow m => MonadThrow (DecideT m) where
+    throwM = lift . throwM
+
+instance MonadCatch m => MonadCatch (DecideT m) where
+    catch (DecideT m) f = DecideT (catch m (unDecideT . f))
+
+instance MonadBase b m => MonadBase b (DecideT m) where
+    liftBase = liftBaseDefault
+
+instance MonadTransControl DecideT where
+    type StT DecideT a = StT (ReaderT DecideEnv) a
+
+    liftWith = defaultLiftWith DecideT unDecideT
+    restoreT = defaultRestoreT DecideT
+
+instance MonadBaseControl b m => MonadBaseControl b (DecideT m) where
+    type StM (DecideT m) a = ComposeSt DecideT m a
+
+    liftBaseWith = defaultLiftBaseWith
+    restoreM     = defaultRestoreM
+
+instance MonadResource m => MonadResource (DecideT m) where
+    liftResourceT = lift . liftResourceT
+
+instance MonadError e m => MonadError e (DecideT m) where
+    throwError     = lift . throwError
+    catchError m f = DecideT (catchError (unDecideT m) (unDecideT . f))
+
+instance Monad m => MonadReader DecideEnv (DecideT m) where
+    ask     = DecideT ask
+    local f = DecideT . local f . unDecideT
+    reader  = DecideT . reader
+
+runDecideT :: DecideEnv -> DecideT m a -> m a
+runDecideT e (DecideT m) = runReaderT m e
+
 data Task = Task
   { tskName    :: Name
   , tskVersion :: Version
@@ -104,15 +213,37 @@
   , tskTimeout :: Timeout
   } deriving ( Eq, Read, Show )
 
+instance FromJSON Task where
+  parseJSON (Object v) =
+    Task             <$>
+      v .: "name"    <*>
+      v .: "version" <*>
+      v .: "queue"   <*>
+      v .: "timeout"
+  parseJSON _ = mzero
+
 data Timer = Timer
   { tmrName    :: Name
   , tmrTimeout :: Timeout
   } deriving ( Eq, Read, Show )
 
+instance FromJSON Timer where
+  parseJSON (Object v) =
+    Timer            <$>
+      v .: "name"    <*>
+      v .: "timeout"
+  parseJSON _ = mzero
+
 data Start = Start
   { strtTask :: Task
   } deriving ( Eq, Read, Show )
 
+instance FromJSON Start where
+  parseJSON (Object v) =
+    Start         <$>
+      v .: "flow"
+  parseJSON _ = mzero
+
 data Spec
   = Work
   { wrkTask :: Task
@@ -121,13 +252,40 @@
   { slpTimer :: Timer
   } deriving ( Eq, Read, Show )
 
+instance FromJSON Spec where
+  parseJSON (Object v) =
+    msum
+      [ Work           <$>
+          v .: "work"
+      , Sleep          <$>
+          v .: "sleep"
+      ]
+  parseJSON _ =
+    mzero
+
 data End
   = Stop
   | Continue
   deriving ( Eq, Read, Show )
 
+instance FromJSON End where
+  parseJSON (String v)
+    | v == "stop"     = return Stop
+    | v == "continue" = return Continue
+    | otherwise = mzero
+  parseJSON _ = mzero
+
 data Plan = Plan
   { plnStart :: Start
   , plnSpecs :: [Spec]
   , plnEnd   :: End
   } deriving ( Eq, Read, Show )
+
+instance FromJSON Plan where
+  parseJSON (Object v) =
+    Plan           <$>
+      v .: "start" <*>
+      v .: "specs" <*>
+      v .: "end"
+  parseJSON _ = mzero
+
diff --git a/src/main/Act.hs b/src/main/Act.hs
--- a/src/main/Act.hs
+++ b/src/main/Act.hs
@@ -1,38 +1,25 @@
-{-# LANGUAGE OverloadedStrings   #-}
-{-# LANGUAGE RecordWildCards     #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 
-module Act ( main ) where
+module Act
+  ( main
+  ) where
 
-import Control.Exception          ( SomeException )
-import Control.Monad              ( forever, forM, mzero, liftM )
-import Control.Monad.IO.Class     ( MonadIO )
-import Crypto.Hash                ( hash )
-import Data.ByteString            ( length )
-import Data.ByteString.Lazy       ( fromStrict )
-import Data.Text                  ( Text, pack, append, words )
+import Control.Exception
+import Control.Monad
+import Control.Monad.Trans.Resource
+import Control.Monad.IO.Class
+import Data.Aeson.Encode
+import Data.ByteString ( length )
+import Data.ByteString.Lazy ( fromStrict )
+import Data.Text ( Text, pack, append, words, strip )
+import Data.Text.Lazy ( toStrict )
+import Data.Text.Lazy.Builder
 import Data.Yaml
-import Network.AWS.Flow           ( Artifact, Metadata, Queue, Uid, runFlowT, act )
-import Network.AWS.Flow.Env       ( flowEnv )
+import Network.AWS.Data.Crypto
+import Network.AWS.Flow
 import Options.Applicative hiding ( action )
-import Shelly              hiding ( FilePath )
-import Prelude             hiding ( length, readFile, words, writeFile )
-
-data Container = Container
-  { cImage   :: Text
-  , cCommand :: Text
-  , cVolumes :: [Text]
-  , cDevices :: [Text]
-  } deriving ( Eq, Read, Show )
-
-instance FromJSON Container where
-  parseJSON (Object v) =
-    Container              <$>
-    v .:  "image"          <*>
-    v .:  "command"        <*>
-    v .:? "volumes" .!= [] <*>
-    v .:? "devices" .!= []
-  parseJSON _ = mzero
+import Shelly hiding ( FilePath )
+import Prelude hiding ( length, readFile, words, writeFile )
 
 data Args = Args
   { aConfig    :: FilePath
@@ -68,9 +55,41 @@
           , aContainer = container
           }
 
+data Container = Container
+  { cImage       :: Text
+  , cCommand     :: Text
+  , cVolumes     :: [Text]
+  , cDevices     :: [Text]
+  , cEnvironment :: [Text]
+  } deriving ( Eq, Read, Show )
+
+instance FromJSON Container where
+  parseJSON (Object v) =
+    Container                  <$>
+    v .:  "image"              <*>
+    v .:  "command"            <*>
+    v .:? "volumes"     .!= [] <*>
+    v .:? "devices"     .!= [] <*>
+    v .:? "environment" .!= []
+  parseJSON _ = mzero
+
+data Control = Control
+  { cUid :: Uid
+  } deriving ( Eq, Read, Show )
+
+instance ToJSON Control where
+  toJSON Control{..} = object
+    [ "run_uid" .= cUid
+    ]
+
+encodeText :: ToJSON a => a -> Text
+encodeText =
+  toStrict . toLazyText . encodeToTextBuilder . toJSON
+
 exec :: MonadIO m => Container -> Uid -> Metadata -> m (Metadata, [Artifact])
 exec container uid metadata =
   shelly $ withDir $ \dataDir storeDir -> do
+    control dataDir $ encodeText $ Control uid
     input dataDir metadata
     docker dataDir storeDir container
     result <- output dataDir
@@ -81,12 +100,14 @@
           mkdir $ dir </> pack "data"
           mkdir $ dir </> pack "store"
           action (dir </> pack "data") (dir </> pack "store")
+      control dir =
+        writefile (dir </> pack "control.json")
       input dir =
-        maybe_ $ writefile $ dir </> pack "input.json" where
+        maybe_ (writefile $ dir </> pack "input.json") where
           maybe_ =
             maybe (return ())
       output dir =
-        catch_sh_maybe $ readfile $ dir </> pack "output.json" where
+        catch_sh_maybe (readfile $ dir </> pack "output.json") where
           catch_sh_maybe action =
             catch_sh (liftM Just action) $ \(_ :: SomeException) -> return Nothing
       store dir = do
@@ -99,31 +120,27 @@
                  , fromIntegral $ length blob
                  , fromStrict blob
                  )
-      docker dataDir storeDir Container{..} =
+      docker dataDir storeDir Container{..} = do
+        devices <- forM cDevices $ \device ->
+          liftM strip $ run "readlink" ["-f", device]
         run_ "docker" $ concat
           [["run"]
-          , devices
-          , volumes
+          , concatMap (("--device" :) . return) devices
+          , concatMap (("--env"    :) . return) cEnvironment
+          , concatMap (("--volume" :) . return) $
+              append (toTextIgnore dataDir)  ":/app/data"  :
+              append (toTextIgnore storeDir) ":/app/store" : cVolumes
           , [cImage]
           , words cCommand
-          ] where
-            devices =
-              concatMap (("--device" :) . return) cDevices
-            volumes =
-              concatMap (("--volume" :) . return) $
-                append (toTextIgnore dataDir)  ":/app/data"  :
-                append (toTextIgnore storeDir) ":/app/store" : cVolumes
+          ]
 
 main :: IO ()
 main =
   execParser argsPI >>= call where
     call Args{..} = do
-      config <- decodeFile aConfig >>= hoistMaybe "Bad Config"
-      container <- decodeFile aContainer >>= hoistMaybe "Bad Container"
+      config <- decodeFile aConfig >>= maybeThrow (userError "Bad Config")
+      container <- decodeFile aContainer >>= maybeThrow (userError "Bad Container")
       env <- flowEnv config
-      forever $ do
-        r <- runFlowT env $
+      forever $
+        runResourceT $ runFlowT env $
           act aQueue $ exec container
-        print r where
-          hoistMaybe s =
-            maybe (error s) return
diff --git a/src/main/Decide.hs b/src/main/Decide.hs
--- a/src/main/Decide.hs
+++ b/src/main/Decide.hs
@@ -1,13 +1,13 @@
-{-# LANGUAGE RecordWildCards #-}
-
-module Decide ( main ) where
+module Decide
+  ( main
+  ) where
 
-import Control.Monad        ( forever )
-import Data.Yaml            ( decodeFile )
-import Network.AWS.Flow     ( runFlowT, decide )
-import Network.AWS.Flow.Env ( flowEnv )
+import Control.Monad
+import Control.Monad.Trans.Resource
+import Data.Yaml
+import Network.AWS.Flow
 import Options.Applicative
-import Prelude       hiding ( readFile )
+import Prelude hiding ( readFile )
 
 data Args = Args
   { aConfig :: FilePath
@@ -37,15 +37,12 @@
             }
 
 main :: IO ()
-main =
+main = do
   execParser argsPI >>= call where
     call Args{..} = do
-      config <- decodeFile aConfig >>= hoistMaybe "Bad Config"
-      plan <- decodeFile aPlan >>= hoistMaybe "Bad Plan"
+      config <- decodeFile aConfig >>= maybeThrow (userError "Bad Config")
+      plan <- decodeFile aPlan >>= maybeThrow (userError "Bad Plan")
       env <- flowEnv config
-      forever $ do
-        r <- runFlowT env $
+      forever $
+        runResourceT $ runFlowT env $
           decide plan
-        print r where
-          hoistMaybe s =
-            maybe (error s) return
diff --git a/src/main/Execute.hs b/src/main/Execute.hs
--- a/src/main/Execute.hs
+++ b/src/main/Execute.hs
@@ -1,18 +1,18 @@
-{-# LANGUAGE RecordWildCards #-}
-
-module Execute ( main ) where
+module Execute
+  ( main
+  ) where
 
-import Data.Text.IO         ( readFile )
-import Data.Yaml            ( decodeFile )
-import Network.AWS.Flow     ( Plan (..), Start (..), runFlowT, execute )
-import Network.AWS.Flow.Env ( flowEnv )
+import Control.Monad.Trans.Resource
+import Data.Text.IO
+import Data.Yaml
+import Network.AWS.Flow
 import Options.Applicative
-import Prelude       hiding ( readFile )
+import Prelude hiding ( readFile )
 
 data Args = Args
   { aConfig :: FilePath
   , aPlan   :: FilePath
-  , aInput  :: FilePath
+  , aInput  :: Maybe FilePath
   } deriving ( Eq, Read, Show )
 
 argsPI :: ParserInfo Args
@@ -32,11 +32,11 @@
           <> short   'p'
           <> metavar "FILE"
           <> help    "AWS SWF Service Flow plan" )
-      <*> strOption
+      <*> optional ( strOption
           ( long     "input"
           <> short   'i'
           <> metavar "FILE"
-          <> help    "AWS SWF Service Flow input" ) where
+          <> help    "AWS SWF Service Flow input" ) ) where
           args config plan input = Args
             { aConfig = config
             , aPlan   = plan
@@ -47,12 +47,11 @@
 main =
   execParser argsPI >>= call where
     call Args{..} = do
-      config <- decodeFile aConfig >>= hoistMaybe "Bad Config"
-      plan <- decodeFile aPlan >>= hoistMaybe "Bad Plan"
-      input <- readFile aInput
+      config <- decodeFile aConfig >>= maybeThrow (userError "Bad Config")
+      plan <- decodeFile aPlan >>= maybeThrow (userError "Bad Plan")
+      input <- readFileMaybe aInput
       env <- flowEnv config
-      r <- runFlowT env $
-        execute (strtTask $ plnStart plan) (Just input)
-      print r where
-        hoistMaybe s =
-          maybe (error s) return
+      runResourceT $ runFlowT env $
+        execute (strtTask $ plnStart plan) input where
+          readFileMaybe =
+            maybe (return Nothing) ((>>= return . Just) . readFile)
diff --git a/src/main/Register.hs b/src/main/Register.hs
--- a/src/main/Register.hs
+++ b/src/main/Register.hs
@@ -1,10 +1,10 @@
-{-# LANGUAGE RecordWildCards #-}
-
-module Register ( main ) where
+module Register
+  ( main
+  ) where
 
-import Data.Yaml            ( decodeFile )
-import Network.AWS.Flow     ( runFlowT, register )
-import Network.AWS.Flow.Env ( flowEnv )
+import Control.Monad.Trans.Resource hiding ( register )
+import Data.Yaml
+import Network.AWS.Flow
 import Options.Applicative
 
 data Args = Args
@@ -35,14 +35,11 @@
             }
 
 main :: IO ()
-main =
+main = do
   execParser argsPI >>= call where
     call Args{..} = do
-      config <- decodeFile aConfig >>= hoistMaybe "Bad Config"
-      plan <- decodeFile aPlan >>= hoistMaybe "Bad Plan"
+      config <- decodeFile aConfig >>= maybeThrow (userError "Bad Config")
+      plan <- decodeFile aPlan >>= maybeThrow (userError "Bad Plan")
       env <- flowEnv config
-      r <- runFlowT env $
+      runResourceT $ runFlowT env $
         register plan
-      print r where
-        hoistMaybe s =
-          maybe (error s) return
diff --git a/wolf.cabal b/wolf.cabal
--- a/wolf.cabal
+++ b/wolf.cabal
@@ -1,5 +1,5 @@
 name:                wolf
-version:             0.1.1
+version:             0.2.0
 synopsis:            Amazon Simple Workflow Service Wrapper.
 homepage:            https://github.com/swift-nav/wolf
 license:             MIT
@@ -21,8 +21,7 @@
 
 library
   exposed-modules:     Network.AWS.Flow
-                     , Network.AWS.Flow.Env
-  other-modules:       Network.AWS.Flow.Internal
+  other-modules:       Network.AWS.Flow.Env
                      , Network.AWS.Flow.S3
                      , Network.AWS.Flow.SWF
                      , Network.AWS.Flow.Types
@@ -31,13 +30,13 @@
   ghc-options:         -Wall -fno-warn-orphans
   build-depends:       aeson
                      , amazonka
+                     , amazonka-core
                      , amazonka-s3
                      , amazonka-swf
-                     , base >= 4.7 && < 4.8
+                     , base >= 4.7 && < 5
                      , bytestring
                      , conduit
                      , conduit-extra
-                     , cryptohash
                      , exceptions
                      , fast-logger
                      , http-conduit
@@ -47,6 +46,7 @@
                      , mtl
                      , mtl-compat
                      , optparse-applicative
+                     , resourcet
                      , safe
                      , text
                      , transformers
@@ -54,51 +54,67 @@
                      , unordered-containers
                      , uuid
                      , yaml
+  default-extensions:  OverloadedStrings
+                       FlexibleContexts
+                       RecordWildCards
 
 executable wolf-register
   default-language:    Haskell2010
   main-is:             Register.hs
   hs-source-dirs:      src/main
-  ghc-options:         -Wall -fno-warn-orphans -main-is Register
-  build-depends:       base >= 4.7 && < 4.8
-                     , wolf
+  ghc-options:         -Wall -main-is Register
+  build-depends:       base
                      , optparse-applicative
+                     , resourcet
                      , text
+                     , wolf
                      , yaml
+  default-extensions:  OverloadedStrings
+                       RecordWildCards
 
 executable wolf-execute
   default-language:    Haskell2010
   main-is:             Execute.hs
   hs-source-dirs:      src/main
-  ghc-options:         -Wall -fno-warn-orphans -main-is Execute
-  build-depends:       base >= 4.7 && < 4.8
-                     , wolf
+  ghc-options:         -Wall -main-is Execute
+  build-depends:       base
                      , optparse-applicative
+                     , resourcet
                      , text
+                     , wolf
                      , yaml
+  default-extensions:  OverloadedStrings
+                       RecordWildCards
 
 executable wolf-decide
   default-language:    Haskell2010
   main-is:             Decide.hs
   hs-source-dirs:      src/main
-  ghc-options:         -Wall -fno-warn-orphans -main-is Decide
-  build-depends:       base >= 4.7 && < 4.8
-                     , wolf
+  ghc-options:         -Wall -main-is Decide
+  build-depends:       base
                      , optparse-applicative
+                     , resourcet
                      , text
+                     , wolf
                      , yaml
+  default-extensions:  OverloadedStrings
+                       RecordWildCards
 
 executable wolf-act
   default-language:    Haskell2010
   main-is:             Act.hs
   hs-source-dirs:      src/main
-  ghc-options:         -Wall -fno-warn-orphans -main-is Act
-  build-depends:       base >= 4.7 && < 4.8
-                     , wolf
+  ghc-options:         -Wall -main-is Act
+  build-depends:       aeson
+                     , amazonka-core
+                     , base
                      , bytestring
-                     , cryptohash
                      , optparse-applicative
                      , shelly
+                     , resourcet
                      , text
                      , transformers
+                     , wolf
                      , yaml
+  default-extensions:  OverloadedStrings
+                       RecordWildCards
