diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,3 @@
+# Changelog for error-context
+
+## Unreleased changes
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Moritz Clasmeier (c) 2018
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Moritz Schulte nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,50 @@
+# !! This is experimental work-in-progress !!
+
+Welcome to *error-context*! This is a library providing context-aware
+error and exception handling for Haskell.
+
+## What problem does *error-context* attempt to solve?
+
+Good error handling is hard. Sometimes it happens that when
+propagating errors some context is lost. Call traces sometimes help,
+but the current solutions in Haskell-land for accessing call traces
+are rather limited. Furthermore, sometimes call traces that at written
+by and for humans are more convenient to read.
+
+The *error-context* library allows you to easily attach call traces
+('error contexts') to errors, in particular to exceptions. Special
+catch-functions are provided for accessing these contexts.
+
+## How to use it?
+
+Add an `ErrorContextT` layer to your monad transformer stack by adding
+`runErrorContextT` to the transformer unwrapping code.
+
+The `ErrorContextT` transformer provides the context-enriching logic
+via special implementations of `MonadThrow`, `MonadCatch` and
+`MonadIO`.
+
+## Examples
+
+See https://github.com/mtesseract/error-context/blob/master/test/Control/Error/Context/Test.hs.
+
+## What about "pure" exceptions?
+
+The `ErrorContextT` transformer implements `MonadThrow` and `MonadIO`,
+therefore exceptions thrown by `throwM` and via `liftIO` are
+automatically context-enriched. But the story for exceptional values
+created via
+
+```haskell
+throw :: Exception e => e -> a
+```
+
+are not context-enriched. But there is a workaround for this use-case:
+
+```haskell
+ensureExceptionContext :: (MonadCatch m, MonadErrorContext m) => m a -> m a
+```
+
+This function provides context-aware enriching for any exceptions
+thrown within some monadic value, including those thrown by evaluating
+values created by `throw`.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/error-context.cabal b/error-context.cabal
new file mode 100644
--- /dev/null
+++ b/error-context.cabal
@@ -0,0 +1,72 @@
+-- This file has been generated from package.yaml by hpack version 0.20.0.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: 73e6d19a6c9cb2896dc6e9b9b7465b7a556008ed26855c87123649feea1d426b
+
+name:           error-context
+version:        0.1.0.0
+synopsis:       Provides API for enriching errors with contexts
+description:    Please see the README on Github at <https://github.com/mtesseract/error-context#readme>
+category:       Control, Error Handling
+homepage:       https://github.com/mtesseract/error-context#readme
+bug-reports:    https://github.com/mtesseract/error-context/issues
+author:         Moritz Clasmeier
+maintainer:     mtesseract@silverratio.net
+copyright:      (c) 2018 Moritz Clasmeier
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+cabal-version:  >= 1.10
+
+extra-source-files:
+    ChangeLog.md
+    README.md
+
+source-repository head
+  type: git
+  location: https://github.com/mtesseract/error-context
+
+library
+  hs-source-dirs:
+      src
+  ghc-options: -Wall
+  build-depends:
+      base >=4.7 && <5
+    , either
+    , exceptions
+    , monad-logger
+    , mtl
+    , resourcet
+    , safe-exceptions
+    , text
+    , unliftio-core
+  exposed-modules:
+      Control.Error.Context
+  other-modules:
+      Paths_error_context
+  default-language: Haskell2010
+
+test-suite error-context-tests
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  hs-source-dirs:
+      test
+  ghc-options: -Wall -g -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      base >=4.7 && <5
+    , either
+    , error-context
+    , exceptions
+    , monad-logger
+    , mtl
+    , resourcet
+    , safe-exceptions
+    , tasty
+    , tasty-hunit
+    , text
+    , unliftio-core
+  other-modules:
+      Control.Error.Context.Test
+      Paths_error_context
+  default-language: Haskell2010
diff --git a/src/Control/Error/Context.hs b/src/Control/Error/Context.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Error/Context.hs
@@ -0,0 +1,291 @@
+{-|
+Module      : Control.Error.Context
+Description : API for enriching errors with contexts
+Copyright   : (c) Moritz Clasmeier 2018
+License     : BSD3
+Maintainer  : mtesseract@silverratio.net
+Stability   : experimental
+Portability : POSIX
+
+Provides an API for enriching errors with contexts.
+-}
+
+{-# LANGUAGE DeriveFunctor              #-}
+{-# LANGUAGE FlexibleInstances          #-}
+{-# LANGUAGE FunctionalDependencies     #-}
+{-# LANGUAGE GADTs                      #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE MultiParamTypeClasses      #-}
+{-# LANGUAGE OverloadedStrings          #-}
+{-# LANGUAGE ScopedTypeVariables        #-}
+{-# LANGUAGE UndecidableInstances       #-}
+
+module Control.Error.Context
+  ( ErrorContext(..)
+  , ErrorContextT
+  , MonadErrorContext(..)
+  , ErrorWithContext(..)
+  , runErrorContextT
+  , errorContextualize
+  , errorContextForget
+  , errorWithContextDump
+  , catchWithoutContext
+  , catchWithContext
+  , tryWithContext
+  , tryAnyWithContext
+  , catchJustWithContext
+  , catchAnyWithContext
+  , ensureExceptionContext
+  )
+  where
+
+import           Control.Exception.Safe       (SomeException (..), catchAny,
+                                               catchJust, try, tryAny)
+import           Control.Monad.Catch          (Exception (..), MonadCatch (..),
+                                               MonadThrow, throwM)
+import           Control.Monad.IO.Unlift
+import           Control.Monad.Reader
+import           Control.Monad.State
+import           Control.Monad.Trans.Resource
+import           Control.Monad.Writer
+import           Data.Either.Combinators      (mapLeft)
+import           Data.Text                    (Text)
+import qualified Data.Text                    as Text
+import           Data.Typeable
+
+-- | Monad type class providing contextualized errors.
+class (Monad m, MonadThrow m) => MonadErrorContext m where
+  errorContextCollect :: m ErrorContext     -- ^ Return the current error context.
+  withErrorContext    :: Text -> m a -> m a -- ^ Execute a monadic action while having the
+                                            -- provided 'Text' being pushed to the error context.
+
+-- | Data type implementing 'MonadErrorContext'.
+newtype ErrorContextT m a =
+  ErrorContextT { _runErrorContextT :: ReaderT ErrorContext m a
+                } deriving ( Functor
+                           , Applicative
+                           , Monad
+                           , MonadTrans
+                           , MonadState s
+                           , MonadWriter w )
+
+-- | Boundles an error with an 'ErrorContext'.
+data ErrorWithContext e =
+  ErrorWithContext ErrorContext e
+  deriving (Show)
+
+instance Functor ErrorWithContext where
+  fmap f (ErrorWithContext ctx e) = ErrorWithContext ctx (f e)
+
+-- | An @ErrorWithContext e@ can be used as an exception.
+instance Exception e => Exception (ErrorWithContext e) where
+  toException exn = SomeException exn
+  fromException (SomeException someExn) =
+    case cast someExn :: Maybe (ErrorWithContext e) of
+      Just (exnWithCtx @ (ErrorWithContext _ctx _exn)) ->
+        Just exnWithCtx
+      Nothing ->
+        Nothing
+  displayException (ErrorWithContext ctx exn) = do
+    "Exception: " <> displayException exn <> "\n" <> errorContextAsString ctx
+
+-- | Unwrap an 'ErrorContextT'. Exceptions of type @e@ thrown in the
+-- provided action via 'throwM' will cause an @'ErrorWithContext' e@
+-- exception to be propagated upwards.
+runErrorContextT
+  :: ErrorContextT m a
+  -> m a
+runErrorContextT ctx =
+  runReaderT (_runErrorContextT ctx) (ErrorContext [])
+
+instance (MonadCatch m, MonadIO m) => MonadIO (ErrorContextT m) where
+  liftIO m = do
+    ctx <- errorContextCollect
+    lift $ errorContextualizeIO ctx m
+
+    where errorContextualizeIO ctx io = liftIO $
+            catchAny io $ \ (SomeException exn) -> throwM (ErrorWithContext ctx exn)
+
+-- | Implement 'MonadErrorContext' for 'ErrorContextT'.
+instance MonadThrow m => MonadErrorContext (ErrorContextT m) where
+  errorContextCollect = ErrorContextT ask
+  withErrorContext layer (ErrorContextT m) =
+    ErrorContextT (local (errorContextPush layer) m)
+
+-- | Implement 'MonadThrow' for 'ErrorContextT'.
+instance MonadThrow m => MonadThrow (ErrorContextT m) where
+  throwM e = do
+    case fromException (toException e) :: Maybe (ErrorWithContext SomeException) of
+      Just exnWithCtx ->
+        lift $ throwM exnWithCtx
+      Nothing -> do
+        ctx <- errorContextCollect
+        lift $ throwM (ErrorWithContext ctx (SomeException e))
+
+-- | Implement 'MonadCatch' for 'ErrorContextT'.
+instance MonadCatch m => MonadCatch (ErrorContextT m) where
+  catch (ErrorContextT (ReaderT m)) c =
+    ErrorContextT . ReaderT $
+    \ r -> m r `catchWithoutContext` \ exn -> runReaderT (_runErrorContextT (c exn)) r
+
+-- | Like 'catch', but the handler is required to be context-aware. Is
+-- also able to catch exceptions of type 'e' (without context).
+catchWithContext
+  :: (MonadCatch m, Exception e) -- , MonadErrorContext m)
+  => m a
+  -> (ErrorWithContext e -> m a)
+  -> m a
+catchWithContext m handler = catchJust pre m handler
+  where pre :: Exception e => SomeException -> Maybe (ErrorWithContext e)
+        pre someExn =
+          -- First we check if the exception is of the type
+          -- 'ErrorWithContext e'. If so, provide it to the handler
+          -- directly.
+          case fromException someExn of
+            Just (ErrorWithContext ctx someExnWithoutCtx :: ErrorWithContext SomeException) ->
+              case fromException someExnWithoutCtx of
+                Just exn -> Just (ErrorWithContext ctx exn)
+                Nothing  -> Nothing
+            Nothing  ->
+              -- Then we check if the exception is of the type 'e',
+              -- (without context). In this case we convert it into an
+              -- 'ErrorWithContext e' by adding an empty context and
+              -- provide the wrapped exception with context to the
+              -- handler.
+              case fromException someExn of
+                Just exn ->
+                  Just (ErrorWithContext (ErrorContext []) exn)
+                Nothing ->
+                  Nothing
+
+-- | Like 'catch', but the handler is required to be context-unaware.
+-- Is also able to catch exceptions with context, in which case the
+-- context will be forgotten before the exception will be provided to
+-- the handler.
+catchWithoutContext
+  :: forall a e m
+   . (MonadCatch m, Exception e) -- , MonadErrorContext m)
+  => m a
+  -> (e -> m a)
+  -> m a
+catchWithoutContext m handler = catchJust pre m handler
+  where pre :: SomeException -> Maybe e
+        pre someExn =
+          -- First we check if the exception is of the type 'e'
+          -- (without context). If so, provide it to the handler
+          -- directly.
+          case fromException someExn :: Maybe e of
+            Just exn ->
+              Just exn
+            Nothing  ->
+              -- Then we check if the exception is of the type
+              -- 'ErrorWithContext e'. In this case we forget the
+              -- context and provide the exception without context to
+              -- the handler.
+              case fromException someExn :: Maybe (ErrorWithContext SomeException) of
+                Just (ErrorWithContext _ctx someExnWithoutContext) ->
+                  case fromException someExnWithoutContext :: Maybe e of
+                    Just exn ->
+                      Just exn
+                    Nothing ->
+                      Nothing
+                Nothing  ->
+                  Nothing
+
+tryAnyWithContext
+  :: (MonadErrorContext m, MonadCatch m)
+  => m a
+  -> m (Either (ErrorWithContext SomeException) a)
+tryAnyWithContext m =
+  mapLeft (ErrorWithContext (ErrorContext [])) <$> tryAny m
+
+tryWithContext
+  :: (MonadErrorContext m, MonadCatch m, Exception e)
+  => m a
+  -> m (Either (ErrorWithContext e) a)
+tryWithContext m =
+  mapLeft (ErrorWithContext (ErrorContext [])) <$> try m
+
+-- | Implement 'MonadResource' for 'ErrorContextT'.
+instance (MonadCatch m, MonadResource m) => MonadResource (ErrorContextT m) where
+  liftResourceT = lift . liftResourceT
+
+-- | Implement 'MonadReader' for 'ErrorContextT'.
+instance MonadReader r m => MonadReader r (ErrorContextT m) where
+  ask = ErrorContextT (lift ask)
+  local f (ErrorContextT (ReaderT m)) =
+    ErrorContextT (ReaderT (\ errCtx -> local f (m errCtx)))
+
+-- | Encapsulates the error context — essentially a stack of 'Text'
+-- values.
+data ErrorContext = ErrorContext [Text] deriving (Show, Eq)
+
+-- | Push a context layer (a 'Text') onto the provided 'ErrorContext'.
+errorContextPush
+  :: Text
+  -> ErrorContext
+  -> ErrorContext
+errorContextPush layer (ErrorContext layers) =
+  ErrorContext (layer : layers)
+
+errorContextAsString :: ErrorContext -> String
+errorContextAsString (ErrorContext layers) =
+  concat $ map (\ layer -> "  caused by: " <> Text.unpack layer <> "\n") layers
+
+-- | Dump an error with context to stdout.
+errorWithContextDump
+  :: (Show e, MonadIO m)
+  => ErrorWithContext e
+  -> m ()
+errorWithContextDump (ErrorWithContext ctx err) = do
+  liftIO . putStrLn $ "Error: " <> show err
+  liftIO . putStrLn . errorContextAsString $ ctx
+
+-- | Enrich an error value with an error context.
+errorContextualize
+  :: MonadErrorContext m
+  => e
+  -> m (ErrorWithContext e)
+errorContextualize e = do
+  ctx <- errorContextCollect
+  pure $ ErrorWithContext ctx e
+
+-- | Forgets the context from an enriched error.
+errorContextForget
+  :: ErrorWithContext e
+  -> e
+errorContextForget (ErrorWithContext _ctx e) = e
+
+catchJustWithContext
+  :: (MonadCatch m, Exception e)
+  => (e -> Maybe b)
+  -> m a
+  -> (b -> m a)
+  -> m a
+catchJustWithContext f a b =
+  a `catch` \ exn -> maybe (throwM exn) b $ f exn
+
+-- | Context aware version of 'catchAny'.
+catchAnyWithContext
+  :: MonadCatch m
+  => m a
+  -> (ErrorWithContext SomeException -> m a)
+  -> m a
+catchAnyWithContext m handler = catchJust pre m handler
+  where pre :: SomeException -> Maybe (ErrorWithContext SomeException)
+        pre someExn =
+          case fromException someExn :: Maybe (ErrorWithContext SomeException) of
+            Just exn ->
+              Just exn
+            Nothing ->
+              Just (ErrorWithContext (ErrorContext []) someExn)
+
+ensureExceptionContext :: (MonadCatch m, MonadErrorContext m) => m a -> m a
+ensureExceptionContext m =
+  catchAny m $ \ someExn ->
+  case fromException someExn :: Maybe (ErrorWithContext SomeException) of
+    Just exnWithCtx ->
+      throwM exnWithCtx
+    Nothing -> do
+      ctx <- errorContextCollect
+      throwM $ ErrorWithContext ctx someExn
diff --git a/test/Control/Error/Context/Test.hs b/test/Control/Error/Context/Test.hs
new file mode 100644
--- /dev/null
+++ b/test/Control/Error/Context/Test.hs
@@ -0,0 +1,131 @@
+{-# LANGUAGE FlexibleInstances   #-}
+{-# LANGUAGE OverloadedStrings   #-}
+{-# LANGUAGE QuasiQuotes         #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TemplateHaskell     #-}
+
+module Control.Error.Context.Test (tests) where
+
+import           Control.Error.Context
+import           Control.Exception      (Exception (..), throw, throwIO)
+import           Control.Monad
+import           Control.Monad.Catch    (catch, throwM, try)
+import           Control.Monad.IO.Class
+import           Test.Tasty
+import           Test.Tasty.HUnit
+
+tests :: TestTree
+tests =
+  testGroup
+    "Tests"
+    [ testCase "Contextualize IO Exception"
+        testContextualizeIOException
+    , testCase "throwM"
+        testThrow
+    , testCase "catchAnyWithContext"
+        testCatchAnyWithContext
+    -- , testCase "catchJustWithContext"
+    --     testCatchJustWithContext
+    , testCase "Catch context-enriched exception without context"
+        testCatchWithoutContext
+    , testCase "Contextualize error value"
+        testContextualizeErrorValue
+    , testCase "Forgetting error context"
+        testForgetErrorContext
+    , testCase "Dumping error context"
+        testDumpErrorContext
+    , testCase "Throw and catch"
+        testThrowAndCatch
+    , testCase "Catch non-contextualized exception with context"
+        testNonContextualizedCatchWithContext
+    , testCase "ensureExceptionContext"
+        testEnsureExceptionContext
+    ]
+
+data TestException = TestException deriving (Show, Eq)
+
+instance Exception TestException
+
+testContextualizeIOException :: Assertion
+testContextualizeIOException = do
+  Left (ErrorWithContext (ErrorContext ctx) TestException) <- try . runErrorContextT $
+    withErrorContext "A" $
+    withErrorContext "B" $
+    liftIO failingIOException
+  ["B", "A"] @=? ctx
+
+  where failingIOException :: IO ()
+        failingIOException =
+          throwIO TestException
+
+testCatchWithoutContext :: Assertion
+testCatchWithoutContext = do
+  TestException <- runErrorContextT $
+    withErrorContext "A" $
+    withErrorContext "B" $
+    catchWithoutContext (throwM TestException) $ \ (exn :: TestException) -> do
+      pure exn
+  pure ()
+
+testContextualizeErrorValue :: Assertion
+testContextualizeErrorValue = do
+  ErrorWithContext (ErrorContext ctx) TestException <- runErrorContextT $
+    withErrorContext "A" $
+    withErrorContext "B" $
+    errorContextualize TestException
+  ["B", "A"] @=? ctx
+
+testForgetErrorContext :: Assertion
+testForgetErrorContext = do
+  errWithCtx @ (ErrorWithContext _ctx TestException) <- runErrorContextT $
+    withErrorContext "A" $
+    withErrorContext "B" $
+    errorContextualize TestException
+  TestException @=? errorContextForget errWithCtx
+
+testDumpErrorContext :: Assertion
+testDumpErrorContext = do
+  errWithCtx @ (ErrorWithContext _ctx _exn) <- runErrorContextT $
+    withErrorContext "A" $
+    withErrorContext "B" $
+    errorContextualize TestException
+  errorWithContextDump errWithCtx
+
+testThrowAndCatch :: Assertion
+testThrowAndCatch = do
+  void . runErrorContextT $
+    catch (throwM TestException) $ \ TestException -> pure ()
+
+testThrow :: Assertion
+testThrow = do
+  catch (runErrorContextT (throwM TestException)) $ \ someExn -> do
+    let Just (ErrorWithContext _ctx someInnerExn) = fromException someExn
+    liftIO $ Just TestException @=? fromException someInnerExn
+
+testCatchAnyWithContext :: Assertion
+testCatchAnyWithContext = do
+  catchAnyWithContext (runErrorContextT (throwM TestException)) $
+    \ (ErrorWithContext _ctx someExn) -> do
+      Just TestException @=? fromException someExn
+
+testNonContextualizedCatchWithContext :: Assertion
+testNonContextualizedCatchWithContext = do
+  ErrorWithContext (ErrorContext ctx) TestException <- runErrorContextT $
+    withErrorContext "A" $
+    withErrorContext "B" $ do
+    catchWithContext throwPureException $ \ (exn :: ErrorWithContext TestException) -> do
+      pure exn
+  [] @=? ctx
+
+  where throwPureException = throw TestException
+
+testEnsureExceptionContext :: Assertion
+testEnsureExceptionContext = do
+  Left someExn <- try . runErrorContextT $
+    withErrorContext "A" $
+    withErrorContext "B" $ do
+    ensureExceptionContext $ do
+      throw TestException
+  let Just (ErrorWithContext ctx someExnWithoutCtx) = fromException someExn
+  Just TestException @=? fromException someExnWithoutCtx
+  ErrorContext ["B", "A"] @=? ctx
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,10 @@
+import qualified Control.Error.Context.Test
+import           Test.Tasty
+
+
+main :: IO ()
+main = do
+  defaultMain $
+    testGroup "Test Suite"
+    [ Control.Error.Context.Test.tests
+    ]
