diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+## [_Unreleased_](https://github.com/freckle/yield/compare/v0.0.0.0...main)
+
+## [v0.0.0.0](https://github.com/freckle/yield/tree/v0.0.0.0)
+
+First tagged release.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2024 Renaissance Learning Inc
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.lhs b/README.lhs
new file mode 100644
--- /dev/null
+++ b/README.lhs
@@ -0,0 +1,42 @@
+# Yield
+
+<!--
+```haskell
+module Main (main) where
+
+import Prelude
+import Text.Markdown.Unlit ()
+import Test.Hspec
+```
+-->
+
+```haskell
+import Control.Monad.Yield
+```
+
+```haskell
+action1 :: MonadYield Char m => m Char
+action1 = do
+  yield 'a'
+  yield 'b'
+  pure 'c'
+```
+
+```haskell
+action2 :: forall m. Monad m => m (String, Char)
+action2 = runYieldT listAggregation action1
+-- returns ("ab", 'c')
+```
+
+<!--
+```haskell
+main :: IO ()
+main = hspec do
+  it "" do
+    action2 `shouldReturn` ("ab", 'c')
+```
+-->
+
+---
+
+[LICENSE](./LICENSE) | [CHANGELOG](./CHANGELOG.md)
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,42 @@
+# Yield
+
+<!--
+```haskell
+module Main (main) where
+
+import Prelude
+import Text.Markdown.Unlit ()
+import Test.Hspec
+```
+-->
+
+```haskell
+import Control.Monad.Yield
+```
+
+```haskell
+action1 :: MonadYield Char m => m Char
+action1 = do
+  yield 'a'
+  yield 'b'
+  pure 'c'
+```
+
+```haskell
+action2 :: forall m. Monad m => m (String, Char)
+action2 = runYieldT listAggregation action1
+-- returns ("ab", 'c')
+```
+
+<!--
+```haskell
+main :: IO ()
+main = hspec do
+  it "" do
+    action2 `shouldReturn` ("ab", 'c')
+```
+-->
+
+---
+
+[LICENSE](./LICENSE) | [CHANGELOG](./CHANGELOG.md)
diff --git a/library/Control/Monad/Trans/Codensity.hs b/library/Control/Monad/Trans/Codensity.hs
new file mode 100644
--- /dev/null
+++ b/library/Control/Monad/Trans/Codensity.hs
@@ -0,0 +1,43 @@
+{-# LANGUAGE UndecidableInstances #-}
+
+module Control.Monad.Trans.Codensity
+  ( CodensityT (..)
+  ) where
+
+import Control.Monad.IO.Class (MonadIO (..))
+import Control.Monad.Reader (MonadReader (ask, local))
+import Control.Monad.State (MonadState (state))
+import Control.Monad.Trans.Class (MonadTrans (..))
+import Control.Monad.Yield.Class (MonadYield (..))
+import Data.Kind (Type)
+import Prelude (Applicative (..), Functor (..), Monad (..), const, ($), (.))
+
+-- | The "codensity transform", taken as an excerpt from the @kan-extensions@
+--   package, following the model of @conduit@ to permit efficient monadic bind
+newtype CodensityT (m :: Type -> Type) (a :: Type) = CodensityT {runCodensity :: forall b. (a -> m b) -> m b}
+
+instance Functor (CodensityT m) where
+  fmap f (CodensityT m) = CodensityT (\k -> m (k . f))
+
+instance Applicative (CodensityT m) where
+  pure x = CodensityT (\k -> k x)
+  CodensityT f <*> CodensityT g = CodensityT (\bfr -> f (\ab -> g (bfr . ab)))
+
+instance Monad (CodensityT m) where
+  m >>= k = CodensityT (\c -> runCodensity m (\a -> runCodensity (k a) c))
+
+instance MonadTrans CodensityT where
+  lift m = CodensityT (m >>=)
+
+instance MonadIO m => MonadIO (CodensityT m) where
+  liftIO = lift . liftIO
+
+instance MonadYield a m => MonadYield a (CodensityT m) where
+  yield = lift . yield
+
+instance MonadReader r m => MonadReader r (CodensityT m) where
+  ask = CodensityT (ask >>=)
+  local f m = CodensityT $ \c -> ask >>= \r -> local f . runCodensity m $ local (const r) . c
+
+instance MonadState s m => MonadState s (CodensityT m) where
+  state = lift . state
diff --git a/library/Control/Monad/Trans/Yield.hs b/library/Control/Monad/Trans/Yield.hs
new file mode 100644
--- /dev/null
+++ b/library/Control/Monad/Trans/Yield.hs
@@ -0,0 +1,122 @@
+{-# LANGUAGE UndecidableInstances #-}
+
+module Control.Monad.Trans.Yield
+  ( YieldT
+  , runYieldT
+  , hoistYieldT
+  ) where
+
+import Prelude
+
+import Control.Monad (ap, liftM)
+import Control.Monad.IO.Class (MonadIO (..))
+import Control.Monad.Reader (MonadReader (..))
+import Control.Monad.State (MonadState (..))
+import Control.Monad.Trans.Class (MonadTrans (..))
+import Control.Monad.Trans.Codensity (CodensityT (..))
+import Control.Monad.Yield.Aggregation (Aggregation (..))
+import Control.Monad.Yield.Class (MonadYield (..))
+
+-- | Monad transformer that adds dynamically interpretable 'MonadYield' support
+newtype YieldT a m r = YieldT (CodensityT (Yielder a m) r)
+  deriving newtype (Functor, Applicative, Monad)
+
+instance MonadTrans (YieldT a) where
+  lift = YieldT . lift . lift
+
+deriving newtype instance MonadIO m => MonadIO (YieldT a m)
+
+deriving newtype instance Functor m => MonadYield a (YieldT a m)
+
+deriving newtype instance MonadReader r m => MonadReader r (YieldT a m)
+
+deriving newtype instance MonadState s m => MonadState s (YieldT a m)
+
+-- | Transforms a 'YieldT''s base monad actions
+hoistYieldT
+  :: forall m n a r
+   . Monad m
+  => (forall x. m x -> n x)
+  -> YieldT a m r
+  -> YieldT a n r
+hoistYieldT f (YieldT (CodensityT y0)) = YieldT $ CodensityT \continue ->
+  let go = \case
+        Pure r -> continue r
+        Yield a y -> Yield a (go y)
+        Act m -> Act (f $ liftM go $ collapse m)
+         where
+          collapse mpipe = do
+            pipe' <- mpipe
+            case pipe' of
+              Act mpipe' -> collapse mpipe'
+              _ -> pure pipe'
+  in  go $ y0 Pure
+
+-- | Runs a 'YieldT' computation in the base monad
+runYieldT
+  :: forall a b m r
+   . Monad m
+  => Aggregation m a b
+  -- ^ What to do with the values emitted from the stream
+  -> YieldT a m r
+  -> m (b, r)
+  -- ^ The result of aggregating of stream values, and the result of the 'YieldT'
+runYieldT agg (YieldT (CodensityT y)) = runYielder agg (y Pure)
+
+data Yielder a m r
+  = Yield a (Yielder a m r)
+  | Act (m (Yielder a m r))
+  | Pure r
+  deriving stock (Functor)
+
+instance Functor m => Applicative (Yielder a m) where
+  pure = Pure
+  (<*>) = ap
+
+instance Functor m => Monad (Yielder a m) where
+  p0 >>= f = go p0
+   where
+    go p = case p of
+      Yield a continue -> Yield a $ go continue
+      Act m -> Act (go <$> m)
+      Pure r -> f r
+
+instance Functor m => MonadYield a (Yielder a m) where
+  yield x = Yield x (Pure ())
+
+instance MonadTrans (Yielder a) where
+  lift = Act . fmap Pure
+
+instance MonadIO m => MonadIO (Yielder a m) where
+  liftIO = lift . liftIO
+
+instance MonadReader r m => MonadReader r (Yielder a m) where
+  ask = lift ask
+  local f = hoistYielder $ local f
+
+instance MonadState s m => MonadState s (Yielder a m) where
+  state = lift . state
+
+hoistYielder
+  :: Functor n => (forall x. m x -> n x) -> Yielder a m r -> Yielder a n r
+hoistYielder f p0 = go p0
+ where
+  go = \case
+    Yield a p -> Yield a $ go p
+    Act m -> Act $ go <$> f m
+    Pure r -> Pure r
+
+-- | Runs a 'Yielder' computation in the base monad
+runYielder
+  :: Monad m
+  => Aggregation m a b
+  -- ^ What to do with the values emitted from the stream
+  -> Yielder a m r
+  -> m (b, r)
+  -- ^ The result of aggregating of stream values, and the result of the 'Yielder'
+runYielder Aggregation {begin, step, done} p0 = begin >>= go p0
+ where
+  go p x = case p of
+    Yield a continue -> (go continue $!) =<< step x a
+    Act m -> m >>= \p' -> go p' x
+    Pure r -> (,r) <$> done x
diff --git a/library/Control/Monad/Yield.hs b/library/Control/Monad/Yield.hs
new file mode 100644
--- /dev/null
+++ b/library/Control/Monad/Yield.hs
@@ -0,0 +1,19 @@
+module Control.Monad.Yield
+  ( -- * Class
+    MonadYield (..)
+
+    -- * Monad transformer
+  , YieldT
+  , runYieldT
+  , hoistYieldT
+
+    -- * Aggregation
+  , Aggregation (..)
+  , noAggregation
+  , seqAggregation
+  , listAggregation
+  ) where
+
+import Control.Monad.Trans.Yield
+import Control.Monad.Yield.Aggregation
+import Control.Monad.Yield.Class
diff --git a/library/Control/Monad/Yield/Aggregation.hs b/library/Control/Monad/Yield/Aggregation.hs
new file mode 100644
--- /dev/null
+++ b/library/Control/Monad/Yield/Aggregation.hs
@@ -0,0 +1,36 @@
+module Control.Monad.Yield.Aggregation
+  ( Aggregation (..)
+  , noAggregation
+  , seqAggregation
+  , listAggregation
+  ) where
+
+import Data.Foldable (toList)
+import Data.Sequence (Seq (..))
+import Prelude (Applicative (..), Functor (..), (.), (<$>))
+
+-- | An effectful stream consumer
+--
+-- The type parameter @x@ represents the consumer's internal state.
+data Aggregation m a b = forall x.
+  Aggregation
+  { begin :: m x
+  -- ^ Produce the initial state
+  , step :: x -> a -> m x
+  -- ^ Consume one item from the stream
+  , done :: x -> m b
+  -- ^ Produce the final result
+  }
+
+instance Functor m => Functor (Aggregation m a) where
+  fmap f Aggregation {begin, step, done} =
+    Aggregation {begin, step, done = fmap f . done}
+
+noAggregation :: Applicative m => Aggregation m a ()
+noAggregation = Aggregation {begin = pure (), step = \() _ -> pure (), done = pure}
+
+seqAggregation :: Applicative m => Aggregation m a (Seq a)
+seqAggregation = Aggregation {begin = pure Empty, step = \xs -> pure . (xs :|>), done = pure}
+
+listAggregation :: Applicative m => Aggregation m a [a]
+listAggregation = toList <$> seqAggregation
diff --git a/library/Control/Monad/Yield/Class.hs b/library/Control/Monad/Yield/Class.hs
new file mode 100644
--- /dev/null
+++ b/library/Control/Monad/Yield/Class.hs
@@ -0,0 +1,9 @@
+module Control.Monad.Yield.Class
+  ( MonadYield (..)
+  ) where
+
+import Control.Monad (Monad)
+import Data.Kind (Type)
+
+class Monad m => MonadYield (a :: Type) (m :: Type -> Type) | m -> a where
+  yield :: a -> m ()
diff --git a/package.yaml b/package.yaml
new file mode 100644
--- /dev/null
+++ b/package.yaml
@@ -0,0 +1,88 @@
+name: yield
+version: 0.0.0.0
+maintainer: Freckle Education
+category: Streaming
+github: freckle/yield
+synopsis: YieldT monad transformer
+description: |
+  This package defines a class @MonadYield a@ for monads that can emit values
+  values of type @a@ while they run, and a monad transformer @YieldT a@ which
+  implements this class such that the emitted values can be captured in a
+  streaming manner.
+
+  This is an amalgamation of concepts from @pipes@, @conduit@, @foldl@, and
+  @kan-extensions@, combined into a small self-contained package.
+
+extra-doc-files:
+  - README.md
+  - CHANGELOG.md
+
+extra-source-files:
+  - package.yaml
+
+language: GHC2021
+
+ghc-options:
+  - -Weverything
+    -Wno-missing-kind-signatures
+  - -Wno-all-missed-specialisations
+  - -Wno-missed-specialisations
+  - -Wno-missing-exported-signatures # re-enables missing-signatures
+  - -Wno-missing-import-lists
+  - -Wno-missing-local-signatures
+  - -Wno-missing-safe-haskell-mode
+  - -Wno-monomorphism-restriction
+  - -Wno-prepositive-qualified-module
+  - -Wno-safe
+  - -Wno-unsafe
+
+when:
+  - condition: "impl(ghc >= 9.8)"
+    ghc-options:
+      - -Wno-missing-role-annotations
+      - -Wno-missing-poly-kind-signatures
+
+dependencies:
+  - base < 5
+
+default-extensions:
+  - BlockArguments
+  - DataKinds
+  - DeriveAnyClass
+  - DerivingVia
+  - DerivingStrategies
+  - FunctionalDependencies
+  - GADTs
+  - LambdaCase
+  - NoImplicitPrelude
+  - NoMonomorphismRestriction
+  - OverloadedStrings
+  - RecordWildCards
+  - TypeFamilies
+
+library:
+  source-dirs: library
+  other-modules:
+    - Control.Monad.Trans.Codensity
+  dependencies:
+    - containers
+    - mtl
+    - transformers
+
+tests:
+  spec:
+    main: Spec.hs
+    source-dirs: tests
+    ghc-options: -threaded -rtsopts "-with-rtsopts=-N"
+    dependencies:
+      - hspec
+      - yield
+      - mtl
+
+  readme:
+    main: README.lhs
+    ghc-options: -pgmL markdown-unlit
+    dependencies:
+      - hspec
+      - markdown-unlit
+      - yield
diff --git a/tests/Spec.hs b/tests/Spec.hs
new file mode 100644
--- /dev/null
+++ b/tests/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover -Wno-missing-export-lists #-}
diff --git a/tests/YieldSpec.hs b/tests/YieldSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/YieldSpec.hs
@@ -0,0 +1,66 @@
+module YieldSpec (spec) where
+
+import Prelude
+
+import Control.Monad.Reader
+import Control.Monad.State
+import Control.Monad.Yield
+import Data.Foldable
+import Test.Hspec
+
+spec :: Spec
+spec = describe "YieldT" do
+  describe "Trivial examples" do
+    it "" $ runYieldT noAggregation (pure 'x') `shouldReturn` ((), 'x')
+    it "" $ runYieldT listAggregation (pure 'x') `shouldReturn` ("", 'x')
+    it "" $
+      runYieldT listAggregation (yield 'a' >> pure 'x') `shouldReturn` ("a", 'x')
+    it "" $
+      runYieldT noAggregation (yield 'a' >> pure 'x') `shouldReturn` ((), 'x')
+    it "" $
+      runYieldT listAggregation (yield 'a' >> yield 'b' >> pure 'c')
+        `shouldReturn` ("ab", 'c')
+
+  describe "Monad instance" do
+    it "" $
+      runYieldT
+        listAggregation
+        (traverse_ @[] yield "ab" >> traverse @[] yield "cd" >> pure 'x')
+        `shouldReturn` ("abcd", 'x')
+
+  describe "MonadReader instance" do
+    it "" $ runReaderT (runYieldT noAggregation ask) 'a' `shouldReturn` ((), 'a')
+    it "" $
+      runReaderT (runYieldT noAggregation (local succ ask)) 'a'
+        `shouldReturn` ((), 'b')
+    it "" $
+      runReaderT
+        ( runYieldT listAggregation do
+            ask >>= yield
+            local succ do
+              ask >>= yield
+              local succ do
+                ask >>= yield
+            ask >>= yield
+        )
+        'g'
+        `shouldReturn` ("ghig", ())
+
+  describe "MonadState instance" do
+    it "" $ evalStateT (runYieldT noAggregation get) 'a' `shouldReturn` ((), 'a')
+    it "" $
+      evalStateT (runYieldT noAggregation (modify' succ >> get)) 'a'
+        `shouldReturn` ((), 'b')
+    it "" $
+      evalStateT
+        ( do
+            (a, _) <- runYieldT listAggregation do
+              get >>= yield
+              modify' succ
+              get >>= yield
+              put 'x'
+              get >>= yield
+            gets (a,)
+        )
+        'g'
+        `shouldReturn` ("ghx", 'x')
diff --git a/yield.cabal b/yield.cabal
new file mode 100644
--- /dev/null
+++ b/yield.cabal
@@ -0,0 +1,124 @@
+cabal-version:      1.18
+name:               yield
+version:            0.0.0.0
+license:            MIT
+license-file:       LICENSE
+maintainer:         Freckle Education
+homepage:           https://github.com/freckle/yield#readme
+bug-reports:        https://github.com/freckle/yield/issues
+synopsis:           YieldT monad transformer
+description:
+    This package defines a class @MonadYield a@ for monads that can emit values
+    values of type @a@ while they run, and a monad transformer @YieldT a@ which
+    implements this class such that the emitted values can be captured in a
+    streaming manner.
+    .
+    This is an amalgamation of concepts from @pipes@, @conduit@, @foldl@, and
+    @kan-extensions@, combined into a small self-contained package.
+
+category:           Streaming
+build-type:         Simple
+extra-source-files: package.yaml
+extra-doc-files:
+    README.md
+    CHANGELOG.md
+
+source-repository head
+    type:     git
+    location: https://github.com/freckle/yield
+
+library
+    exposed-modules:
+        Control.Monad.Trans.Yield
+        Control.Monad.Yield
+        Control.Monad.Yield.Aggregation
+        Control.Monad.Yield.Class
+
+    hs-source-dirs:     library
+    other-modules:      Control.Monad.Trans.Codensity
+    default-language:   GHC2021
+    default-extensions:
+        BlockArguments DataKinds DeriveAnyClass DerivingVia
+        DerivingStrategies FunctionalDependencies GADTs LambdaCase
+        NoImplicitPrelude NoMonomorphismRestriction OverloadedStrings
+        RecordWildCards TypeFamilies
+
+    ghc-options:
+        -Weverything -Wno-missing-kind-signatures
+        -Wno-all-missed-specialisations -Wno-missed-specialisations
+        -Wno-missing-exported-signatures -Wno-missing-import-lists
+        -Wno-missing-local-signatures -Wno-missing-safe-haskell-mode
+        -Wno-monomorphism-restriction -Wno-prepositive-qualified-module
+        -Wno-safe -Wno-unsafe
+
+    build-depends:
+        base >=4.16.4.0 && <5,
+        containers >=0.6.5.1,
+        mtl >=2.2.2,
+        transformers >=0.5.6.2
+
+    if impl(ghc >=9.8)
+        ghc-options:
+            -Wno-missing-role-annotations -Wno-missing-poly-kind-signatures
+
+test-suite readme
+    type:               exitcode-stdio-1.0
+    main-is:            README.lhs
+    other-modules:      Paths_yield
+    default-language:   GHC2021
+    default-extensions:
+        BlockArguments DataKinds DeriveAnyClass DerivingVia
+        DerivingStrategies FunctionalDependencies GADTs LambdaCase
+        NoImplicitPrelude NoMonomorphismRestriction OverloadedStrings
+        RecordWildCards TypeFamilies
+
+    ghc-options:
+        -Weverything -Wno-missing-kind-signatures
+        -Wno-all-missed-specialisations -Wno-missed-specialisations
+        -Wno-missing-exported-signatures -Wno-missing-import-lists
+        -Wno-missing-local-signatures -Wno-missing-safe-haskell-mode
+        -Wno-monomorphism-restriction -Wno-prepositive-qualified-module
+        -Wno-safe -Wno-unsafe -pgmL markdown-unlit
+
+    build-depends:
+        base >=4.16.4.0 && <5,
+        hspec >=2.9.7,
+        markdown-unlit >=0.5.1,
+        yield
+
+    if impl(ghc >=9.8)
+        ghc-options:
+            -Wno-missing-role-annotations -Wno-missing-poly-kind-signatures
+
+test-suite spec
+    type:               exitcode-stdio-1.0
+    main-is:            Spec.hs
+    hs-source-dirs:     tests
+    other-modules:
+        YieldSpec
+        Paths_yield
+
+    default-language:   GHC2021
+    default-extensions:
+        BlockArguments DataKinds DeriveAnyClass DerivingVia
+        DerivingStrategies FunctionalDependencies GADTs LambdaCase
+        NoImplicitPrelude NoMonomorphismRestriction OverloadedStrings
+        RecordWildCards TypeFamilies
+
+    ghc-options:
+        -Weverything -Wno-missing-kind-signatures
+        -Wno-all-missed-specialisations -Wno-missed-specialisations
+        -Wno-missing-exported-signatures -Wno-missing-import-lists
+        -Wno-missing-local-signatures -Wno-missing-safe-haskell-mode
+        -Wno-monomorphism-restriction -Wno-prepositive-qualified-module
+        -Wno-safe -Wno-unsafe -threaded -rtsopts -with-rtsopts=-N
+
+    build-depends:
+        base >=4.16.4.0 && <5,
+        hspec >=2.9.7,
+        mtl >=2.2.2,
+        yield
+
+    if impl(ghc >=9.8)
+        ghc-options:
+            -Wno-missing-role-annotations -Wno-missing-poly-kind-signatures
