diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,31 @@
+# Revision history for cauldron
+
+## 0.4.0.0
+
+* `exportToDot` takes a new parameter to configure how to print the steps. Before, only the TyCon was printed. Now, the full type is printed by default.
+
+## 0.3.1.0 
+
+* Now the `MissingDependencies` only includes keys with actual missing dependencies.
+
+## 0.3.0.0 
+
+* Add `cookNonEmpty` and `cookTree` for cooking hierarchies of 'Cauldron's.
+* Rename `addLast` to `addOuter` and `addFirst` to `addInner`.
+* Add a copy of the `Managed` type from ["managed"](https://hackage.haskell.org/package/managed).
+* Change the nomenclature of the `pack-` related functions.
+* Add the `Packer` type.
+* Add `Fire` type to customize the handling of dependency cycles.
+
+## 0.2.0.0 
+
+* Decorators are no longer `Endo`s. They just take the decorated entity as a 
+  regular parameter.
+
+* Remove the applicative wrappers.
+
+* Allow effectful constructors.
+
+## 0.1.0.0 -- YYYY-mm-dd
+
+* First version. Released on an unsuspecting world.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2023, Daniel Diaz
+
+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 Daniel Diaz 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/app/Main.hs b/app/Main.hs
new file mode 100644
--- /dev/null
+++ b/app/Main.hs
@@ -0,0 +1,240 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DerivingStrategies #-}
+
+-- | We have a bunch of datatypes, and a single recipe (constructor) for each
+-- datatype. This means the wiring can be type-directed: we don't have to make a
+-- decision at the term level about which constructor to use.
+--
+-- We wire the constructors in two ways: manually, and using a bit of dynamic
+-- typing magic from the "Cauldron" module.
+module Main where
+
+import Cauldron
+import Data.Function ((&))
+import Data.Maybe (fromJust)
+
+{-
+  HERE ARE A BUNCH OF DATATYPES.
+
+  The idea is that each datatype represents a bean, a component of our application.
+
+  In a real application, these datatypes would be records containing effectful functions.
+
+-}
+
+data A = A deriving (Show)
+
+data B = B deriving (Show)
+
+data C = C deriving (Show)
+
+data D = D deriving (Show)
+
+data E = E deriving (Show)
+
+data F = F deriving (Show)
+
+data G = G deriving (Show)
+
+data H = H deriving (Show)
+
+data I = I deriving (Show)
+
+data J = J deriving (Show)
+
+data K = K deriving (Show)
+
+data L = L deriving (Show)
+
+data M = M deriving (Show)
+
+data N = N deriving (Show)
+
+data O = O deriving (Show)
+
+data P = P deriving (Show)
+
+data Q = Q deriving (Show)
+
+data R = R deriving (Show)
+
+data S = S deriving (Show)
+
+data T = T deriving (Show)
+
+data U = U deriving (Show)
+
+data V = V deriving (Show)
+
+data W = W deriving (Show)
+
+data X = X deriving (Show)
+
+data Y = Y deriving (Show)
+
+data Z = Z deriving (Show)
+
+{-
+  These beans are a bit special: they are "secondary" beans which are optionally
+  produced by the constructors of other beans.
+
+  They have Monoid instances. The values returned by all the constructors that
+  produce them will be combined.
+-}
+
+-- | A window into the internal state of some bean.
+newtype Inspector = Inspector {inspect :: IO [String]}
+  deriving newtype (Semigroup, Monoid)
+
+-- | And initialization action which some beans might register.
+newtype Initializer = Initializer {runInitializer :: IO ()}
+  deriving newtype (Semigroup, Monoid)
+
+{-
+  HERE ARE A BUNCH OF CONSTRUCTORS AND DECORATORS.
+
+-}
+makeA :: A
+makeA = A
+
+-- A bean with a monoidal registration.
+--
+-- The registration could be some generic introspection mechanism, or perhaps
+-- some effectful action that sets up a worker thread.
+makeB :: (Inspector, B)
+makeB = (Inspector (pure ["B stuff"]), B)
+
+makeC :: C
+makeC = C
+
+makeD :: D
+makeD = D
+
+makeE :: A -> E
+makeE = \_ -> E
+
+makeF :: B -> C -> (Inspector, F)
+makeF = \_ _ -> (Inspector (pure ["F stuff"]), F)
+
+-- | A bean with a self-dependency!
+--
+-- We need this if we want self-invocations to be decorated.
+--
+-- Dependency cycles of more than one bean are forbidden, however.
+makeG :: E -> F -> G -> G
+makeG _ _ !_ = G
+
+-- | A decorator.
+--
+--  Decorators are basically normal constructors, only that they return
+--  a Endo that knows how to tweak the value of a bean.
+--
+-- Because they are normal constructors, they can be effectful, and they
+-- might have dependencies of their own.
+makeGDeco1 :: A -> G -> G
+makeGDeco1 _ g = g
+
+-- | A bean with two monoidal registrations.
+makeH :: A -> D -> G -> (Initializer, Inspector, H)
+makeH _ _ _ = (Initializer (putStrLn "H init"), Inspector (pure ["H stuff"]), H)
+
+-- | Notice that this bean has "Inspector" as a dependency. Inspector is a
+-- monoidal bean which is aggregated across all the constructor that register
+-- it. This is OK as long as there are no dependency cycles.
+--
+-- Why would a bean depend on such a aggregated bean? Well, for example, a
+-- server bean might want to publish diagnostic information collected from beans
+-- that register it.
+makeZ :: Inspector -> D -> H -> Z
+makeZ _ _ _ = Z
+
+makeZDeco1 :: B -> E -> Z -> Z
+makeZDeco1 _ _ z = z
+
+-- | A decorator with a monoidal registration.
+makeZDeco2 :: (F -> Z -> (Initializer, Z))
+makeZDeco2 = \_ z -> (Initializer (putStrLn "Z deco init"), z)
+
+boringWiring :: IO (Initializer, Inspector, Z)
+boringWiring = do
+  let -- We have to remember to collect the monoidal registrations.
+      initializer = init1 <> init2
+      -- We have to remember to collect the monoidal registrations.
+      inspector = inspector1 <> inspector2 <> inspector3
+      -- Now let's tie the constructors together.
+      a = makeA
+      (inspector1, b) = makeB
+      c = makeC
+      d = makeD
+      e = makeE a
+      (inspector2, f) = makeF b c
+      g0 = makeG e f g
+      g1 = makeGDeco1 a g0
+      g = g1
+      -- Here we apply a single decorator.
+      (init1, inspector3, h) = makeH a d g
+      -- Compose the decorators before applying them.
+      z0 = makeZ inspector d h
+      z1 = makeZDeco1 b e z0
+      (init2, z2) = makeZDeco2 f z1
+      z = z2
+  pure (initializer, inspector, z)
+
+-- | Here we don't have to worry about positional parameters. We throw all the
+-- constructors into the 'Cauldron' and taste the bean values at the end, plus a
+-- graph we may want to draw.
+--
+-- Note that we detect wiring errors *before* running the effectful constructors.
+coolWiring :: Fire IO -> Either BadBeans (DependencyGraph, IO (Initializer, Inspector, Z))
+coolWiring fire = do
+  let cauldron :: Cauldron IO =
+        mempty
+          & insert @A do makeBean do pack value makeA
+          & insert @B do makeBean do pack (valueWith \(reg, bean) -> regs1 reg bean) do makeB
+          & insert @C do makeBean do pack value makeC
+          & insert @D do makeBean do pack value makeD
+          & insert @E do makeBean do pack value makeE
+          & insert @F do makeBean do pack (valueWith \(reg, bean) -> regs1 reg bean) do makeF
+          & insert @G do
+            Bean
+              { constructor = pack value makeG,
+                decos =
+                  fromConstructors
+                    [ pack value makeGDeco1
+                    ]
+              }
+          & insert @H do makeBean do pack (valueWith \(reg1, reg2, bean) -> regs2 reg1 reg2 bean) do makeH
+          & insert @Z do
+            Bean
+              { constructor = pack value makeZ,
+                decos =
+                  fromConstructors
+                    [ pack value makeZDeco1,
+                      pack (valueWith \(reg, bean) -> regs1 reg bean) do makeZDeco2
+                    ]
+              }
+          & insert @(Initializer, Inspector, Z) do makeBean do pack value \a b c -> (a, b, c)
+  fmap (fmap (fmap (fromJust . taste @(Initializer, Inspector, Z)))) do cook fire cauldron
+
+main :: IO ()
+main = do
+  do
+    (Initializer {runInitializer}, Inspector {inspect}, z) <- boringWiring
+    inspection <- inspect
+    print inspection
+    print z
+    runInitializer
+  case coolWiring allowSelfDeps of
+    Left badBeans -> do
+      print badBeans
+    Right (depGraph, action) -> do
+      exportToDot defaultStepToText "beans.dot" depGraph
+      exportToDot defaultStepToText "beans-no-agg.dot" do removeSecondaryBeans do depGraph
+      exportToDot defaultStepToText "beans-no-agg-no-decos.dot" do removeDecos do removeSecondaryBeans do depGraph
+      exportToDot defaultStepToText "beans-simple.dot" do collapsePrimaryBeans do removeDecos do removeSecondaryBeans do depGraph
+      exportToDot defaultStepToText "beans-simple-with-decos.dot" do collapsePrimaryBeans do removeSecondaryBeans do depGraph
+      (Initializer {runInitializer}, Inspector {inspect}, z) <- action
+      inspection <- inspect
+      print inspection
+      print z
+      runInitializer
diff --git a/cauldron.cabal b/cauldron.cabal
new file mode 100644
--- /dev/null
+++ b/cauldron.cabal
@@ -0,0 +1,77 @@
+cabal-version:      3.4
+name:               cauldron
+version:            0.4.0.0
+synopsis:           Toy dependency injection framework
+description:        Toy dependency injection framework that wires things at runtime.
+license:            BSD-3-Clause
+license-file:       LICENSE
+author:             diaz_carrete@yahoo.com
+maintainer:         Daniel Díaz
+-- copyright:
+build-type:         Simple
+extra-doc-files:    CHANGELOG.md
+-- extra-source-files:
+category: Dependency Injection
+tested-with: GHC ==9.8.1 || ==9.6.3
+source-repository    head
+    type:     git
+    location: https://github.com/danidiaz/cauldron.git
+
+common common-base
+    build-depends:
+        base >= 4.18.0 && < 5,
+    ghc-options: -Wall
+    default-language: GHC2021
+
+common common-lib
+    import: common-base
+    build-depends:
+      containers >= 0.5.0 && < 0.8,
+      text >= 2.0 && < 2.2,
+
+common common-tests
+    import: common-lib
+    build-depends:
+      tasty           ^>= 1.5,
+      tasty-hunit     ^>= 0.10,
+      transformers >= 0.5 && < 0.7,
+      cauldron,
+
+library
+    import:           common-lib
+    build-depends:
+        algebraic-graphs ^>= 0.7,
+        multicurryable ^>= 0.1.1.0,
+        bytestring >= 0.10.0 && < 0.13,
+        sop-core >= 0.5.0 && < 0.6,
+    hs-source-dirs:   lib
+    exposed-modules:  
+        Cauldron
+        Cauldron.Managed
+
+executable cauldron-example-wiring
+    import:           common-base
+    main-is:          Main.hs
+    build-depends:
+        cauldron,
+
+    hs-source-dirs:   app
+
+test-suite tests
+    import:           common-tests
+    type:             exitcode-stdio-1.0
+    hs-source-dirs:   test
+    main-is:          tests.hs
+
+test-suite app-tests
+    import:           common-tests
+    type:             exitcode-stdio-1.0
+    hs-source-dirs:   test
+    main-is:          appTests.hs
+
+test-suite tests-managed
+    import:           common-tests
+    type:             exitcode-stdio-1.0
+    hs-source-dirs:   test
+    main-is:          managedTests.hs
+
diff --git a/lib/Cauldron.hs b/lib/Cauldron.hs
new file mode 100644
--- /dev/null
+++ b/lib/Cauldron.hs
@@ -0,0 +1,1081 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE FunctionalDependencies #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE UndecidableInstances #-}
+{-# LANGUAGE ViewPatterns #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+-- | This is a library for performing dependency injection. It's an alternative
+-- to manually wiring your functions and passing all required parameters
+-- explicitly. Instead of that, you throw your functions into a 'Cauldron', which wires
+-- them for you, guiding itself by the types.
+--
+-- Wiring errors are detected at runtime, not at compile time.
+--
+-- This library should be used at the ["composition root"](https://stackoverflow.com/questions/6277771/what-is-a-composition-root-in-the-context-of-dependency-injection) of the application,
+-- and only there: the components we are wiring together need not be aware that the library exists.
+--
+-- >>> :{
+-- data A = A deriving Show
+-- data B = B deriving Show
+-- data C = C deriving Show
+-- makeA :: A
+-- makeA = A
+-- makeB :: A -> B
+-- makeB = \_ -> B
+-- makeC :: A -> B -> IO C
+-- makeC = \_ _ -> pure C
+-- :}
+--
+-- >>> :{
+-- do
+--   let cauldron :: Cauldron IO
+--       cauldron =
+--         emptyCauldron
+--         & insert @A do makeBean do pack value makeA
+--         & insert @B do makeBean do pack value makeB
+--         & insert @C do makeBean do pack effect makeC
+--       Right (_ :: DependencyGraph, action) = cook forbidDepCycles cauldron
+--   beans <- action
+--   pure do taste @C beans
+-- :}
+-- Just C
+module Cauldron
+  ( -- * Filling the cauldron
+    Cauldron,
+    emptyCauldron,
+    insert,
+    adjust,
+    delete,
+    hoistCauldron,
+
+    -- * Beans
+    Bean (..),
+    makeBean,
+    setConstructor,
+    setDecos,
+    overDecos,
+    hoistBean,
+
+    -- ** Decorators
+    -- $decos
+    Decos,
+    emptyDecos,
+    fromConstructors,
+    addOuter,
+    addInner,
+    hoistDecos,
+
+    -- ** Constructors
+    -- $constructors
+    Constructor,
+    pack,
+    pack0,
+    pack1,
+    pack2,
+    pack3,
+    hoistConstructor,
+    Packer (..),
+    value,
+    effect,
+
+    -- *** Registering secondary beans
+    -- $registrations
+    valueWith,
+    effectWith,
+    Regs,
+    regs0,
+    regs1,
+    regs2,
+    regs3,
+
+    -- * Cooking the beans
+    cook,
+    cookNonEmpty,
+    cookTree,
+
+    -- ** How loopy can we get?
+    Fire,
+    forbidDepCycles,
+    allowSelfDeps,
+
+    -- ** Tasting the results
+    BoiledBeans,
+    taste,
+    BadBeans (..),
+    PathToCauldron,
+
+    -- ** Drawing deps
+    DependencyGraph,
+    exportToDot,
+    defaultStepToText,
+    BeanConstructionStep (..),
+    removeSecondaryBeans,
+    removeDecos,
+    collapsePrimaryBeans,
+    toAdjacencyMap,
+  )
+where
+
+import Algebra.Graph.AdjacencyMap (AdjacencyMap)
+import Algebra.Graph.AdjacencyMap qualified as Graph
+import Algebra.Graph.AdjacencyMap.Algorithm qualified as Graph
+import Algebra.Graph.Export.Dot qualified as Dot
+import Control.Applicative
+import Control.Monad.Fix
+import Data.Bifunctor (first)
+import Data.ByteString qualified
+import Data.Dynamic
+import Data.Foldable qualified
+import Data.Functor (($>), (<&>))
+import Data.Functor.Compose
+import Data.Functor.Contravariant
+import Data.Kind
+import Data.List.NonEmpty (NonEmpty)
+import Data.List.NonEmpty qualified
+import Data.Map.Strict (Map)
+import Data.Map.Strict qualified as Map
+import Data.Maybe (fromJust)
+import Data.Monoid (Endo (..))
+import Data.SOP (All, And, K (..))
+import Data.SOP.NP
+import Data.Sequence (Seq)
+import Data.Sequence qualified as Seq
+import Data.Set (Set)
+import Data.Set qualified as Set
+import Data.Text qualified
+import Data.Text.Encoding qualified
+import Data.Tree
+import Data.Type.Equality (testEquality)
+import Data.Typeable
+import GHC.Exts (IsList (..))
+import Multicurryable
+import Type.Reflection qualified
+
+-- | A map of 'Bean' recipes. Parameterized by the monad @m@ in which the 'Bean'
+-- 'Constructor's might have effects.
+newtype Cauldron m where
+  Cauldron :: {recipes :: Map TypeRep (SomeBean m)} -> Cauldron m
+
+-- | Union of two 'Cauldron's, right-biased: prefers values from the /right/ cauldron when
+-- both contain the same bean. (Note that 'Data.Map.Map' is left-biased.)
+instance Semigroup (Cauldron m) where
+  Cauldron {recipes = r1} <> Cauldron {recipes = r2} = Cauldron do Map.unionWith (flip const) r1 r2
+
+instance Monoid (Cauldron m) where
+  mempty = Cauldron do Map.empty
+
+emptyCauldron :: Cauldron m
+emptyCauldron = mempty
+
+-- | Change the monad used by the beans in the 'Cauldron'.
+hoistCauldron :: (forall x. m x -> n x) -> Cauldron m -> Cauldron n
+hoistCauldron f (Cauldron {recipes}) = Cauldron {recipes = hoistSomeBean f <$> recipes}
+
+data SomeBean m where
+  SomeBean :: (Typeable bean) => Bean m bean -> SomeBean m
+
+hoistSomeBean :: (forall x. m x -> n x) -> SomeBean m -> SomeBean n
+hoistSomeBean f (SomeBean bean) = SomeBean do hoistBean f bean
+
+-- | A bean recipe, to be inserted into a 'Cauldron'.
+data Bean m bean where
+  Bean ::
+    { -- | How to build the bean itself.
+      constructor :: Constructor m bean,
+      -- | How to build the decorators that wrap the bean. There might be no decorators.
+      decos :: Decos m bean
+    } ->
+    Bean m bean
+
+-- | Change the monad used by the bean\'s 'Constructor' and its 'Decos'.
+hoistBean :: (forall x. m x -> n x) -> Bean m bean -> Bean n bean
+hoistBean f (Bean {constructor, decos}) =
+  Bean
+    { constructor = hoistConstructor f constructor,
+      decos = hoistDecos f decos
+    }
+
+-- | A 'Bean' without decorators, having only the main constructor.
+makeBean :: Constructor m a -> Bean m a
+makeBean constructor = Bean {constructor, decos = mempty}
+
+-- $decos
+--
+-- Decorators are 'Constructor's which, instead constructing the original
+-- version of a bean, they modify it in some way (but without changing its
+-- type). Because they modify the bean, typically decorators will take the bean
+-- as an argument.
+--
+-- Decorators can have other dependencies beyond the modified bean.
+--
+-- When the bean is a record-of-functions, decorators can be used to
+-- add behaviors like caching, logging... to the functions.
+--
+--
+-- >>> :{
+-- newtype Foo = Foo { sayFoo :: IO () }
+-- makeFoo :: Foo
+-- makeFoo = Foo { sayFoo = putStrLn "foo" }
+-- makeFooDeco1 :: Foo -> Foo
+-- makeFooDeco1 Foo { sayFoo } = Foo { sayFoo = putStrLn "deco1 enter" >> sayFoo >> putStrLn "deco1 exit" }
+-- makeFooDeco2 :: Foo -> IO Foo
+-- makeFooDeco2 Foo { sayFoo } = putStrLn "deco2 init" >> pure Foo { sayFoo = putStrLn "deco2 enter" >> sayFoo >> putStrLn "deco2 exit" }
+-- :}
+--
+-- >>> :{
+-- do
+--   let cauldron :: Cauldron IO
+--       cauldron =
+--         emptyCauldron
+--         & insert @Foo
+--           Bean {
+--             constructor = pack value makeFoo,
+--             decos = fromConstructors [
+--                  pack value makeFooDeco1,
+--                  pack effect makeFooDeco2
+--               ]
+--           }
+--       Right (_ :: DependencyGraph, action) = cook forbidDepCycles cauldron
+--   beans <- action
+--   let Just Foo {sayFoo} = taste beans
+--   sayFoo
+-- :}
+-- deco2 init
+-- deco2 enter
+-- deco1 enter
+-- foo
+-- deco1 exit
+-- deco2 exit
+
+-- | A list of 'Constructor's for the decorators of some 'Bean'.
+--
+-- 'Constructor's for a decorator will have the @bean@ itself among their
+-- arguments. That @bean@ argument will be either the \"bare\" undecorated
+-- bean (for the first decorator) or the result of applying the previous
+-- decorator in the list.
+--
+-- Decorators can have other dependencies besides the @bean@.
+newtype Decos m bean where
+  Decos :: {decoCons :: Seq (Constructor m bean)} -> Decos m bean
+  deriving newtype (Semigroup, Monoid)
+
+instance IsList (Decos m bean) where
+  type Item (Decos m bean) = Constructor m bean
+  fromList decos = Decos do GHC.Exts.fromList decos
+  toList (Decos {decoCons}) = GHC.Exts.toList decoCons
+
+-- | Empty list of decorators.
+emptyDecos :: Decos m bean
+emptyDecos = mempty
+
+-- | Change the monad used by the decorators.
+hoistDecos :: (forall x. m x -> n x) -> Decos m bean -> Decos n bean
+hoistDecos f (Decos {decoCons}) = Decos {decoCons = hoistConstructor f <$> decoCons}
+
+setConstructor :: Constructor m bean -> Bean m bean -> Bean m bean
+setConstructor constructor (Bean {decos}) = Bean {constructor, decos}
+
+setDecos :: Decos m bean -> Bean m bean -> Bean m bean
+setDecos decos (Bean {constructor}) = Bean {constructor, decos}
+
+overDecos :: (Decos m bean -> Decos m bean) -> Bean m bean -> Bean m bean
+overDecos f (Bean {constructor, decos}) = Bean {constructor, decos = f decos}
+
+-- | Add a new decorator that modifies the bean /after/ all existing decorators.
+--
+-- This means the behaviours it adds to the bean\'s methods will be applied
+-- /first/ when entering the method.
+addOuter :: Constructor m bean -> Decos m bean -> Decos m bean
+addOuter con (Decos {decoCons}) = Decos do decoCons Seq.|> con
+
+-- | Add a new decorator that modifies the bean /before/ all existing
+-- decorators.
+--
+-- This means the behaviours it adds to the bean\'s methods will be applied
+-- /last/, just before entering the base bean's method.
+--
+-- Usually 'addOuter' is preferrable.
+addInner :: Constructor m bean -> Decos m bean -> Decos m bean
+addInner con (Decos {decoCons}) = Decos do con Seq.<| decoCons
+
+-- | Build the decorators from a list of 'Constructor's, first innermost,
+-- last outermost.
+fromConstructors ::
+  [Constructor m bean] ->
+  Decos m bean
+fromConstructors cons = Decos do Seq.fromList cons
+
+-- $constructors
+--
+-- The bean-producing or bean-decorating functions that we want to wire need to be
+-- coaxed into a 'Constructor' value before creating a 'Bean' recipe and adding it to the 'Cauldron'.
+--
+-- If your aren't dealing with secondary beans, don't sweat it: use @pack value@ for pure
+-- constructors functions and @pack effect@ for effectful ones. That should be enough.
+
+-- | A way of building some @bean@ value, potentially requiring some
+-- dependencies, potentially returning some secondary beans
+-- along the primary @bean@ result, and also potentially requiring some
+-- initialization effect in a monad @m@.
+--
+-- Note that only the type of the primary @bean@ is reflected in the
+-- 'Constructor' type. Those of the dependencies and secondary beans are not.
+--
+-- A typical initialization monad will be 'IO', used for example to create
+-- mutable references that the bean will use internally. Sometimes the a
+-- constructor will allocate resources with bracket-like operations, and in that
+-- case a monad like 'Managed' might be needed instead.
+data Constructor m bean where
+  Constructor ::
+    (All Typeable args, All (Typeable `And` Monoid) regs) =>
+    { constructor_ :: Args args (m (Regs regs bean))
+    } ->
+    Constructor m bean
+
+data ConstructorReps where
+  ConstructorReps ::
+    { beanRep :: TypeRep,
+      argReps :: Set TypeRep,
+      regReps :: Map TypeRep Dynamic
+    } ->
+    ConstructorReps
+
+-- | Change the monad in which the 'Constructor'\'s effects take place.
+hoistConstructor :: (forall x. m x -> n x) -> Constructor m bean -> Constructor n bean
+hoistConstructor f (Constructor {constructor_}) = Constructor do fmap f constructor_
+
+-- | Put a recipe for a 'Bean' into the 'Cauldron'.
+--
+-- Only one recipe is allowed for each different @bean@ type, so 'insert' for a
+-- @bean@ will overwrite previous recipes for that type.
+insert ::
+  forall (bean :: Type) m.
+  (Typeable bean) =>
+  Bean m bean ->
+  Cauldron m ->
+  Cauldron m
+insert recipe Cauldron {recipes} = do
+  let rep = typeRep (Proxy @bean)
+  Cauldron {recipes = Map.insert rep (SomeBean recipe) recipes}
+
+-- | Tweak an already existing 'Bean' recipe.
+adjust ::
+  forall bean m.
+  (Typeable bean) =>
+  (Bean m bean -> Bean m bean) ->
+  Cauldron m ->
+  Cauldron m
+adjust f (Cauldron {recipes}) = do
+  let rep = typeRep (Proxy @bean)
+  Cauldron
+    { recipes =
+        Map.adjust
+          do
+            \(SomeBean (r :: Bean m a)) ->
+              case testEquality (Type.Reflection.typeRep @bean) (Type.Reflection.typeRep @a) of
+                Nothing -> error "should never happen"
+                Just Refl -> SomeBean (f r)
+          rep
+          recipes
+    }
+
+delete ::
+  forall bean m.
+  (Typeable bean) =>
+  Cauldron m ->
+  Cauldron m
+delete Cauldron {recipes} =
+  Cauldron {recipes = Map.delete (typeRep (Proxy @bean)) recipes}
+
+-- | Strategy for dealing with dependency cycles.
+--
+-- (Terrible uninformative name caused by a metaphor stretched too far.)
+data Fire m = Fire
+  { shouldOmitDependency :: (BeanConstructionStep, BeanConstructionStep) -> Bool,
+    followPlanCauldron ::
+      Cauldron m ->
+      BoiledBeans ->
+      Plan ->
+      m BoiledBeans
+  }
+
+removeBeanFromArgs :: ConstructorReps -> ConstructorReps
+removeBeanFromArgs ConstructorReps {argReps, regReps, beanRep} =
+  ConstructorReps {argReps = Set.delete beanRep argReps, regReps, beanRep}
+
+-- | Allow /direct/ self-dependencies.
+--
+-- A bean constructor might depend on itself. This can be useful for having
+-- decorated self-invocations, because the version of the bean received as
+-- argument comes \"from the future\" and is already decorated. (__BEWARE__:
+-- Pattern-matching too eagerly on this \"bean from the future\" during
+-- construction will cause infinite loops.)
+--
+-- Note that a 'MonadFix' instance is required of the initialization monad.
+allowSelfDeps :: (MonadFix m) => Fire m
+allowSelfDeps =
+  Fire
+    { shouldOmitDependency = \case
+        (BarePrimaryBean bean, PrimaryBean anotherBean) | bean == anotherBean -> True
+        _ -> False,
+      followPlanCauldron = \cauldron initial plan ->
+        mfix do
+          \final ->
+            Data.Foldable.foldlM
+              do followPlanStep cauldron final
+              initial
+              plan
+    }
+
+-- | Forbid any kind of cyclic dependencies between beans. This is probably what you want.
+forbidDepCycles :: (Monad m) => Fire m
+forbidDepCycles =
+  Fire
+    { shouldOmitDependency = \_ -> False,
+      followPlanCauldron = \cauldron initial plan ->
+        Data.Foldable.foldlM
+          do followPlanStep cauldron BoiledBeans {beans = Map.empty}
+          initial
+          plan
+    }
+
+-- https://discord.com/channels/280033776820813825/280036215477239809/1147832555828162594
+-- https://github.com/ghc-proposals/ghc-proposals/pull/126#issuecomment-1363403330
+
+-- | This function DOESN'T return the bean rep itself in the argreps.
+constructorReps :: (Typeable bean) => Constructor m bean -> ConstructorReps
+constructorReps Constructor {constructor_ = (_ :: Args args (m (Regs accums bean)))} =
+  ConstructorReps
+    { beanRep,
+      argReps =
+        do
+          Set.fromList do
+            collapse_NP do
+              cpure_NP @_ @args
+                do Proxy @Typeable
+                typeRepHelper,
+      regReps =
+        Map.fromList do
+          collapse_NP do
+            cpure_NP @_ @accums
+              do Proxy @(Typeable `And` Monoid)
+              typeRepHelper'
+    }
+  where
+    typeRepHelper :: forall a. (Typeable a) => K TypeRep a
+    typeRepHelper = K (typeRep (Proxy @a))
+    typeRepHelper' :: forall a. ((Typeable `And` Monoid) a) => K (TypeRep, Dynamic) a
+    typeRepHelper' = K (typeRep (Proxy @a), toDyn @a mempty)
+    beanRep = typeRep (Proxy @bean)
+
+type Plan = [BeanConstructionStep]
+
+-- | A step in the construction of a bean value.
+data BeanConstructionStep
+  = -- | Undecorated bean.
+    BarePrimaryBean TypeRep
+  | -- | Apply the decorator with the given index. Comes after the 'BarePrimaryBean' and all 'PrimaryBeanDeco's with a lower index value.
+    PrimaryBeanDeco TypeRep Int
+  | -- | Final, fully decorated version of a bean. If there are no decorators, comes directly after 'BarePrimaryBean'.
+    PrimaryBean TypeRep
+  | -- | Beans that are secondary registrations of a 'Constructor' and which are aggregated monoidally.
+    SecondaryBean TypeRep
+  deriving stock (Show, Eq, Ord)
+
+-- | The successful result of 'cook'ing a 'Cauldron'. Can't do a lot with them other than to 'taste' them.
+newtype BoiledBeans where
+  BoiledBeans :: {beans :: Map TypeRep Dynamic} -> BoiledBeans
+
+-- | Build the beans using the recipes stored in the 'Cauldron'.
+cook ::
+  forall m.
+  (Monad m) =>
+  Fire m ->
+  Cauldron m ->
+  Either BadBeans (DependencyGraph, m BoiledBeans)
+cook fire cauldron = do
+  let result = cookTree (Node (fire, cauldron) [])
+  result <&> \(tg, m) -> (rootLabel tg, rootLabel <$> m)
+
+-- | Cook a list of 'Cauldron's.
+--
+-- 'Cauldron's later in the list can see the beans in all previous 'Cauldron's,
+-- but not vice versa.
+--
+-- Beans in a 'Cauldron' have priority over the same beans in previous 'Cauldron's.
+cookNonEmpty ::
+  forall m.
+  (Monad m) =>
+  NonEmpty (Fire m, Cauldron m) ->
+  Either BadBeans (NonEmpty DependencyGraph, m (NonEmpty BoiledBeans))
+cookNonEmpty nonemptyCauldronList = do
+  let result = cookTree (nonEmptyToTree nonemptyCauldronList)
+  result <&> \(ng, m) -> (unsafeTreeToNonEmpty ng, unsafeTreeToNonEmpty <$> m)
+
+-- | Cook a hierarchy of 'Cauldron's.
+--
+-- 'Cauldron's down in the branches can see the beans of their ancestor
+-- 'Cauldron's, but not vice versa.
+--
+-- Beans in a 'Cauldron' have priority over the same beans in ancestor 'Cauldron's.
+cookTree ::
+  forall m.
+  (Monad m) =>
+  Tree (Fire m, Cauldron m) ->
+  Either BadBeans (Tree DependencyGraph, m (Tree BoiledBeans))
+cookTree (treecipes) = do
+  accumMap <- first DoubleDutyBeans do checkNoDoubleDutyBeans (snd <$> treecipes)
+  () <- first (uncurry MissingDependencies) do checkMissingDeps (Map.keysSet accumMap) (snd <$> treecipes)
+  treeplan <- first DependencyCycle do buildPlans (Map.keysSet accumMap) treecipes
+  Right
+    ( treeplan <&> \(graph, _) -> DependencyGraph {graph},
+      followPlan (BoiledBeans accumMap) (snd <$> treeplan)
+    )
+
+checkNoDoubleDutyBeans ::
+  Tree (Cauldron m) ->
+  Either (Set TypeRep) (Map TypeRep Dynamic)
+checkNoDoubleDutyBeans treecipes = do
+  let (accumMap, beanSet) = cauldronTreeRegs treecipes
+  let common = Set.intersection (Map.keysSet accumMap) beanSet
+  if not (Set.null common)
+    then Left common
+    else Right accumMap
+
+-- | Will always be @[]@ when using 'cook'; identifies a 'Cauldron' in a hierarchy of 'Cauldron's when
+-- using 'cookNonEmpty' or 'cookTree'.
+type PathToCauldron = [Int]
+
+cauldronTreeRegs :: Tree (Cauldron m) -> (Map TypeRep Dynamic, Set TypeRep)
+cauldronTreeRegs = foldMap cauldronRegs
+
+cauldronRegs :: Cauldron m -> (Map TypeRep Dynamic, Set TypeRep)
+cauldronRegs Cauldron {recipes} =
+  Map.foldMapWithKey
+    do \rep recipe -> (recipeRegs recipe, Set.singleton rep)
+    recipes
+
+-- | Returns the accumulators, not the main bean
+recipeRegs :: SomeBean m -> Map TypeRep Dynamic
+recipeRegs (SomeBean (Bean {constructor, decos = Decos {decoCons}})) = do
+  let extractRegReps = (.regReps) . constructorReps
+  extractRegReps constructor
+    <> foldMap extractRegReps decoCons
+
+checkMissingDeps ::
+  -- | accums
+  Set TypeRep ->
+  Tree (Cauldron m) ->
+  Either (PathToCauldron, Map TypeRep (Set TypeRep)) ()
+checkMissingDeps accums treecipes = do
+  let decoratedTreecipes = decorate ([], Map.empty, treecipes)
+      missing = (\(key, available, requested) -> first (key,) do checkMissingDepsCauldron accums (Map.keysSet available) requested) <$> decoratedTreecipes
+  sequence_ missing
+  where
+    decorate ::
+      (PathToCauldron, Map TypeRep PathToCauldron, Tree (Cauldron m)) ->
+      Tree (PathToCauldron, Map TypeRep PathToCauldron, Cauldron m)
+    decorate = unfoldTree
+      do
+        \(key, acc, Node (current@Cauldron {recipes}) rest) ->
+          let -- current level has priority
+              newAcc = (recipes $> key) `Map.union` acc
+              newSeeds = do
+                (i, z) <- zip [0 ..] rest
+                let newKey = key ++ [i]
+                [(newKey, newAcc, z)]
+           in ((key, newAcc, current), newSeeds)
+
+checkMissingDepsCauldron ::
+  -- | accums
+  Set TypeRep ->
+  -- | available at this level
+  Set TypeRep ->
+  Cauldron m ->
+  Either (Map TypeRep (Set TypeRep)) ()
+checkMissingDepsCauldron accums available Cauldron {recipes} = do
+  let missingMap = (`Map.mapMaybe` recipes) \someBean -> do
+        let missing = Set.filter (`Set.notMember` available) do demanded someBean
+        if Set.null missing
+          then Nothing
+          else Just missing
+  if not (Map.null missingMap)
+    then Left missingMap
+    else Right ()
+  where
+    demanded :: SomeBean m -> Set TypeRep
+    demanded (SomeBean Bean {constructor, decos = Decos {decoCons}}) =
+      ( Set.fromList do
+          let ConstructorReps {argReps = beanArgReps} = constructorReps constructor
+          Set.toList beanArgReps ++ do
+            decoCon <- Data.Foldable.toList decoCons
+            let ConstructorReps {argReps = decoArgReps} = constructorReps decoCon
+            Set.toList decoArgReps
+      )
+        `Set.difference` accums
+
+buildPlans :: Set TypeRep -> Tree (Fire m, Cauldron m) -> Either (NonEmpty BeanConstructionStep) (Tree (AdjacencyMap BeanConstructionStep, (Plan, Fire m, Cauldron m)))
+buildPlans secondary = traverse \(fire@Fire {shouldOmitDependency}, cauldron) -> do
+  let deps = filter (not . shouldOmitDependency) do buildDepsCauldron secondary cauldron
+  let graph = Graph.edges deps
+  case Graph.topSort graph of
+    Left recipeCycle ->
+      Left recipeCycle
+    Right (reverse -> plan) -> do
+      let completeGraph = Graph.edges deps
+      Right (completeGraph, (plan, fire, cauldron))
+
+buildDepsCauldron :: Set TypeRep -> Cauldron m -> [(BeanConstructionStep, BeanConstructionStep)]
+buildDepsCauldron secondary Cauldron {recipes} = do
+  let makeTargetStep :: TypeRep -> BeanConstructionStep
+      makeTargetStep rep =
+        if rep `Set.member` secondary
+          then SecondaryBean rep
+          else PrimaryBean rep
+  (flip Map.foldMapWithKey)
+    recipes
+    \beanRep
+     ( SomeBean
+         ( Bean
+             { constructor = constructor :: Constructor m bean,
+               decos = Decos {decoCons}
+             }
+           )
+       ) -> do
+        let bareBean = BarePrimaryBean beanRep
+            boiledBean = PrimaryBean beanRep
+            decos = do
+              (decoIndex, decoCon) <- zip [0 :: Int ..] (Data.Foldable.toList decoCons)
+              [(PrimaryBeanDeco beanRep decoIndex, decoCon)]
+            beanDeps = do
+              constructorEdges makeTargetStep bareBean (do constructorReps constructor)
+            decoDeps = do
+              (decoBean, decoCon) <- decos
+              constructorEdges makeTargetStep decoBean (removeBeanFromArgs do constructorReps decoCon)
+            full = bareBean Data.List.NonEmpty.:| (fst <$> decos) ++ [boiledBean]
+            innerDeps = zip (Data.List.NonEmpty.tail full) (Data.List.NonEmpty.toList full)
+        beanDeps ++ decoDeps ++ innerDeps
+
+constructorEdges ::
+  (TypeRep -> BeanConstructionStep) ->
+  BeanConstructionStep ->
+  ConstructorReps ->
+  [(BeanConstructionStep, BeanConstructionStep)]
+constructorEdges makeTargetStep item (ConstructorReps {argReps, regReps}) =
+  -- consumers depend on their args
+  ( do
+      argRep <- Set.toList argReps
+      let argStep = makeTargetStep argRep
+      [(item, argStep)]
+  )
+    ++
+    -- secondary beans depend on their producers
+    ( do
+        (regRep, _) <- Map.toList regReps
+        let repStep = SecondaryBean regRep
+        [(repStep, item)]
+    )
+
+followPlan ::
+  (Monad m) =>
+  BoiledBeans ->
+  (Tree (Plan, Fire m, Cauldron m)) ->
+  m (Tree BoiledBeans)
+followPlan initial treecipes =
+  unfoldTreeM
+    ( \(initial', Node (plan, Fire {followPlanCauldron}, cauldron) rest) -> do
+        newInitial' <- followPlanCauldron cauldron initial' plan
+        pure (newInitial', (,) newInitial' <$> rest)
+    )
+    (initial, treecipes)
+
+followPlanStep ::
+  (Monad m) =>
+  Cauldron m ->
+  BoiledBeans ->
+  BoiledBeans ->
+  BeanConstructionStep ->
+  m BoiledBeans
+followPlanStep Cauldron {recipes} (BoiledBeans final) (BoiledBeans super) item =
+  BoiledBeans <$> case item of
+    BarePrimaryBean rep -> case fromJust do Map.lookup rep recipes of
+      SomeBean (Bean {constructor}) -> do
+        let ConstructorReps {beanRep} = constructorReps constructor
+        -- We delete the beanRep before running the constructor,
+        -- because if we have a self-dependency, we don't want to use the bean
+        -- from a previous context (if it exists) we want the bean from final.
+        -- There is a test for this.
+        (super', bean) <- followConstructor constructor final (Map.delete beanRep super)
+        pure do Map.insert beanRep (toDyn bean) super'
+    PrimaryBeanDeco rep index -> case fromJust do Map.lookup rep recipes of
+      SomeBean (Bean {decos = Decos {decoCons}}) -> do
+        let decoCon = fromJust do Seq.lookup index decoCons
+        let ConstructorReps {beanRep} = constructorReps decoCon
+        -- Unlike before, we don't delete the beanRep before running the constructor.
+        (super', bean) <- followConstructor decoCon final super
+        pure do Map.insert beanRep (toDyn bean) super'
+    -- \| We do nothing here, the work has been done in previous 'BarePrimaryBean' and
+    -- 'PrimaryBeanDeco' steps.
+    PrimaryBean _ -> pure super
+    -- \| We do nothing here, secondary beans are built as a byproduct
+    -- of primary beans and decorators.
+    SecondaryBean _ -> pure super
+
+-- | Build a bean out of already built beans.
+-- This can only work without blowing up if there aren't dependecy cycles
+-- and the order of construction respects the depedencies!
+followConstructor ::
+  (Monad m) =>
+  Constructor m bean ->
+  Map TypeRep Dynamic ->
+  Map TypeRep Dynamic ->
+  m (Map TypeRep Dynamic, bean)
+followConstructor Constructor {constructor_ = Args {runArgs}} final super = do
+  let Extractor {runExtractor} = sequence_NP do cpure_NP (Proxy @Typeable) makeExtractor
+      args = runExtractor final super
+  results <- runArgs args
+  case results of
+    Regs regs bean -> do
+      let inserters = cfoldMap_NP (Proxy @(Typeable `And` Monoid)) makeRegInserter regs
+      pure (appEndo inserters super, bean)
+
+newtype Extractor a where
+  Extractor :: {runExtractor :: Map TypeRep Dynamic -> Map TypeRep Dynamic -> a} -> Extractor a
+  deriving (Functor, Applicative) via ((->) (Map TypeRep Dynamic) `Compose` ((->) (Map TypeRep Dynamic)))
+
+makeExtractor :: forall a. (Typeable a) => Extractor a
+makeExtractor =
+  let runExtractor final super =
+        fromJust do taste' @a super <|> taste' @a final
+   in Extractor {runExtractor}
+
+makeRegInserter :: forall a. ((Typeable `And` Monoid) a) => I a -> Endo (Map TypeRep Dynamic)
+makeRegInserter (I a) =
+  let appEndo dynMap = do
+        let reg = fromJust do taste' @a dynMap
+            dyn = toDyn (reg <> a)
+        Map.insert (dynTypeRep dyn) dyn dynMap
+   in Endo {appEndo}
+
+-- | Return the resulting @bean@, if present.
+taste :: forall bean. (Typeable bean) => BoiledBeans -> Maybe bean
+taste BoiledBeans {beans} = taste' beans
+
+taste' :: forall bean. (Typeable bean) => Map TypeRep Dynamic -> Maybe bean
+taste' beans = do
+  let rep = typeRep (Proxy @bean)
+  dyn <- Map.lookup rep beans
+  fromDynamic @bean dyn
+
+-- | Sometimes the 'cook'ing process goes wrong.
+data BadBeans
+  = -- | The 'Cauldron' identified by 'PathToCauldron' has beans
+    -- that depend on beans that can't be found either in the current 'Cauldron' or its ancestors.
+    MissingDependencies PathToCauldron (Map TypeRep (Set TypeRep))
+  | -- | Beans that work both as primary beans and as secondary beans
+    -- are disallowed.
+    DoubleDutyBeans (Set TypeRep)
+  | -- | Dependency cycles are disallowed by some 'Fire's.
+    DependencyCycle (NonEmpty BeanConstructionStep)
+  deriving stock (Show)
+
+-- | An edge means that the source depends on the target.
+--
+-- The dependencies of each bean are given separatedly from its decorators.
+newtype DependencyGraph = DependencyGraph {graph :: AdjacencyMap BeanConstructionStep}
+
+-- | Conversion to a graph type
+-- from the
+-- [algebraic-graphs](https://hackage.haskell.org/package/algebraic-graphs-0.7/docs/Algebra-Graph-AdjacencyMap.html)
+-- library for further processing.
+toAdjacencyMap :: DependencyGraph -> AdjacencyMap BeanConstructionStep
+toAdjacencyMap DependencyGraph {graph} = graph
+
+removeSecondaryBeans :: DependencyGraph -> DependencyGraph
+removeSecondaryBeans DependencyGraph {graph} =
+  DependencyGraph {graph = Graph.induce (\case SecondaryBean {} -> False; _ -> True) graph}
+
+removeDecos :: DependencyGraph -> DependencyGraph
+removeDecos DependencyGraph {graph} =
+  DependencyGraph {graph = Graph.induce (\case PrimaryBeanDeco {} -> False; _ -> True) graph}
+
+-- | Unifies 'PrimaryBean's with their respective 'BarePrimaryBean's and 'PrimaryBeanDeco's.
+--
+-- Also removes any self-loops.
+collapsePrimaryBeans :: DependencyGraph -> DependencyGraph
+collapsePrimaryBeans DependencyGraph {graph} = do
+  let simplified =
+        Graph.gmap
+          ( \case
+              BarePrimaryBean rep -> PrimaryBean rep
+              PrimaryBeanDeco rep _ -> PrimaryBean rep
+              other -> other
+          )
+          graph
+      -- Is there a simpler way to removoe self-loops?
+      vertices = Graph.vertexList simplified
+      edges = Graph.edgeList simplified
+      edgesWithoutSelfLoops =
+        filter
+          ( \case
+              (PrimaryBean source, PrimaryBean target) -> if source == target then False else True
+              _ -> True
+          )
+          edges
+  DependencyGraph {graph = Graph.vertices vertices `Graph.overlay` Graph.edges edgesWithoutSelfLoops}
+
+-- | See the [DOT format](https://graphviz.org/doc/info/lang.html).
+exportToDot :: (BeanConstructionStep -> Data.Text.Text) -> FilePath -> DependencyGraph -> IO ()
+exportToDot prettyRep filepath DependencyGraph {graph} = do
+  let dot =
+        Dot.export
+          do Dot.defaultStyle prettyRep
+          graph
+  Data.ByteString.writeFile filepath (Data.Text.Encoding.encodeUtf8 dot)
+
+defaultStepToText :: BeanConstructionStep -> Data.Text.Text
+defaultStepToText =
+  let p rep = Data.Text.pack do show rep
+   in \case
+        BarePrimaryBean rep -> p rep <> Data.Text.pack "#bare"
+        PrimaryBeanDeco rep index -> p rep <> Data.Text.pack ("#deco#" ++ show index)
+        PrimaryBean rep -> p rep
+        SecondaryBean rep -> p rep <> Data.Text.pack "#sec"
+
+newtype Args args r = Args {runArgs :: NP I args -> r}
+  deriving newtype (Functor, Applicative, Monad)
+
+argsN ::
+  forall (args :: [Type]) r curried.
+  (MulticurryableF args r curried (IsFunction curried)) =>
+  curried ->
+  Args args r
+argsN = Args . multiuncurry
+
+-- $registrations
+--
+-- 'Constructor's produce a single primary bean, but sometimes they might also
+-- \"register\" a number of secondary beans.
+--
+-- These secondary beans
+-- must have 'Monoid' instances and, unlike the primary bean, can be produced by
+-- more that one 'Constructor'. Their values are aggregated across all the 'Constructor's
+-- that produce them. The final aggregated value can be depended upon by other 'Constructor's
+-- as if it were a normal bean.
+--
+-- The 'Regs' type is used to represent the main bean along with the secondary
+-- beans that it registers. Because usually we'll be working with functions that
+-- do not use the 'Regs' type, a 'Packer' must be used to coax the \"tip\" of
+-- the constructor function into the required shape expected by 'Constructor'.
+--
+-- >>> :{
+-- data A = A deriving Show
+-- data B = B deriving Show
+-- data C = C (Sum Int) deriving Show
+-- makeA :: (Sum Int, A)
+-- makeA = (Sum 1, A)
+-- makeB :: A -> IO (Sum Int, B)
+-- makeB = \_ -> pure (Sum 2, B)
+-- makeC :: Sum Int -> C
+-- makeC = \theSum -> C theSum
+-- :}
+--
+--
+-- >>> :{
+-- do
+--   let cauldron :: Cauldron IO
+--       cauldron =
+--         emptyCauldron
+--         & insert @A do makeBean do pack (valueWith \(s, a) -> regs1 s a) makeA
+--         & insert @B do makeBean do pack (effectWith \(s, b) -> regs1 s b) makeB
+--         & insert @C do makeBean do pack value makeC
+--       Right (_ :: DependencyGraph, action) = cook forbidDepCycles cauldron
+--   beans <- action
+--   pure do taste @C beans
+-- :}
+-- Just (C (Sum {getSum = 3}))
+
+-- | Auxiliary type which contains a primary bean along with zero or more
+-- secondary beans. The secondary beans must have
+-- 'Monoid' instances.
+data Regs (regs :: [Type]) bean = Regs (NP I regs) bean
+  deriving (Functor)
+
+-- | A primary @bean@ without secondary beans.
+regs0 :: bean -> Regs '[] bean
+regs0 bean = Regs Nil bean
+
+-- | A primary @bean@ with one secondary bean.
+regs1 :: reg1 -> bean -> Regs '[reg1] bean
+regs1 reg1 bean = Regs (I reg1 :* Nil) bean
+
+-- | A primary @bean@ with two secondary beans.
+regs2 :: reg1 -> reg2 -> bean -> Regs '[reg1, reg2] bean
+regs2 reg1 reg2 bean = Regs (I reg1 :* I reg2 :* Nil) bean
+
+-- | A primary @bean@ with three secondary beans.
+regs3 :: reg1 -> reg2 -> reg3 -> bean -> Regs '[reg1, reg2, reg3] bean
+regs3 reg1 reg2 reg3 bean = Regs (I reg1 :* I reg2 :* I reg3 :* Nil) bean
+
+-- | Applies a transformation to the tip of a curried function, coaxing
+-- it into the shape expected by a 'Constructor', which includes information
+-- about which is the primary bean and which are the secondary ones.
+--
+-- * For pure constructors without registrations, try 'value'.
+--
+-- * For effectful constructors without registrations, try 'effect'.
+--
+-- More complex cases might require 'valueWith', 'effectWith', or working with
+-- the 'Packer' constructor itself.
+newtype Packer m regs bean r = Packer (r -> m (Regs regs bean))
+
+runPacker :: Packer m regs bean r -> r -> m (Regs regs bean)
+runPacker (Packer f) = f
+
+instance Contravariant (Packer m regs bean) where
+  contramap f (Packer p) = Packer (p . f)
+
+-- | For pure constructors that return the @bean@ directly, and do not register
+-- secondary beans.
+value :: (Applicative m) => Packer m '[] bean bean
+value = Packer \bean -> pure do regs0 bean
+
+-- | For effectul constructors that return an @m bean@ initialization action,
+-- and do not register secondary beans.
+effect :: (Applicative m) => Packer m '[] bean (m bean)
+effect = Packer \action -> do fmap regs0 action
+
+-- |
+-- >>> :{
+-- data A = A deriving Show
+-- data B = B deriving Show
+-- makeB :: A -> (Sum Int, B)
+-- makeB = \_ -> (Sum 1, B)
+-- constructorB :: Constructor IO B
+-- constructorB = pack (valueWith \(s,bean) -> regs1 s bean) makeB
+-- :}
+valueWith ::
+  (Applicative m, All (Typeable `And` Monoid) regs) =>
+  -- | Massage the pure value at the tip of the constructor into a 'Regs'.
+  (r -> Regs regs bean) ->
+  Packer m regs bean r
+valueWith f = Packer do pure . f
+
+-- |
+-- >>> :{
+-- data A = A deriving Show
+-- data B = B deriving Show
+-- makeB :: A -> IO (Sum Int, B)
+-- makeB = \_ -> pure (Sum 1, B)
+-- constructorB :: Constructor IO B
+-- constructorB = pack (effectWith \(s,bean) -> regs1 s bean) makeB
+-- :}
+effectWith ::
+  (Applicative m, All (Typeable `And` Monoid) regs) =>
+  -- | Massage the value returned by the action at the tip of the constructor into a 'Regs'.
+  (r -> Regs regs bean) ->
+  Packer m regs bean (m r)
+effectWith f = Packer do fmap f
+
+-- | Take a curried function that constructs a bean, uncurry it recursively and
+-- then apply a 'Packer' to its tip, resulting in a 'Constructor'.
+--
+-- >>> :{
+-- data A = A deriving Show
+-- data B = B deriving Show
+-- data C = C deriving Show
+-- makeB :: A -> B
+-- makeB = \_ -> B
+-- makeC :: A -> B -> IO C
+-- makeC = \_ _ -> pure C
+-- constructorB :: Constructor IO B
+-- constructorB = pack value makeB
+-- constructorC :: Constructor IO C
+-- constructorC = pack effect makeC
+-- :}
+--
+-- There are 'pack0', 'pack1'... functions which work for specific number of arguments, but
+-- the generic 'pack' should work in most cases anyway.
+pack ::
+  forall (args :: [Type]) r curried regs bean m.
+  ( MulticurryableF args r curried (IsFunction curried),
+    All Typeable args,
+    All (Typeable `And` Monoid) regs
+  ) =>
+  -- | Fit the outputs of the constructor into the auxiliary 'Regs' type.
+  --
+  -- See 'regs1' and similar functions.
+  Packer m regs bean r ->
+  -- | Action returning a function ending in @r@, some datatype containing
+  -- @regs@ and @bean@ values.
+  curried ->
+  Constructor m bean
+pack packer curried = Constructor do runPacker packer <$> do argsN curried
+
+-- | Slightly simpler version of 'pack' for @0@-argument functions.
+pack0 ::
+  (All (Typeable `And` Monoid) regs) =>
+  Packer m regs bean r ->
+  -- | @0@-argument constructor
+  r ->
+  Constructor m bean
+pack0 packer r = Constructor do Args @'[] \Nil -> runPacker packer r
+
+-- | Slightly simpler version of 'pack' for @1@-argument functions.
+pack1 ::
+  forall arg1 r m regs bean.
+  (Typeable arg1, All (Typeable `And` Monoid) regs) =>
+  Packer m regs bean r ->
+  -- | @1@-argument constructor
+  (arg1 -> r) ->
+  Constructor m bean
+pack1 packer f = Constructor do Args @'[arg1] \(I arg1 :* Nil) -> runPacker packer (f arg1)
+
+-- | Slightly simpler version of 'pack' for @2@-argument functions.
+pack2 ::
+  forall arg1 arg2 r m regs bean.
+  (Typeable arg1, Typeable arg2, All (Typeable `And` Monoid) regs) =>
+  Packer m regs bean r ->
+  -- | @2@-argument constructor
+  (arg1 -> arg2 -> r) ->
+  Constructor m bean
+pack2 packer f = Constructor do Args @[arg1, arg2] \(I arg1 :* I arg2 :* Nil) -> runPacker packer (f arg1 arg2)
+
+-- | Slightly simpler version of 'pack' for @3@-argument functions.
+pack3 ::
+  forall arg1 arg2 arg3 r m regs bean.
+  (Typeable arg1, Typeable arg2, Typeable arg3, All (Typeable `And` Monoid) regs) =>
+  Packer m regs bean r ->
+  -- | @3@-argument constructor
+  (arg1 -> arg2 -> arg3 -> r) ->
+  Constructor m bean
+pack3 packer f = Constructor do Args @[arg1, arg2, arg3] \(I arg1 :* I arg2 :* I arg3 :* Nil) -> runPacker packer (f arg1 arg2 arg3)
+
+nonEmptyToTree :: NonEmpty a -> Tree a
+nonEmptyToTree = \case
+  a Data.List.NonEmpty.:| [] -> Node a []
+  a Data.List.NonEmpty.:| (b : rest) -> Node a [nonEmptyToTree (b Data.List.NonEmpty.:| rest)]
+
+unsafeTreeToNonEmpty :: Tree a -> NonEmpty a
+unsafeTreeToNonEmpty = \case
+  Node a [] -> a Data.List.NonEmpty.:| []
+  Node a [b] -> Data.List.NonEmpty.cons a (unsafeTreeToNonEmpty b)
+  _ -> error "tree not list-shaped"
+
+-- $setup
+-- >>> :set -XBlockArguments
+-- >>> :set -Wno-incomplete-uni-patterns
+-- >>> import Data.Functor.Identity
+-- >>> import Data.Function ((&))
+-- >>> import Data.Monoid
diff --git a/lib/Cauldron/Managed.hs b/lib/Cauldron/Managed.hs
new file mode 100644
--- /dev/null
+++ b/lib/Cauldron/Managed.hs
@@ -0,0 +1,77 @@
+{-# LANGUAGE BlockArguments #-}
+
+module Cauldron.Managed
+  ( -- * The Managed monad for handling resources
+    Managed,
+    managed,
+    with,
+  )
+where
+
+import Control.Concurrent.MVar
+import Control.Exception.Base
+import Control.Monad.Fix
+import Control.Monad.IO.Class
+import GHC.IO.Unsafe
+
+-- | This is a copy of the @Managed@ type from the
+-- [managed](https://hackage.haskell.org/package/managed) package, with a dodgy
+-- 'Control.Monad.Fix.MonadFix' instance tacked on.
+newtype Managed a = Managed (forall b. (a -> IO b) -> IO b)
+
+-- | Build a 'Managed' value from a @withFoo@-style resource-handling function
+-- that accepts a continuation, like 'System.IO.withFile'.
+--
+-- Passing functions that do weird things like running their continuation
+-- /twice/ will tear apart the fabric of reality. Why would you want to do that?
+-- Pass only @withFoo@-style functions.
+managed :: (forall r. (a -> IO r) -> IO r) -> Managed a
+managed = Managed
+
+-- | This instance is a little dodgy (continuation-like monads don't have proper
+-- 'MonadFix' instances) but it is nevertheless useful because it lets us use
+-- 'Managed' with 'allowSelfDeps'. Follow the recommendations for the 'managed'
+-- function.
+--
+-- [\"if you embrace the unsafety, it could be a fun way to tie knots.\"](https://stackoverflow.com/questions/25827227/why-cant-there-be-an-instance-of-monadfix-for-the-continuation-monad#comment113010373_63906214)
+instance MonadFix Managed where
+  -- https://stackoverflow.com/a/63906214
+  -- See also the implementation for fixIO https://hackage.haskell.org/package/base-4.19.0.0/docs/src/System.IO.html#fixIO
+  mfix f = Managed \k -> do
+    m <- newEmptyMVar
+    x <-
+      unsafeDupableInterleaveIO
+        ( readMVar m `catch` \BlockedIndefinitelyOnMVar ->
+            throwIO FixIOException
+        )
+    unManage (f x) \x' -> do
+      putMVar m x'
+      k x'
+    where
+      unManage (Managed a) = a
+
+-- | Make use of the managed resource by supplying a callback.
+with :: Managed a -> (a -> IO b) -> IO b
+with (Managed r) = r
+
+instance Functor Managed where
+  fmap f (Managed m) = Managed (\k -> m (\x -> k (f x)))
+  {-# INLINE fmap #-}
+
+instance Applicative Managed where
+  pure x = Managed (\k -> k x)
+  {-# INLINE pure #-}
+  Managed f <*> Managed g = Managed (\bfr -> f (\ab -> g (\x -> bfr (ab x))))
+  {-# INLINE (<*>) #-}
+
+instance Monad Managed where
+  return = pure
+  {-# INLINE return #-}
+  m >>= k = Managed (\c -> with m (\a -> with (k a) c))
+  {-# INLINE (>>=) #-}
+
+instance MonadIO Managed where
+  liftIO m = Managed \return_ -> do
+    a <- m
+    return_ a
+  {-# INLINE liftIO #-}
diff --git a/test/appTests.hs b/test/appTests.hs
new file mode 100644
--- /dev/null
+++ b/test/appTests.hs
@@ -0,0 +1,159 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+-- | The example in the executable, as a test.
+module Main (main) where
+
+import Cauldron
+import Data.Function ((&))
+import Data.Maybe (fromJust)
+import Test.Tasty
+import Test.Tasty.HUnit
+
+data A = A deriving (Show)
+
+data B = B deriving (Show)
+
+data C = C deriving (Show)
+
+data D = D deriving (Show)
+
+data E = E deriving (Show)
+
+data F = F deriving (Show)
+
+data G = G deriving (Show)
+
+data H = H deriving (Show)
+
+data I = I deriving (Show)
+
+data J = J deriving (Show)
+
+data K = K deriving (Show)
+
+data L = L deriving (Show)
+
+data M = M deriving (Show)
+
+data N = N deriving (Show)
+
+data O = O deriving (Show)
+
+data P = P deriving (Show)
+
+data Q = Q deriving (Show)
+
+data R = R deriving (Show)
+
+data S = S deriving (Show)
+
+data T = T deriving (Show)
+
+data U = U deriving (Show)
+
+data V = V deriving (Show)
+
+data W = W deriving (Show)
+
+data X = X deriving (Show)
+
+data Y = Y deriving (Show)
+
+data Z = Z deriving (Show)
+
+newtype Inspector = Inspector {inspect :: IO [String]}
+  deriving newtype (Semigroup, Monoid)
+
+newtype Initializer = Initializer {runInitializer :: IO ()}
+  deriving newtype (Semigroup, Monoid)
+
+makeA :: A
+makeA = A
+
+makeB :: (Inspector, B)
+makeB = (Inspector (pure ["B stuff"]), B)
+
+makeC :: C
+makeC = C
+
+makeD :: D
+makeD = D
+
+makeE :: A -> E
+makeE = \_ -> E
+
+makeF :: B -> C -> (Inspector, F)
+makeF = \_ _ -> (Inspector (pure ["F stuff"]), F)
+
+makeG :: E -> F -> G -> G
+makeG _ _ (_ :: G) = G
+
+makeGDeco1 :: A -> G -> G
+makeGDeco1 _ g = g
+
+makeH :: A -> D -> G -> (Initializer, Inspector, H)
+makeH _ _ _ = (Initializer (putStrLn "H init"), Inspector (pure ["H stuff"]), H)
+
+makeZ :: Inspector -> D -> H -> Z
+makeZ _ _ _ = Z
+
+makeZDeco1 :: B -> E -> Z -> Z
+makeZDeco1 _ _ z = z
+
+makeZDeco2 :: F -> Z -> (Initializer, Z)
+makeZDeco2 = \_ z -> (Initializer (putStrLn "Z deco init"), z)
+
+coolWiring :: Fire IO -> Either BadBeans (DependencyGraph, IO (Initializer, Inspector, Z))
+coolWiring fire = do
+  let cauldron :: Cauldron IO =
+        mempty
+          & insert @A do makeBean do pack value makeA
+          & insert @B do makeBean do pack (valueWith \(reg, bean) -> regs1 reg bean) do makeB
+          & insert @C do makeBean do pack value makeC
+          & insert @D do makeBean do pack value makeD
+          & insert @E do makeBean do pack value makeE
+          & insert @F do makeBean do pack (valueWith \(reg, bean) -> regs1 reg bean) do makeF
+          & insert @G
+            Bean
+              { constructor = pack value do makeG,
+                decos =
+                  fromConstructors
+                    [ pack value do makeGDeco1
+                    ]
+              }
+          & insert @H do makeBean do pack (valueWith \(reg1, reg2, bean) -> regs2 reg1 reg2 bean) do makeH
+          & insert @Z
+            Bean
+              { constructor = pack value do makeZ,
+                decos =
+                  fromConstructors
+                    [ pack value do makeZDeco1,
+                      pack (valueWith \(reg, bean) -> regs1 reg bean) do makeZDeco2
+                    ]
+              }
+          & insert @(Initializer, Inspector, Z) do makeBean do pack value do \a b c -> (a, b, c)
+  fmap (fmap (fmap (fromJust . taste @(Initializer, Inspector, Z)))) do cook fire cauldron
+
+tests :: TestTree
+tests =
+  testGroup
+    "All"
+    [ testCase "example" do
+        case coolWiring allowSelfDeps of
+          Left badBeans -> assertFailure do show badBeans
+          Right _ -> pure ()
+        pure (),
+      testCase "dep cycles forbidden" do
+        case coolWiring forbidDepCycles of
+          Left (DependencyCycle _) -> pure ()
+          Left _ -> assertFailure do "wrong kind of error detected"
+          Right _ -> assertFailure do "self dependency not detected"
+        pure ()
+    ]
+
+main :: IO ()
+main = defaultMain tests
diff --git a/test/managedTests.hs b/test/managedTests.hs
new file mode 100644
--- /dev/null
+++ b/test/managedTests.hs
@@ -0,0 +1,109 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+module Main (main) where
+
+import Cauldron
+import Cauldron.Managed
+import Data.Function ((&))
+import Data.IORef
+import Data.Maybe (fromJust)
+import Data.Text (Text)
+import Test.Tasty
+import Test.Tasty.HUnit
+
+newtype Logger m = Logger
+  { logMessage :: Text -> m ()
+  }
+
+makeLogger :: IORef [Text] -> forall r. (Logger IO -> IO r) -> IO r
+makeLogger ref =
+  makeWithWrapperWithMessage
+    ref
+    "allocating logger"
+    "deallocating logger"
+    ( Logger \message ->
+        modifyIORef ref (++ [message])
+    )
+
+data Weird m = Weird
+  { weirdOp :: m (),
+    anotherWeirdOp :: m ()
+  }
+
+makeSelfInvokingWeird :: IORef [Text] -> Logger IO -> Weird IO -> forall r. (Weird IO -> IO r) -> IO r
+makeSelfInvokingWeird ref Logger {logMessage} ~Weird {weirdOp = selfWeirdOp} = do
+  makeWithWrapperWithMessage
+    ref
+    "allocating weird"
+    "deallocating weird"
+    ( Weird
+        { weirdOp = do
+            modifyIORef ref (++ ["weirdOp 2"])
+            logMessage "logging",
+          anotherWeirdOp = do
+            modifyIORef ref (++ ["another weirdOp 2"])
+            selfWeirdOp
+        }
+    )
+
+makeWeirdDecorator :: Logger IO -> Weird IO -> Weird IO
+makeWeirdDecorator Logger {logMessage} Weird {weirdOp = selfWeirdOp, anotherWeirdOp} =
+  Weird
+    { weirdOp = do
+        selfWeirdOp
+        logMessage "logging from deco",
+      anotherWeirdOp
+    }
+
+makeWithWrapperWithMessage ::
+  IORef [Text] ->
+  Text ->
+  Text ->
+  a ->
+  forall r. (a -> IO r) -> IO r
+makeWithWrapperWithMessage ref inMsg outMsg v handler = do
+  modifyIORef ref (++ [inMsg])
+  r <- handler v
+  modifyIORef ref (++ [outMsg])
+  pure r
+
+managedCauldron :: IORef [Text] -> Cauldron Managed
+managedCauldron ref =
+  emptyCauldron
+    & insert @(Logger IO) do makeBean do pack effect do managed (makeLogger ref)
+    & insert @(Weird IO)
+      Bean
+        { constructor = pack effect do \logger self -> managed (makeSelfInvokingWeird ref logger self),
+          decos =
+            fromConstructors
+              [ pack value makeWeirdDecorator
+              ]
+        }
+    & insert @(Logger IO, Weird IO) do makeBean do pack value do (,)
+
+tests :: TestTree
+tests =
+  testGroup
+    "All"
+    [ testCase "simple" do
+        ref <- newIORef []
+        case cook allowSelfDeps (managedCauldron ref) of
+          Left _ -> assertFailure "could not wire"
+          Right (_, beansAction) -> with beansAction \boiledBeans -> do
+            let (Logger {logMessage}, (Weird {anotherWeirdOp}) :: Weird IO) = fromJust . taste $ boiledBeans
+            logMessage "foo"
+            anotherWeirdOp
+            pure ()
+        traces <- readIORef ref
+        assertEqual
+          "traces"
+          ["allocating logger", "allocating weird", "foo", "another weirdOp 2", "weirdOp 2", "logging", "logging from deco", "deallocating weird", "deallocating logger"]
+          traces
+    ]
+
+main :: IO ()
+main = defaultMain tests
diff --git a/test/tests.hs b/test/tests.hs
new file mode 100644
--- /dev/null
+++ b/test/tests.hs
@@ -0,0 +1,242 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+module Main (main) where
+
+import Cauldron
+import Control.Monad.IO.Class
+import Control.Monad.Trans.Writer
+import Data.Function ((&))
+import Data.IORef
+import Data.List.NonEmpty (NonEmpty)
+import Data.List.NonEmpty qualified
+import Data.Map (Map)
+import Data.Map qualified as Map
+import Data.Maybe (fromJust)
+import Data.Monoid
+import Data.Text (Text)
+import Test.Tasty
+import Test.Tasty.HUnit
+
+type M = WriterT [Text] IO
+
+-- | And initialization action which some beans might register.
+newtype Initializer = Initializer {runInitializer :: M ()}
+  deriving (Semigroup, Monoid) via (Ap M ())
+
+newtype Logger m = Logger
+  { logMessage :: Text -> m ()
+  }
+
+makeLogger :: M (Initializer, Logger M)
+makeLogger = do
+  tell ["logger constructor"]
+  pure
+    ( Initializer do tell ["logger init"],
+      Logger \message -> tell [message]
+    )
+
+data Repository m = Repository
+  { findById :: Int -> m (Maybe Text),
+    store :: Int -> Text -> m ()
+  }
+
+makeRepository :: Logger M -> M (Initializer, Repository M)
+makeRepository Logger {logMessage} = do
+  mapRef <- liftIO do newIORef @(Map Int Text) mempty
+  pure
+    ( Initializer do logMessage "repo init invoking logger",
+      Repository
+        { findById = \key -> do
+            logMessage "findById"
+            m <- liftIO do readIORef mapRef
+            pure do Map.lookup key m,
+          store = \key v -> do
+            logMessage "store"
+            liftIO do modifyIORef mapRef do Map.insert key v
+        }
+    )
+
+data Weird m = Weird
+  { weirdOp :: m (),
+    anotherWeirdOp :: m ()
+  }
+
+makeWeird :: Logger M -> M (Weird M)
+makeWeird _ = do
+  tell ["weird constructor"]
+  pure
+    Weird
+      { weirdOp = tell ["weirdOp"],
+        anotherWeirdOp = tell ["another weirdOp"]
+      }
+
+data Lonely m = Lonely {soLonely :: m ()}
+
+makeLonely :: Lonely M
+makeLonely = do
+  Lonely {soLonely = tell ["so lonely"]}
+
+-- | Note that the patter-match on the self-dependency must be lazy, or else a
+-- nasty, difficult to diagnose infinite loop will happen!
+makeSelfInvokingWeird :: Weird M -> M (Weird M)
+makeSelfInvokingWeird ~Weird {weirdOp = selfWeirdOp} = do
+  tell ["self-invoking weird constructor"]
+  pure
+    Weird
+      { weirdOp = tell ["weirdOp 2"],
+        anotherWeirdOp = do
+          tell ["another weirdOp 2"]
+          selfWeirdOp
+      }
+
+weirdDeco :: Text -> Weird M -> Weird M
+weirdDeco txt Weird {weirdOp, anotherWeirdOp} =
+  Weird
+    { weirdOp = do
+        tell ["deco for weirdOp " <> txt]
+        weirdOp,
+      anotherWeirdOp = do
+        tell ["deco for anotherWeirdOp " <> txt]
+        anotherWeirdOp
+    }
+
+cauldron :: Cauldron M
+cauldron =
+  mempty
+    & insert @(Logger M) do makeBean do pack (Packer do fmap (\(reg, bean) -> regs1 reg bean)) do makeLogger
+    & insert @(Repository M) do makeBean do pack (Packer do fmap (\(reg, bean) -> regs1 reg bean)) do makeRepository
+    & insert @(Initializer, Repository M) do makeBean do pack value do \a b -> (a, b)
+
+cauldronMissingDep :: Cauldron M
+cauldronMissingDep =
+  cauldron
+    & delete @(Logger M)
+
+cauldronDoubleDutyBean :: Cauldron M
+cauldronDoubleDutyBean =
+  cauldron
+    & insert @Initializer do makeBean do pack value do do (Initializer (pure ()))
+
+cauldronWithCycle :: Cauldron M
+cauldronWithCycle =
+  cauldron
+    & insert @(Logger M) do makeBean do pack (Packer do fmap \(reg, bean) -> regs1 reg bean) do const @_ @(Repository M) makeLogger
+
+cauldronNonEmpty :: NonEmpty (Cauldron M)
+cauldronNonEmpty =
+  Data.List.NonEmpty.fromList
+    [ mempty
+        & do
+          let packer = Packer do fmap (\(reg, bean) -> regs1 reg bean)
+          insert @(Logger M) do makeBean do pack packer do makeLogger
+        & insert @(Weird M) do makeBean do pack effect makeWeird,
+      mempty
+        & insert @(Repository M) do makeBean do pack (Packer do fmap (\(reg, bean) -> regs1 reg bean)) do makeRepository
+        & insert @(Weird M)
+          Bean
+            { constructor = pack effect makeSelfInvokingWeird,
+              decos =
+                fromConstructors
+                  [ pack value do weirdDeco "inner",
+                    pack value do weirdDeco "outer"
+                  ]
+            }
+        & insert @(Initializer, Repository M, Weird M) do makeBean do pack value do \a b c -> (a, b, c)
+    ]
+
+cauldronLonely :: Cauldron M
+cauldronLonely = emptyCauldron & insert @(Lonely M) do makeBean do pack0 value makeLonely
+
+tests :: TestTree
+tests =
+  testGroup
+    "All"
+    [ testCase "value" do
+        (_, traces) <- case cook' cauldron of
+          Left _ -> assertFailure "could not wire"
+          Right (_, beansAction) -> runWriterT do
+            boiledBeans <- beansAction
+            let (Initializer {runInitializer}, Repository {findById, store}) = fromJust . taste $ boiledBeans
+            runInitializer
+            store 1 "foo"
+            _ <- findById 1
+            pure ()
+        assertEqual
+          "traces"
+          [ "logger constructor",
+            "logger init",
+            "repo init invoking logger",
+            "store",
+            "findById"
+          ]
+          traces,
+      testCase "value sequential" do
+        (_, traces) <- case cookNonEmpty' cauldronNonEmpty of
+          Left _ -> assertFailure "could not wire"
+          Right (_, beansAction) -> runWriterT do
+            _ Data.List.NonEmpty.:| [boiledBeans] <- beansAction
+            let ( Initializer {runInitializer},
+                  Repository {findById, store},
+                  Weird {anotherWeirdOp}
+                  ) = fromJust . taste $ boiledBeans
+            runInitializer
+            store 1 "foo"
+            _ <- findById 1
+            anotherWeirdOp
+            pure ()
+        assertEqual
+          "traces"
+          [ "logger constructor",
+            "weird constructor",
+            "self-invoking weird constructor",
+            "logger init",
+            "repo init invoking logger",
+            "store",
+            "findById",
+            -- the deco is applied! The outer the deco, the earliest is invoked.
+            "deco for anotherWeirdOp outer",
+            "deco for anotherWeirdOp inner",
+            "another weirdOp 2",
+            "deco for weirdOp outer",
+            "deco for weirdOp inner",
+            -- note that the self-invocation used the method from 'makeSelfInvokingWeird'
+            "weirdOp 2"
+          ]
+          traces,
+      testCase "lonely beans get build" do
+        (_, _) <- case cook' cauldronLonely of
+          Left _ -> assertFailure "could not wire"
+          Right (_, beansAction) -> runWriterT do
+            boiledBeans <- beansAction
+            let Lonely {soLonely} = fromJust . taste $ boiledBeans
+            soLonely
+            pure ()
+        pure (),
+      testCase "cauldron missing dep" do
+        case cook' cauldronMissingDep of
+          Left (MissingDependencies [] missingMap)
+            | Map.size missingMap == 1 -> pure ()
+            | otherwise -> assertFailure "missing dependency error has too many entries"
+          _ -> assertFailure "missing dependency not detected"
+        pure (),
+      testCase "cauldron with double duty bean" do
+        case cook' cauldronDoubleDutyBean of
+          Left (DoubleDutyBeans _) -> pure ()
+          _ -> assertFailure "double duty beans not detected"
+        pure (),
+      testCase "cauldron with cycle" do
+        case cook' cauldronWithCycle of
+          Left (DependencyCycle _) -> pure ()
+          _ -> assertFailure "dependency cycle not detected"
+        pure ()
+    ]
+  where
+    cook' = cook allowSelfDeps
+    cookNonEmpty' = cookNonEmpty . fmap (allowSelfDeps,)
+
+main :: IO ()
+main = defaultMain tests
