diff --git a/src/Zifter.hs b/src/Zifter.hs
--- a/src/Zifter.hs
+++ b/src/Zifter.hs
@@ -14,13 +14,17 @@
     , prechecker
     , checker
     , ziftP
+    , mapZ
+    , mapZ_
+    , forZ
+    , forZ_
     , recursiveZift
-      -- ** Zift Script utilities
     , ZiftScript
     , renderZiftSetup
       -- * Defining your own zift actions
     , Zift
     , getRootDir
+    , getTmpDir
     , getSettings
     , getSetting
     , Settings(..)
@@ -116,10 +120,12 @@
 runZiftAuto :: (ZiftContext -> Zift ()) -> Settings -> IO ()
 runZiftAuto func sets = do
     rd <- autoRootDir
+    td <- resolveDir rd ".zifter"
     pchan <- newTChanIO
     let ctx =
             ZiftContext
             { rootdir = rd
+            , tmpdir = td
             , settings = sets
             , printChan = pchan
             , recursionList = []
diff --git a/src/Zifter/Recurse.hs b/src/Zifter/Recurse.hs
--- a/src/Zifter/Recurse.hs
+++ b/src/Zifter/Recurse.hs
@@ -49,9 +49,7 @@
 recursively :: (Path Abs File -> Zift ()) -> Zift ()
 recursively func = do
     fs <- findZiftFilesRecursively
-    -- Do it in serial (for errors to show up nicely)
-    -- TODO make it possible to run them in parallel instead?
-    --      we might have to make it possible for zift to output something machine-readible instead.
+    -- In serial on purpose.
     forM_ fs func
 
 halfIndent :: String -> String
diff --git a/src/Zifter/Zift.hs b/src/Zifter/Zift.hs
--- a/src/Zifter/Zift.hs
+++ b/src/Zifter/Zift.hs
@@ -1,8 +1,13 @@
 module Zifter.Zift
     ( getRootDir
+    , getTmpDir
     , getSettings
     , getSetting
     , ziftP
+    , mapZ
+    , mapZ_
+    , forZ
+    , forZ_
     , printZift
     , printZiftMessage
     , printPreprocessingDone
@@ -13,8 +18,8 @@
     , module Zifter.Zift.Types
     ) where
 
+import Control.Monad
 import Control.Monad.IO.Class (liftIO)
-import Data.Foldable
 
 import System.Console.ANSI
 
@@ -30,6 +35,12 @@
 getRootDir :: Zift (Path Abs Dir)
 getRootDir = fmap rootdir getContext
 
+-- | Get the temporary directory of the @zift.hs@ script that is being executed.
+--
+-- To persist any state between runs, use this directory.
+getTmpDir :: Zift (Path Abs Dir)
+getTmpDir = fmap tmpdir getContext
+
 -- | Get all the 'Settings'
 getSettings :: Zift Settings
 getSettings = fmap settings getContext
@@ -40,7 +51,31 @@
 
 -- | Declare a given list of 'Zift' actions to be execute in parallel.
 ziftP :: [Zift ()] -> Zift ()
-ziftP = sequenceA_
+ziftP = mconcat
+
+-- | Like 'mapA', but specialised to 'Zift' and '[]', and ensures that the
+-- output of actions is printed in the right order, even if they are
+-- executed in an arbitrary order.
+mapZ :: (a -> Zift b) -> [a] -> Zift [b]
+mapZ func as = forZ as func
+
+-- | Like 'mapA_', but specialised to 'Zift' and '[]', and ensures that the
+-- output of actions is printed in the right order, even if they are
+-- executed in an arbitrary order.
+mapZ_ :: (a -> Zift b) -> [a] -> Zift ()
+mapZ_ func as = forZ_ as func
+
+-- | Like 'for', but specialised to 'Zift' and '[]', and ensures that the
+-- output of actions is printed in the right order, even if they are
+-- executed in an arbitrary order.
+forZ :: [a] -> (a -> Zift b) -> Zift [b]
+forZ [] _ = pure []
+forZ (a:as) func = (:) <$> func a <*> forZ as func
+
+-- | Like 'for_', but specialised to 'Zift' and '[]', and ensures that the output of
+-- actions is printed in the right order.
+forZ_ :: [a] -> (a -> Zift b) -> Zift ()
+forZ_ as func = void $ forZ as func
 
 -- | Print a message (with a newline appended to the end).
 printZift :: String -> Zift ()
diff --git a/src/Zifter/Zift/Types.hs b/src/Zifter/Zift/Types.hs
--- a/src/Zifter/Zift/Types.hs
+++ b/src/Zifter/Zift/Types.hs
@@ -25,6 +25,7 @@
 
 data ZiftContext = ZiftContext
     { rootdir :: Path Abs Dir
+    , tmpdir :: Path Abs Dir
     , settings :: Settings
     , printChan :: TChan ZiftOutput
     , recursionList :: [LMR] -- In reverse order
@@ -67,7 +68,7 @@
 -- | 'Zift' actions can be sequenced.
 --
 -- The implementation automatically parallelises the arguments of the
--- '(<*>)' function. If any of the actions fails, the other is cancelled
+-- @(<*>)@ function. If any of the actions fails, the other is cancelled
 -- and the result fails.
 instance Applicative Zift where
     pure a = Zift $ \_ st -> pure (pure a, st)
@@ -119,7 +120,7 @@
 
 -- | A 'Zift' action can fail.
 --
--- To make a Zift action fail, you can use the 'fail :: String -> Zift a'
+-- To make a Zift action fail, you can use the @fail :: String -> Zift a@
 -- function.
 --
 -- The implementation uses the given string as the message that is shown at
diff --git a/test/Zifter/ZiftSpec.hs b/test/Zifter/ZiftSpec.hs
--- a/test/Zifter/ZiftSpec.hs
+++ b/test/Zifter/ZiftSpec.hs
@@ -345,16 +345,20 @@
 
 forAllCtx :: Testable (IO b) => (ZiftContext -> IO b) -> Property
 forAllCtx func =
-    forAll genUnchecked $ \(rd, sets, rl) -> do
-        pchan <- atomically newTChan
-        let zc =
-                ZiftContext
-                { rootdir = rd
-                , settings = sets
-                , printChan = pchan
-                , recursionList = rl
-                }
-        func zc
+    forAll genUnchecked $ \rd ->
+        forAll genUnchecked $ \sets ->
+            forAll genUnchecked $ \rl ->
+                forAll genUnchecked $ \td -> do
+                    pchan <- atomically newTChan
+                    let zc =
+                            ZiftContext
+                            { rootdir = rd
+                            , tmpdir = td
+                            , settings = sets
+                            , printChan = pchan
+                            , recursionList = rl
+                            }
+                    func zc
 
 runZiftTest :: Settings -> Zift a -> IO (ZiftResult a, ZiftState)
 runZiftTest sets func = do
@@ -379,9 +383,11 @@
     -> IO (ZiftResult a, ZiftState)
 runZiftTestWith zs pchan sets func = do
     rd <- getCurrentDir
+    td <- resolveDir rd ".zifter"
     let zc =
             ZiftContext
             { rootdir = rd
+            , tmpdir = td
             , settings = sets
             , printChan = pchan
             , recursionList = []
diff --git a/test/ZifterSpec.hs b/test/ZifterSpec.hs
--- a/test/ZifterSpec.hs
+++ b/test/ZifterSpec.hs
@@ -8,6 +8,8 @@
 import Test.QuickCheck
 import Test.Validity
 
+import Path.IO
+
 import Control.Concurrent.STM
 import Data.GenValidity.Path ()
 import System.Exit (ExitCode(..))
@@ -24,9 +26,11 @@
     forAll genUnchecked $ \sets ->
         forAll genValid $ \rd -> do
             pchan <- newTChanIO
+            td <- resolveDir rd ".zifter"
             let ctx =
                     ZiftContext
                     { rootdir = rd
+                    , tmpdir = td
                     , settings = sets
                     , printChan = pchan
                     , recursionList = []
diff --git a/zifter.cabal b/zifter.cabal
--- a/zifter.cabal
+++ b/zifter.cabal
@@ -1,5 +1,5 @@
 name: zifter
-version: 0.0.1.1
+version: 0.0.1.2
 cabal-version: >=1.10
 build-type: Simple
 license: MIT
@@ -29,7 +29,7 @@
     build-depends:
         base >=4.9 && <=5,
         async >=2.1 && <2.2,
-        directory >=1.3 && <1.4,
+        directory >=1.2 && <1.4,
         exceptions >=0.8 && <0.9,
         filepath >=1.4 && <1.5,
         optparse-applicative >=0.13 && <0.14,
