diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -32,7 +32,7 @@
  1. how to [install this library][install]?
  1. how to do [mocking][mocking]?
  1. how to [specialize some values in some contexts][specialize]?
- 1. how to [control effects][memoization] occurring when creating a component (like a connection pool)?
+ 1. how to [control effects][caching] occurring when creating a component (like a connection pool)?
  1. how to [allocate resources][resources] which must be finalized?
  1. how to [extract a dot graph from the registry][dot] in an application?
  1. how to [interact with a library using monad transformers](https://github.com/etorreborre/registry/blob/master/test/Test/Data/Registry/MonadRandomSpec.hs)?
@@ -53,7 +53,7 @@
 [mocking]: http://github.com/etorreborre/registry/blob/main/doc/applications.md#integration
 [install]: http://github.com/etorreborre/registry/blob/main/doc/install.md
 [specialize]: http://github.com/etorreborre/registry/blob/main/doc/applications.md#context-dependent-configurations
-[memoization]: http://github.com/etorreborre/registry/blob/main/doc/applications.md#memoization
+[caching]: http://github.com/etorreborre/registry/blob/main/doc/applications.md#caching
 [resources]: http://github.com/etorreborre/registry/blob/main/doc/applications.md#resources
 [dot]: http://github.com/etorreborre/registry/blob/main/doc/dot.md
 [boilerplate]: http://github.com/etorreborre/registry/blob/main/doc/boilerplate.md
diff --git a/registry.cabal b/registry.cabal
--- a/registry.cabal
+++ b/registry.cabal
@@ -4,10 +4,10 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: f5f64917e56100dee68b1a07abc7a53b5e323440dfeb36623410f015fb2129c5
+-- hash: eba4b73d85a7c3fbee86501abdf3870e4af595503b7879280fad3c2baecbb4c1
 
 name:           registry
-version:        0.5.0.0
+version:        0.6.0.0
 synopsis:       data structure for assembling components
 description:    This library provides a "Registry" which is a data structure containing a list of functions and values representing dependencies in a directed acyclic graph. A `make` function can then be used to create a value of a specific type out of the registry.
                 You can start with the [README](https://github.com/etorreborre/registry/blob/master/README.md) for a full description of the library.
@@ -40,7 +40,7 @@
       Data.Registry.Lift
       Data.Registry.Make
       Data.Registry.Registry
-      Data.Registry.RIO
+      Data.Registry.Rio
       Data.Registry.Solver
       Data.Registry.State
       Data.Registry.Statistics
@@ -77,6 +77,7 @@
     , template-haskell >=2.13 && <3.0
     , text >=1.1 && <2
     , transformers-base ==0.4.*
+    , unliftio >=0.2 && <1
   default-language: GHC2021
 
 test-suite spec
@@ -86,7 +87,6 @@
       AutoDiscoveredSpecs
       Test.Data.Registry.DotSpec
       Test.Data.Registry.GenSpec
-      Test.Data.Registry.Internal.CacheSpec
       Test.Data.Registry.Internal.DynamicSpec
       Test.Data.Registry.Internal.Gens
       Test.Data.Registry.Internal.GensRegistry
@@ -95,7 +95,7 @@
       Test.Data.Registry.Internal.RegistrySpec
       Test.Data.Registry.Internal.TypesSpec
       Test.Data.Registry.Make.MakeSpec
-      Test.Data.Registry.Make.MemoizeSpec
+      Test.Data.Registry.Make.RioSpec
       Test.Data.Registry.Make.SpecializationFunctionsSpec
       Test.Data.Registry.Make.SpecializationSpec
       Test.Data.Registry.Make.TweakingSpec
@@ -159,4 +159,5 @@
     , text <2
     , transformers-base ==0.4.*
     , universum <2
+    , unliftio >=0.2 && <1
   default-language: GHC2021
diff --git a/src/Data/Registry.hs b/src/Data/Registry.hs
--- a/src/Data/Registry.hs
+++ b/src/Data/Registry.hs
@@ -11,7 +11,7 @@
 import Data.Registry.Dot as M -- Produce a graph out of a registry
 import Data.Registry.Lift as M -- Lift functions into a monadic context
 import Data.Registry.Make as M -- Various "make" functions to create components from a registry
-import Data.Registry.RIO as M -- ResourceT monad for effectful instantiation
+import Data.Registry.Rio as M -- ResourceT + cache monad for effectful instantiation and singletons
 import Data.Registry.Registry as M -- The Registry data structure
 import Data.Registry.Solver as M -- Type-level constraints to check if we can make a component from a registry
 import Data.Registry.Statistics as M -- Provide statistics about the execution of a registry
diff --git a/src/Data/Registry/Internal/Cache.hs b/src/Data/Registry/Internal/Cache.hs
--- a/src/Data/Registry/Internal/Cache.hs
+++ b/src/Data/Registry/Internal/Cache.hs
@@ -1,38 +1,108 @@
--- |
---
--- Cache for individual IO values when we wish to memoize actions
--- for database connection pools for example
---
--- This is inspired by https://hackage.haskell.org/package/io-memoize
+{-# LANGUAGE AllowAmbiguousTypes #-}
+
+{- Cache for Rio values, backed by a MVar -}
 module Data.Registry.Internal.Cache where
 
-import Data.Map.Strict
-import Data.Registry.Internal.Types (SpecializationPath)
-import Protolude as P
+import Data.Dynamic
+import Data.Map as M hiding (singleton)
+import Data.Map qualified as M
+import Data.Registry.Internal.Reflection (showSingleType)
+import Protolude
+import Type.Reflection (someTypeRep)
 
--- | A thread-safe write-once cache. If you need more functionality,
--- (e.g. multiple write, cache clearing) use an 'MVar' instead.
-newtype Cache a = Cache (MVar (Map Key a))
-  deriving (Eq, Typeable)
+-- * EXPORTED FUNCTIONS
 
--- | We need to cache different values to account for the fact
---   that different values might be specialized for the same type
-type Key = Maybe [SpecializationPath]
+-- | Cache an effectful value with a given text key
+--   so that the value is not recreated for the same key
+cacheAt :: forall a m. (Typeable a, MonadIO m, MonadReader Cache m) => Text -> m a -> m a
+cacheAt = cacheAtKey . Custom
 
--- | Fetch the value stored in the cache,
--- or call the supplied fallback and store the result,
--- if the cache is empty.
-fetch :: forall a m. (MonadIO m, Typeable a) => Cache a -> Key -> m a -> m a
-fetch (Cache var) key action = do
-  m <- liftIO $ P.readMVar var
-  case lookup key m of
+-- | Cache an effectful value by using its type as the cache key
+singleton :: forall a m. (Typeable a, MonadIO m, MonadReader Cache m) => m a -> m a
+singleton = cacheAtKey Singleton
+
+-- * IMPLEMENTATION
+
+-- | A cache for created values, with a map from
+--   the textual representation of a type to various cached values
+newtype Cache = Cache (MVar (Map Text Cached))
+  deriving (Eq)
+
+-- | Cache for a value of a single type
+--   There is at most one singleton and possibly some custom values, indexed by a specific key
+data Cached = Cached
+  { singletonCached :: Maybe Dynamic,
+    customCached :: Map Text Dynamic
+  }
+  deriving (Show)
+
+-- | An empty cached value (with no cached instances yet)
+emptyCached :: Cached
+emptyCached = Cached Nothing mempty
+
+-- | Create an empty cache
+newCache :: MonadIO m => m Cache
+newCache = liftIO $ Cache <$> newMVar mempty
+
+-- | Get the current cache
+askCache :: MonadReader Cache m => m Cache
+askCache = ask
+
+-- | Type of keys used to cache values
+--   A value can either be cached with a specific key, or it is a singleton
+data Key
+  = Custom Text
+  | Singleton
+  deriving (Eq, Show, Ord)
+
+-- | Make sure that an effectful value is cached after the first evaluation for a specific key
+cacheAtKey :: forall a m. (Typeable a, MonadIO m, MonadReader Cache m) => Key -> m a -> m a
+cacheAtKey key action = do
+  m <- getCached @a key
+  case m of
     Just a ->
       pure a
     Nothing -> do
-      val <- action
-      liftIO $ modifyMVar_ var (pure . insert key val)
-      pure val
+      a <- action
+      setCached key a
+      pure a
 
--- | Create an empty cache.
-newCache :: IO (Cache a)
-newCache = Cache <$> P.newMVar mempty
+-- | Get a cached value from the cache
+--   This is a IO operation since we access the cache MVar
+getCached :: (Typeable a, MonadIO m, MonadReader Cache m) => Key -> m (Maybe a)
+getCached key = askCache >>= getCachedValue key
+
+-- | Cache a value at a given key in the cache
+--   This is a IO operation since we access the cache MVar
+setCached :: forall a m. (Typeable a, MonadIO m, MonadReader Cache m) => Key -> a -> m ()
+setCached key a =
+  askCache >>= cacheValue
+  where
+    -- \| Cache a value as a Dynamic value for a given key
+    cacheValue :: Cache -> m ()
+    cacheValue (Cache ms) = liftIO $
+      modifyMVar_ ms $
+        \m -> pure (M.alter (cacheDynValue key (toDyn a)) (makeTypeText @a) m)
+
+-- | Retrieve a cached value given its key
+getCachedValue :: forall a m. (Typeable a, MonadIO m) => Key -> Cache -> m (Maybe a)
+getCachedValue key (Cache ms) = liftIO $ do
+  m <- readMVar ms
+  let c = lookup (makeTypeText @a) m
+  pure $ c >>= getDynValue key >>= fromDynamic @a
+
+-- | Insert a (dynamic) value in the Cached data structure for a specific type of value
+cacheDynValue :: Key -> Dynamic -> Maybe Cached -> Maybe Cached
+cacheDynValue Singleton dynamic Nothing = Just $ emptyCached {singletonCached = Just dynamic}
+cacheDynValue Singleton dynamic (Just cached) = Just $ cached {singletonCached = singletonCached cached <|> Just dynamic}
+cacheDynValue (Custom key) dynamic Nothing = Just $ emptyCached {customCached = M.singleton key dynamic}
+cacheDynValue (Custom key) dynamic (Just cached) = Just $ cached {customCached = M.insert key dynamic $ customCached cached}
+
+-- | Return the dynamic value cached at a given key
+getDynValue :: Key -> Cached -> Maybe Dynamic
+getDynValue Singleton (Cached s _) = s
+getDynValue (Custom k) (Cached _ m) = M.lookup k m
+
+-- | Return a textual description of a Haskell type
+makeTypeText :: forall a. (Typeable a) => Text
+makeTypeText = showSingleType $ someTypeRep (Proxy :: Proxy a)
diff --git a/src/Data/Registry/Internal/Dynamic.hs b/src/Data/Registry/Internal/Dynamic.hs
--- a/src/Data/Registry/Internal/Dynamic.hs
+++ b/src/Data/Registry/Internal/Dynamic.hs
@@ -29,7 +29,7 @@
   do
     created <- applyFunctionDyn (funDyn function) (valueDyn <$> values)
     let description = ValueDescription (_outputType . funDescription $ function) Nothing
-    let dependencies = foldMap dependenciesOn values
+    let dependencies = foldMap dependenciesOf values
 
     pure $ makeCreatedValue created description dependencies
 
diff --git a/src/Data/Registry/Internal/Registry.hs b/src/Data/Registry/Internal/Registry.hs
--- a/src/Data/Registry/Internal/Registry.hs
+++ b/src/Data/Registry/Internal/Registry.hs
@@ -86,5 +86,5 @@
     modifyValue :: Value -> [(SomeTypeRep, ModifierFunction)] -> Stack Value
     modifyValue v [] = pure v
     modifyValue v ((_, f) : rest) = do
-      applied <- lift $ applyModification (f (specializationPaths v)) v
+      applied <- lift $ applyModification (f (specializedContexts v)) v
       modifyValue applied rest
diff --git a/src/Data/Registry/Internal/Types.hs b/src/Data/Registry/Internal/Types.hs
--- a/src/Data/Registry/Internal/Types.hs
+++ b/src/Data/Registry/Internal/Types.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
 
 -- |
@@ -229,7 +230,6 @@
 untypedDyn (UntypedFunction f) = funDyn f
 untypedDyn (UntypedValue v) = valueDyn v
 
-
 -- | This is a list of entries in the registry available for constructing values
 --   They are sorted by output type and if there are several available functions or values
 --   for a given type the first one in the list has the highest priority
@@ -338,8 +338,8 @@
 dependenciesTypes (Dependencies ds) = DependenciesTypes (valueDynTypeRep <$> ds)
 
 -- | The dependencies of a value + the value itself
-dependenciesOn :: Value -> Dependencies
-dependenciesOn value = Dependencies $ value : (unDependencies . valueDependencies $ value)
+dependenciesOf :: Value -> Dependencies
+dependenciesOf value = Dependencies $ value : (unDependencies . valueDependencies $ value)
 
 -- | Specification of values which become available for
 --   construction when a corresponding type comes in context
@@ -350,8 +350,8 @@
 
 -- | A specialization is defined by
 --   a path of types, from top to bottom in the
---    value graph and target value, which is the
---   value to use when we need a value on that type
+--    value graph and a target value, which is the
+--   value to use when we need a value of that type
 --   on that path.
 --   For example:
 --      specializationPath = [App, PaymentEngine, TransactionRepository]
@@ -370,13 +370,20 @@
 --   See the comments on 'Specialization'
 type SpecializationPath = NonEmpty SomeTypeRep
 
--- | Return the various specialization paths which have possibly led to the
---   creation of that value
-specializationPaths :: Value -> Maybe [SpecializationPath]
-specializationPaths v =
-  case mapMaybe valueSpecialization (unDependencies $ dependenciesOn v) of
-    [] -> Nothing
-    ss -> Just (_specializationPath <$> ss)
+-- | For each dependency of the value
+--   Return the specialization context of the value if
+--     - that dependency value is specialized
+--     - the current value is part of the context stack and part of a context path
+specializedContexts :: Value -> [SpecializationContext]
+specializedContexts v = do
+  let contexts = mapMaybe valueSpecializationContext (unDependencies $ dependenciesOf v)
+  P.filter isCurrentValueSpecialized contexts
+  where
+    isCurrentValueSpecialized (SpecializationContext (Context stack) (Specialization path _)) = do
+      let stackTypes = fst <$> stack
+      let topSpecializedType = NonEmpty.head path
+      let specializedTypes = P.takeWhile (/= topSpecializedType) stackTypes
+      valueDynTypeRep v `elem` (topSpecializedType : specializedTypes)
 
 -- | First type of a specialization
 specializationStart :: Specialization -> SomeTypeRep
@@ -475,7 +482,7 @@
 --   then the specialization path is also passed to the function
 --   This is used for memoizing actions using a cache so that we
 --   cache each specialized value separately.
-type ModifierFunction = Maybe [SpecializationPath] -> Function
+type ModifierFunction = [SpecializationContext] -> Function
 
 -- | Create a 'ModifierFunction' value from a Haskell function
 --   The application of that function does not depend on the fact
@@ -483,6 +490,13 @@
 createConstModifierFunction :: (Typeable f) => f -> ModifierFunction
 createConstModifierFunction f = const (createFunction f)
 
+-- | Create a 'ModifierFunction' value from a Haskell function
+--   that will only act on unspecialized values
+createUnspecializedModifierFunction :: forall a f. (Typeable f, Typeable a, Typeable (a -> a)) => f -> ModifierFunction
+createUnspecializedModifierFunction f = \case
+    [] -> createFunction f
+    _ -> createFunction @(a -> a) identity
+
 instance Show Modifiers where
   show = toS . describeModifiers
 
@@ -493,7 +507,6 @@
   if P.null ms
     then ""
     else "modifiers for types\n" <> unlines (P.show . fst <$> ms)
-
 
 -- * VALUES
 
diff --git a/src/Data/Registry/RIO.hs b/src/Data/Registry/RIO.hs
deleted file mode 100644
--- a/src/Data/Registry/RIO.hs
+++ /dev/null
@@ -1,34 +0,0 @@
--- | Utilities for working with ResourceT IO
-module Data.Registry.RIO where
-
-import Control.Monad.Trans.Resource
-import Data.Registry.Make
-import Data.Registry.Registry
-import Data.Registry.Solver
-import Protolude
-
--- | Type alias for ResourceT IO
-type RIO = ResourceT IO
-
--- | This function must be used to run services involving a top component
---   It creates an application of type a and which can return a result of type b.
---
---   We also make sure that all effects are memoized by calling `memoizeAll` on the Registry here!
-withRegistry ::
-  forall a b ins out m.
-  (Typeable a, Contains (RIO a) out, Solvable ins out, MonadIO m, MemoizedActions out) =>
-  Registry ins out ->
-  (a -> IO b) ->
-  m b
-withRegistry registry f = liftIO $
-  runResourceT (runRegistryT @a registry >>= liftIO . f)
-
--- | This can be used if you want to insert the component creation inside
---   another action managed with ResourceT. Or if you want to call runResourceT yourself later
-runRegistryT ::
-  forall a ins out .
-  (Typeable a, Contains (RIO a) out, Solvable ins out, MemoizedActions out) =>
-  Registry ins out ->
-  ResourceT IO a
-runRegistryT registry =
-  liftIO (memoizeAll @RIO registry) >>= make @(RIO a)
diff --git a/src/Data/Registry/Registry.hs b/src/Data/Registry/Registry.hs
--- a/src/Data/Registry/Registry.hs
+++ b/src/Data/Registry/Registry.hs
@@ -36,7 +36,6 @@
 module Data.Registry.Registry where
 
 import Data.Dynamic
-import Data.Registry.Internal.Cache
 import Data.Registry.Internal.Types
 import Data.Registry.Lift
 import Data.Registry.Solver
@@ -267,57 +266,12 @@
     specializations
     (Modifiers ((someTypeRep (Proxy :: Proxy a), createConstModifierFunction f) : mf))
 
--- * Memoization
-
--- | Instantiating components can trigger side-effects
---   The way the resolution algorithm works a component of type `m a` will be
---   re-executed *every time* it is needed as a given dependency
---   This section adds support for memoizing those actions
-
--- | Return memoized values for a monadic type
---   Note that the returned Registry is in 'IO' because we are caching a value
---   and this is a side-effect!
-memoize :: forall m a ins out. (MonadIO m, Typeable a, Typeable (m a)) => Registry ins out -> IO (Registry ins out)
-memoize (Registry entries specializations (Modifiers mf)) = do
-  cache <- newCache @a
-  let modifiers = Modifiers ((someTypeRep (Proxy :: Proxy (m a)), createFunction . fetch @a @m cache) : mf)
-  pure $ Registry entries specializations modifiers
-
--- | Memoize *all* the output actions of a Registry when they are creating effectful components
---   This relies on a helper data structure `MemoizeRegistry` tracking the types already
---   memoized and a typeclass MemoizedActions going through the list of out types to process them
---   one by one. Note that a type of the form a will not be memoized (only `m a`)
-memoizeAll :: forall m ins out. (MonadIO m, MemoizedActions out) => Registry ins out -> IO (Registry ins out)
-memoizeAll r =
-  _unMemoizeRegistry
-    <$> memoizeActions (startMemoizeRegistry r)
-
--- | Registry where all output values are memoized
-newtype MemoizeRegistry (todo :: [Type]) (ins :: [Type]) (out :: [Type]) = MemoizeRegistry {_unMemoizeRegistry :: Registry ins out}
-
--- | Prepare a Registry for memoization
-startMemoizeRegistry :: Registry ins out -> MemoizeRegistry out ins out
-startMemoizeRegistry = MemoizeRegistry
-
--- | Prepare a Registry for memoization for a specific list of types
-makeMemoizeRegistry :: forall todo ins out. Registry ins out -> MemoizeRegistry todo ins out
-makeMemoizeRegistry = MemoizeRegistry @todo
-
--- | This typeclass take an existing registry and memoize values created for the ls types
-class MemoizedActions ls where
-  memoizeActions :: MemoizeRegistry ls ins out -> IO (MemoizeRegistry '[] ins out)
-
--- | If the list of types is empty there is nothing to memoize
-instance MemoizedActions '[] where
-  memoizeActions = pure
-
--- | If the type represents an effectful value, memoize it and recurse with the rest
-instance {-# OVERLAPPING #-} (MonadIO m, Typeable a, Typeable (m a), MemoizedActions rest) => MemoizedActions (m a : rest) where
-  memoizeActions (MemoizeRegistry r) = do
-    r' <- memoize @m @a r
-    memoizeActions (makeMemoizeRegistry @rest r')
-
--- | If the type represents a pure value, memoize the rest
-instance (MemoizedActions rest) => MemoizedActions (a : rest) where
-  memoizeActions (MemoizeRegistry r) =
-    memoizeActions (makeMemoizeRegistry @rest r)
+-- | Once a value has been computed allow to modify it before storing it
+--   This keeps the same registry type
+--   This only tweaks unspecialized values!
+tweakUnspecialized :: forall a ins out. (Typeable a) => (a -> a) -> Registry ins out -> Registry ins out
+tweakUnspecialized f (Registry entries specializations (Modifiers mf)) =
+  Registry
+    entries
+    specializations
+    (Modifiers ((someTypeRep (Proxy :: Proxy a), createUnspecializedModifierFunction @a f) : mf))
diff --git a/src/Data/Registry/Rio.hs b/src/Data/Registry/Rio.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Registry/Rio.hs
@@ -0,0 +1,117 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE DataKinds #-}
+
+-- | Utilities for working with resources
+module Data.Registry.Rio
+  ( module Data.Registry.Rio,
+    singleton,
+    cacheAt
+  )
+where
+
+import Control.Monad.Morph
+import Control.Monad.Trans.Resource
+import Data.Dynamic
+import Data.Registry.Internal.Cache
+import Data.Registry.Make (make)
+import Data.Registry.Registry
+import Protolude
+
+-- | This data type provides some support for creating effectful components with resources
+--   You can use the regular MonadResource functions like allocate to make sure that resources are cleaned up
+--   You can also use the 'cacheAt' function
+newtype Rio a = Rio {rioRun :: ReaderT Cache (ResourceT IO) a}
+  deriving (Functor, Applicative, Monad, MonadReader Cache, MonadIO, MonadResource, MonadUnliftIO)
+
+-- | Run a Rio action by providing an empty cache and allocating / destroying resources
+runRio :: MonadIO m => Rio a -> m a
+runRio = liftIO . runResourceT . runCache
+
+-- | Run a Rio action by providing an empty cache and allocating / destroying resources
+execRio :: MonadIO m => Rio a -> m (a, Cache)
+execRio = liftIO . runResourceT . execCache
+
+-- | Use the value created by a Rio action so that resources are properly allocated and cached
+withRio :: MonadIO m => Rio a -> (a -> IO b) -> m b
+withRio action f = liftIO . runResourceT $ do
+  a <- runCache action
+  liftIO $ f a
+
+-- | Use the value created by a Rio action so that resources are properly allocated and cached
+--   inside a monad transformer
+withRioM :: (MonadResource (m (ResourceT IO)), MFunctor m) => Rio a -> (a -> m IO b) -> m IO b
+withRioM action f = hoist runResourceT $ do
+  a <- liftResourceT (runCache action)
+  hoist lift (f a)
+
+-- | Run a Rio action by providing an empty cache
+runCache :: Rio a -> ResourceT IO a
+runCache (Rio action) = do
+  cache <- liftIO newCache
+  runReaderT action cache
+
+-- | Run a Rio action by providing an empty cache, and return the final cache
+--   for inspection
+execCache :: Rio a -> ResourceT IO (a, Cache)
+execCache (Rio action) = do
+  cache <- liftIO newCache
+  (,cache) <$> runReaderT action cache
+
+-- | Lift a resourceful value into Rio
+liftRio :: ResourceT IO a -> Rio a
+liftRio = Rio . lift
+
+-- | This function must be used to run services involving resources
+--   The value a is created using the registry, used with the function 'f'
+--   and all resources are freed at the end
+withRegistry :: forall a b ins out m. (Typeable a, MonadIO m, MakeSingletons out) => Registry ins out -> (a -> IO b) -> m b
+withRegistry registry f =
+  liftIO $ runResourceT (runRegistryT @a registry >>= liftIO . f)
+
+-- | This function works like 'withRegistry' for a higher-order monad, typically `PropertyT IO` when
+--   writing property tests with Hedgehog
+withRegistryM ::
+  forall a b ins out m.
+  (Typeable a, MonadResource (m (ResourceT IO)), MFunctor m, MakeSingletons out) =>
+  Registry ins out ->
+  (a -> m IO b) ->
+  m IO b
+withRegistryM = withRioM . make @(Rio a) . singletons
+
+-- | Create a function of type a with a given registry
+--   Return a ResourceT value to control resource allocation
+runRegistryT :: forall a ins out. (Typeable a, MakeSingletons out) => Registry ins out -> ResourceT IO a
+runRegistryT = runCache . make @(Rio a) . singletons
+
+-- | Make singletons for all the output types of a registry
+--   but only if they not specialized values
+singletons :: forall ins out. (MakeSingletons out) => Registry ins out -> Registry ins out
+singletons r = _singletonsRegistry $ makeSingletons (startSingletonsRegistry r)
+
+-- | Registry where all Rio values are singletons
+newtype SingletonsRegistry (todo :: [Type]) (ins :: [Type]) (out :: [Type]) = SingletonsRegistry {_singletonsRegistry :: Registry ins out}
+
+-- | Prepare a Registry for making singletons
+startSingletonsRegistry :: Registry ins out -> SingletonsRegistry out ins out
+startSingletonsRegistry = SingletonsRegistry
+
+-- | Prepare a Registry for making singletons on a specific list of types
+makeSingletonsRegistry :: forall todo ins out. Registry ins out -> SingletonsRegistry todo ins out
+makeSingletonsRegistry = SingletonsRegistry @todo
+
+-- | This typeclass take an existing registry and makes a singleton for each Rio output type
+class MakeSingletons ls where
+  makeSingletons :: SingletonsRegistry ls ins out -> SingletonsRegistry '[] ins out
+
+-- | If the list of types is empty there is nothing to do
+instance MakeSingletons '[] where
+  makeSingletons = identity
+
+-- | If the type represents an effectful value, make a singleton for it and recurse on the rest
+instance {-# OVERLAPPING #-} (Typeable a, MakeSingletons rest) => MakeSingletons (Rio a : rest) where
+  makeSingletons (SingletonsRegistry r) =
+    makeSingletons $ SingletonsRegistry @rest (tweakUnspecialized @(Rio a) singleton r)
+
+-- | If the type represents a pure value, make singletons for the rest
+instance (MakeSingletons rest) => MakeSingletons (a : rest) where
+  makeSingletons (SingletonsRegistry r) = makeSingletons (makeSingletonsRegistry @rest r)
diff --git a/test/Test/Data/Registry/Internal/CacheSpec.hs b/test/Test/Data/Registry/Internal/CacheSpec.hs
deleted file mode 100644
--- a/test/Test/Data/Registry/Internal/CacheSpec.hs
+++ /dev/null
@@ -1,22 +0,0 @@
-{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
-
-module Test.Data.Registry.Internal.CacheSpec where
-
-import Control.Concurrent.Async
-import Data.Registry.Internal.Cache
-import Protolude as P
-import Test.Tasty.Extensions
-
-test_cache = test "caching an IO action must always return the same value" $ do
-  cached <- liftIO $ do
-    -- create an action which will increment an Int everytime it is called
-    ref <- newMVar (0 :: Int)
-    let action = modifyMVar_ ref (pure . (+ 1)) >> readMVar ref
-    cache <- newCache
-
-    -- when the action is cached it will always return the same value
-    let cachedAction = fetch cache Nothing action
-    void $ replicateConcurrently_ 100 cachedAction -- with concurrent accesses
-    cachedAction
-
-  cached === 1
diff --git a/test/Test/Data/Registry/Make/MemoizeSpec.hs b/test/Test/Data/Registry/Make/MemoizeSpec.hs
deleted file mode 100644
--- a/test/Test/Data/Registry/Make/MemoizeSpec.hs
+++ /dev/null
@@ -1,159 +0,0 @@
-{-# LANGUAGE DataKinds #-}
-{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
-
-module Test.Data.Registry.Make.MemoizeSpec where
-
-import Data.IORef
-import Data.Registry
-import Protolude hiding (C1, D1)
-import System.IO.Memoize
-import Test.Tasty.Extensions
-
-test_memoize = test "effectful values can be memoized with System.IO.Memoize" $ do
-  (c1, c2) <- liftIO $ do
-    -- create a counter for the number of instantiations
-    counter <- newIORef 0
-
-    newSingOnce <- once (newSing counter)
-    let r =
-          funTo @IO newC1
-            <: funTo @IO newC2
-            <: funTo @IO newSingOnce
-
-    c1 <- make @(IO C1) r
-    c2 <- make @(IO C2) r
-    pure (c1, c2)
-
-  c1 === C1 (Sing 1)
-  c2 === C2 (Sing 1)
-
-test_memoize_proper = test "effectful values can memoized" $ do
-  (c1, c2) <- liftIO $ do
-    -- create a counter for the number of instantiations
-    counter <- newIORef 0
-
-    let r =
-          funTo @IO newC1
-            <: funTo @IO newC2
-            <: funTo @IO (newSing counter)
-
-    r' <- memoize @IO @Sing r
-    c1 <- make @(IO C1) r'
-    c2 <- make @(IO C2) r'
-    pure (c1, c2)
-
-  c1 === C1 (Sing 1)
-  c2 === C2 (Sing 1)
-
-test_automatic_memoize_all_for_with_registry = test "withRegistry automatically uses memoizeAll with RIO" $ do
-  messagesRef <- liftIO $ newIORef []
-  let registry =
-        funTo @RIO App
-          <: funTo @RIO newA
-          <: funTo @RIO newB
-          <: fun (newC messagesRef)
-
-  --  just instantiate the app for its effects
-  withRegistry @App registry $ \_ -> pure ()
-
-  ms <- liftIO $ readIORef messagesRef
-
-  annotate "if memoize works properly, then only one instantiation is invoked"
-  ms === ["x"]
-
-test_memoize_with_specialization = test "all the values on a specialization path are memoized independently" $ do
-  D3 d1 d2 <- liftIO $ do
-    -- create a counter for the number of instantiations
-    counter <- newIORef 0
-
-    let r =
-          specialize @(IO D2) @(IO Specialized) (valTo @IO Specialized2) $
-            funTo @IO D3
-              <: funTo @IO D1
-              <: funTo @IO D2
-              <: funTo @IO (newCounter counter)
-              <: valTo @IO Specialized1
-
-    r' <- memoizeAll @IO r
-    make @(IO D3) r'
-
-  d1 === D1 (Counter 1 Specialized1)
-  d2 === D2 (Counter 2 Specialized2)
-
-test_memoize_with_specialized_functions = test "all the values created from functions on a specialization path are memoized independently" $ do
-  D3 d1 d2 <- liftIO $ do
-    -- create a counter for the number of instantiations
-    counter <- newIORef 0
-
-    let r =
-          specialize @(IO D2) (funTo @IO (\(_ :: Text) -> Specialized2)) $
-            funTo @IO D3
-              <: funTo @IO D1
-              <: funTo @IO D2
-              <: funTo @IO (newCounter counter)
-              <: valTo @IO Specialized1
-              <: valTo @IO ("text" :: Text)
-
-    r' <- memoizeAll @IO r
-    make @(IO D3) r'
-
-  d1 === D1 (Counter 1 Specialized1)
-  d2 === D2 (Counter 2 Specialized2)
-
--- * HELPERS
-
-newtype C1 = C1 Sing deriving (Eq, Show)
-
-newC1 :: Sing -> IO C1
-newC1 = pure . C1
-
-newtype C2 = C2 Sing deriving (Eq, Show)
-
-newC2 :: Sing -> IO C2
-newC2 = pure . C2
-
-newtype Sing = Sing Int deriving (Eq, Show)
-
-newSing :: IORef Int -> IO Sing
-newSing counter = do
-  _ <- modifyIORef counter (+ 1)
-  i <- readIORef counter
-  pure (Sing i)
-
-data C3 = C3 C1 C2 deriving (Eq, Show)
-
-newtype A = A {doItA :: IO ()}
-
-newtype B = B {doItB :: IO ()}
-
-newtype C = C {doItC :: IO ()}
-
-newA :: C -> A
-newA c = A {doItA = doItC c}
-
-newB :: C -> B
-newB c = B {doItB = doItC c}
-
-newC :: IORef [Text] -> RIO C
-newC messagesRef = do
-  let c = C {doItC = pure ()}
-  liftIO $ modifyIORef messagesRef ("x" :)
-  pure c
-
-data App = App {a :: A, b :: B}
-
-newtype D1 = D1 Counter deriving (Eq, Show)
-
-newtype D2 = D2 Counter deriving (Eq, Show)
-
-data Counter = Counter Int Specialized deriving (Eq, Show)
-
-data Specialized = Specialized1 | Specialized2 deriving (Eq, Show)
-
-newCounter :: IORef Int -> Specialized -> IO Counter
-newCounter counter specialized = do
-  _ <- modifyIORef counter (+ 1)
-  i <- readIORef counter
-  pure (Counter i specialized)
-
-data D3 = D3 D1 D2 deriving (Eq, Show)
diff --git a/test/Test/Data/Registry/Make/RioSpec.hs b/test/Test/Data/Registry/Make/RioSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Data/Registry/Make/RioSpec.hs
@@ -0,0 +1,126 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE OverloadedLists #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
+
+module Test.Data.Registry.Make.RioSpec where
+
+import Control.Monad.Trans.Resource
+import Data.IORef
+import Data.Map as M hiding (singleton)
+import Data.Registry as Rio
+import Protolude hiding (C1, D1)
+import Test.Tasty.Extensions
+
+test_fmap = test "a cached value can still be mapped over" $ do
+  -- count the number of creation for the counter
+  creations <- liftIO (newIORef (0 :: Int))
+
+  -- create a singleton counter starting at 1
+  let counter = Rio.singleton $ liftIO $ do
+        c <- newIORef (1 :: Int)
+        modifyIORef creations (+ 1)
+        pure c
+
+  -- make the counter once, then make the counter start at 2
+  c1 <- liftIO (newIORef (2 :: Int))
+  ref <- runRio (counter >> (counter $> c1))
+
+  -- the counter starts at 2
+  incremented <- liftIO . readIORef $ ref
+  incremented === 2
+
+  -- the counter has been created only once
+  created <- liftIO . readIORef $ creations
+  created === 1
+
+test_singletons_with_specialization = test "some effectful components can be cached to become singletons in some contexts" $ do
+  let registry =
+        singletons $
+          specialize @(Rio Storage3) (valTo @Rio (DatabaseConfig "localhost" 9876)) $
+            funTo @Rio newApp
+              <: funTo @Rio newBusinessLogic
+              <: funTo @Rio Storage3
+              <: funTo @Rio Storage2
+              <: funTo @Rio Storage1
+              <: funTo @Rio newConnectionPool
+              <: fun newCounter
+              <: valTo @Rio (DatabaseConfig "localhost" 5432)
+
+  withRegistryM @App registry $ \App {..} -> do
+    let db1 = getDatabaseConfig $ getStorage1ConnectionPool storage1
+    let db2 = getDatabaseConfig $ getStorage2ConnectionPool storage2
+    let db3 = getDatabaseConfig $ getStorage3ConnectionPool storage3
+
+    db1 === db2
+    db2 /== db3
+    db3 === DatabaseConfig "localhost" 9876
+
+    n <- readCounter counter
+    n
+      === [ ("create the App", 1),
+            ("create the BusinessLogic", 1),
+            ("http://localhost:5432", 1),
+            ("http://localhost:9876", 1)
+          ]
+
+-- * HELPERS
+
+data App = App
+  { storage1 :: Storage1,
+    storage2 :: Storage2,
+    storage3 :: Storage3,
+    businessLogic :: BusinessLogic,
+    counter :: Counter
+  }
+
+newApp :: Counter -> Storage1 -> Storage2 -> Storage3 -> BusinessLogic -> Rio App
+newApp counter s1 s2 s3 bl = do
+  incrementCounter counter "create the App"
+  pure (App s1 s2 s3 bl counter)
+
+newtype ConnectionPool = ConnectionPool {getDatabaseConfig :: DatabaseConfig}
+
+newConnectionPool :: Counter -> DatabaseConfig -> Rio ConnectionPool
+newConnectionPool counter config = do
+  let key = databaseConfigUrl config
+
+  let create = do
+        -- count the number of times that a pool is created with some specific parameters
+        incrementCounter counter (databaseConfigUrl config)
+        pure (ConnectionPool config)
+  let destroy = const $ pure ()
+
+  cacheAt key (snd <$> allocate create destroy)
+
+data DatabaseConfig = DatabaseConfig {host :: Text, port :: Int} deriving (Eq, Show)
+
+databaseConfigUrl :: DatabaseConfig -> Text
+databaseConfigUrl (DatabaseConfig h p) = "http://" <> h <> ":" <> show p
+
+newtype Storage1 = Storage1 {getStorage1ConnectionPool :: ConnectionPool}
+
+newtype Storage2 = Storage2 {getStorage2ConnectionPool :: ConnectionPool}
+
+newtype Storage3 = Storage3 {getStorage3ConnectionPool :: ConnectionPool}
+
+newtype Counter = Counter (IORef (Map Text Int))
+
+newtype BusinessLogic = BusinessLogic Storage1
+
+newBusinessLogic :: Counter -> Storage1 -> Storage3 -> Rio BusinessLogic
+newBusinessLogic counter storage1 _storage3 = do
+  incrementCounter counter "create the BusinessLogic"
+  pure $ BusinessLogic storage1
+
+newCounter :: Rio Counter
+newCounter = Counter <$> liftIO (newIORef mempty)
+
+incrementCounter :: MonadIO m => Counter -> Text -> m ()
+incrementCounter (Counter ref) key = liftIO $ do
+  m <- readIORef ref
+  let current = fromMaybe 0 (M.lookup key m)
+  modifyIORef ref $ pure (M.insert key (current + 1) m)
+
+readCounter :: MonadIO m => Counter -> m (Map Text Int)
+readCounter (Counter ref) = liftIO $ readIORef ref
diff --git a/test/Test/Data/Registry/Make/SpecializationSpec.hs b/test/Test/Data/Registry/Make/SpecializationSpec.hs
--- a/test/Test/Data/Registry/Make/SpecializationSpec.hs
+++ b/test/Test/Data/Registry/Make/SpecializationSpec.hs
@@ -111,18 +111,18 @@
   (c1, c2, c3) <- liftIO $
     do
       let r =
-            funTo @RIO newBase2
-              <: funTo @RIO newClient1
-              <: funTo @RIO newClient2
-              <: funTo @RIO newUseConfig
-              <: valTo @RIO (Config 3)
+            funTo @Rio newBase2
+              <: funTo @Rio newClient1
+              <: funTo @Rio newClient2
+              <: funTo @Rio newUseConfig
+              <: valTo @Rio (Config 3)
 
       let r' =
-            specializePath @[RIO Base2, RIO Client1, RIO UseConfig] (valTo @RIO $ Config 1)
-              . specialize @(RIO UseConfig) (valTo @RIO $ Config 2)
+            specializePath @[Rio Base2, Rio Client1, Rio UseConfig] (valTo @Rio $ Config 1)
+              . specialize @(Rio UseConfig) (valTo @Rio $ Config 2)
               $ r
 
-      printBase2 <$> runResourceT (make @(RIO Base2) r')
+      printBase2 <$> runResourceT (runRegistryT @Base2 r')
 
   c1 === Config 1
   c2 === Config 2
@@ -214,46 +214,6 @@
   StatsStore
     { statsStoreConfig = (twitterConfig client, sqlConfig sql, supervisorConfig supervisor)
     }
-
--- | Case 6 (taken from a real case...)
-test_specialization_6 = test "specialized values must not be affected by memoization" $ do
-  someData <- liftIO $ do
-    r <- aRegistryIO
-    make @(IO SomeData) r
-
-  (someData & toOverride & toOverrideConfig) === ("specialized config" :: Text)
-  (someData & toKeepDefault & toKeepDefaultConfig) === ("default config" :: Text)
-
-data SomeData = SomeData
-  { toKeepDefault :: ToKeepDefault,
-    toOverride :: ToOverride,
-    inCommon :: InCommon
-  }
-
-newtype ToOverride = ToOverride {toOverrideConfig :: Text}
-
-newtype ToKeepDefault = ToKeepDefault {toKeepDefaultConfig :: Text}
-
-newtype InCommon = InCommon {config :: SomeConfig}
-
-newtype SomeConfig = SomeConfig Text deriving (Eq, Show)
-
-newToKeepDefault :: InCommon -> ToKeepDefault
-newToKeepDefault (InCommon (SomeConfig t)) = ToKeepDefault {toKeepDefaultConfig = t}
-
-newToOverride :: InCommon -> ToOverride
-newToOverride (InCommon (SomeConfig t)) = ToOverride {toOverrideConfig = t}
-
-aRegistryIO :: IO (Registry _ _)
-aRegistryIO =
-  memoizeAll @IO $
-    specializePath @[IO ToOverride, IO InCommon] (valTo @IO $ SomeConfig "specialized config") $
-      funTo @IO SomeData
-        <: funTo @IO newToKeepDefault
-        <: funTo @IO newToOverride
-        <: funTo @IO InCommon
-        <: fun (\(c:: IO SomeConfig) -> InCommon <$> c)
-        <: valTo @IO (SomeConfig "default config")
 
 test_make_specialized_values = test "specialized values can be made" $ do
   let r =
diff --git a/test/Test/Data/Registry/RegistrySpec.hs b/test/Test/Data/Registry/RegistrySpec.hs
--- a/test/Test/Data/Registry/RegistrySpec.hs
+++ b/test/Test/Data/Registry/RegistrySpec.hs
@@ -81,17 +81,17 @@
 a :: Int -> Int -> IO Int
 a _ _ = pure 0
 
-b :: Int -> Int -> RIO Int
-b = outTo @RIO liftIO a
+b :: Int -> Int -> Rio Int
+b = outTo @Rio liftIO a
 
-c :: RIO Int -> RIO Int -> RIO Int
-c = allTo @RIO b
+c :: Rio Int -> Rio Int -> Rio Int
+c = allTo @Rio b
 
 -- here the result of outTo needs to be explicit
--- otherwise the type of d is RIO (Int -> Int -> RIO Int)
-d :: RIO Int -> RIO Int -> RIO Int
-d = allTo @RIO (outTo @RIO liftIO a :: Int -> Int -> RIO Int)
+-- otherwise the type of d is Rio (Int -> Int -> Rio Int)
+d :: Rio Int -> Rio Int -> Rio Int
+d = allTo @Rio (outTo @Rio liftIO a :: Int -> Int -> Rio Int)
 
 -- to avoid the issue with type inference above, we can use argsTo
-e :: RIO Int -> RIO Int -> RIO Int
-e = argsTo @RIO (outTo @RIO liftIO a)
+e :: Rio Int -> Rio Int -> Rio Int
+e = argsTo @Rio (outTo @Rio liftIO a)
diff --git a/test/Test/Tutorial/Exercise1.hs b/test/Test/Tutorial/Exercise1.hs
--- a/test/Test/Tutorial/Exercise1.hs
+++ b/test/Test/Tutorial/Exercise1.hs
@@ -8,7 +8,7 @@
       console' = newConsole
       userInput' = newUserInput console'
       rng' = newRng logger'
-      secretReader' = newSecretReader (SecretReaderConfig "txe/tests/Test/Tutorial/secret.txt") logger'
+      secretReader' = newSecretReader (SecretReaderConfig "test/Test/Tutorial/secret.txt") logger'
    in App
         { userInput = userInput',
           console = console',
diff --git a/test/Test/Tutorial/Exercise2.hs b/test/Test/Tutorial/Exercise2.hs
--- a/test/Test/Tutorial/Exercise2.hs
+++ b/test/Test/Tutorial/Exercise2.hs
@@ -14,7 +14,7 @@
     <: fun newRng
     <: fun newConsole
     <: fun newLogger
-    <: val (SecretReaderConfig "txe/tests/Test/Tutorial/secret.txt")
+    <: val (SecretReaderConfig "test/Test/Tutorial/secret.txt")
 
 newApp :: App
 newApp = make @App registry
diff --git a/test/Test/Tutorial/Exercise5.hs b/test/Test/Tutorial/Exercise5.hs
--- a/test/Test/Tutorial/Exercise5.hs
+++ b/test/Test/Tutorial/Exercise5.hs
@@ -10,10 +10,10 @@
 import System.Directory (doesFileExist)
 import Test.Tutorial.Application
 
-newCheckedSecretReader :: SecretReaderConfig -> Logger IO -> IO (SecretReader IO)
-newCheckedSecretReader (SecretReaderConfig path) logger = do
+newCheckedSecretReader :: MonadIO m => SecretReaderConfig -> Logger IO -> m (SecretReader IO)
+newCheckedSecretReader (SecretReaderConfig path) logger = liftIO $ do
   exists <- doesFileExist (toS path)
-  if not exists then fileDoesNotExist else pure ()
+  unless exists fileDoesNotExist
   pure
     SecretReader
       { readSecret =
@@ -29,10 +29,10 @@
   funTo @IO App
     <: funTo @IO newUserInput
     <: funTo @IO newRng
-    <: funTo @IO newCheckedSecretReader
+    <: funTo @IO (newCheckedSecretReader @IO)
     <: funTo @IO newLogger
     <: funTo @IO newConsole
-    <: valTo @IO (SecretReaderConfig "txe/tests/Test/Tutorial/secret.txt")
+    <: valTo @IO (SecretReaderConfig "test/Test/Tutorial/secret.txt")
 
 newAppIO :: IO App
 newAppIO = make @(IO App) registryIO
diff --git a/test/Test/Tutorial/Exercise6.hs b/test/Test/Tutorial/Exercise6.hs
--- a/test/Test/Tutorial/Exercise6.hs
+++ b/test/Test/Tutorial/Exercise6.hs
@@ -9,10 +9,10 @@
 import System.Directory (doesFileExist)
 import Test.Tutorial.Application
 
-newCheckedSecretReader :: SecretReaderConfig -> Logger IO -> Tag "unchecked" (SecretReader IO) -> IO (SecretReader IO)
-newCheckedSecretReader (SecretReaderConfig path) logger uncheckedReader = do
+newCheckedSecretReader :: MonadIO m => SecretReaderConfig -> Logger IO -> Tag "unchecked" (SecretReader IO) -> m (SecretReader IO)
+newCheckedSecretReader (SecretReaderConfig path) logger uncheckedReader = liftIO $ do
   exists <- doesFileExist (toS path)
-  if not exists then fileDoesNotExist else pure ()
+  unless exists fileDoesNotExist
   pure $ unTag uncheckedReader
   where
     fileDoesNotExist = error logger ("file does not exist at " <> path)
@@ -21,12 +21,12 @@
 registryIO =
   funTo @IO App
     <: funTo @IO newUserInput
-    <: funTo @IO newCheckedSecretReader
+    <: funTo @IO (newCheckedSecretReader @IO)
     <: funTo @IO (tag @"unchecked" newSecretReader)
     <: funTo @IO newRng
     <: funTo @IO newLogger
     <: funTo @IO newConsole
-    <: valTo @IO (SecretReaderConfig "txe/tests/Test/Tutorial/secret.txt")
+    <: valTo @IO (SecretReaderConfig "test/Test/Tutorial/secret.txt")
 
 newAppIO :: IO App
 newAppIO = make @(IO App) registryIO
diff --git a/test/Test/Tutorial/Exercise7.hs b/test/Test/Tutorial/Exercise7.hs
--- a/test/Test/Tutorial/Exercise7.hs
+++ b/test/Test/Tutorial/Exercise7.hs
@@ -7,27 +7,22 @@
 import Data.Registry
 import Protolude
 import Test.Tutorial.Application
-import Test.Tutorial.Exercise6
 
-newInitializedLogger :: IO (Logger IO)
-newInitializedLogger = do
+-- | This makes sure that there is only one logger ever used in the application
+newCachedLogger :: Rio (Logger IO)
+newCachedLogger = singleton $ do
   print ("start the logger" :: Text)
   pure (Logger putStrLn putStrLn)
 
-newInitializedRegistry :: Registry _ _
-newInitializedRegistry = fun newInitializedLogger <: registryIO
-
-newInitializedAppIO :: IO App
-newInitializedAppIO = make @(IO App) newInitializedRegistry
-
-memoizedRegistry :: IO (Registry _ _)
-memoizedRegistry = memoize @IO @(Logger IO) newInitializedRegistry
-
-newInitializedMemoizedAppIO :: IO App
-newInitializedMemoizedAppIO = make @(IO App) =<< memoizedRegistry
-
-memoizedAllRegistry :: IO (Registry _ _)
-memoizedAllRegistry = memoizeAll @IO newInitializedRegistry
+registry :: Registry _ _
+registry =
+  funTo @Rio App
+    <: funTo @Rio newUserInput
+    <: funTo @Rio newRng
+    <: funTo @Rio newSecretReader
+    <: fun newCachedLogger
+    <: funTo @Rio newConsole
+    <: valTo @Rio (SecretReaderConfig "test/Test/Tutorial/secret.txt")
 
-newInitializedMemoizedAllAppIO :: IO App
-newInitializedMemoizedAllAppIO = make @(IO App) =<< memoizedAllRegistry
+newAppIO :: IO App
+newAppIO = withRegistry @App registry pure
