In the pipes library, the type of the composition operator is
(>+>) :: Pipe m a b r -> Pipe m b c r -> Pipe m a c r
If you look closely, then you will notice that all three pipes have result type r. How does this work? Simple: whichever pipe stops first provides the final result.
In my opinion this is wrong. The upstream pipe produces values, and the downstream pipe does something with them. The downstream pipe is the one that leads the computation, by pulling results from the upstream pipe. It is therefore always the downstream pipe that should provide the result. So, in the pipification of conduit, the proposed type for composition is instead
(>+>) :: Pipe m a b () -> Pipe m b c r -> Pipe m a c r
This makes it clear that the result of the first pipe is not used, the result of the composition always has to come from downstream. But now the result of the first pipe would be discarded completely.
Another, more general, solution is to communicate the result of the first pipe to the second one. That would give the await function in the downstream pipe the type
await :: Pipe m a b (Either r1 a)
where r1 is the result of the upstream pipe. Of course that r1 type needs to come from somewhere. So Pipe would need another type argument
data Pipe m streamin streamout finalin finalout
giving await the type
await :: Pipe m a b x (Either x a)
(>+>) :: Pipe m a b x y -> Pipe m b c y z -> Pipe m a c x z
Note that this article is just about a quick idea I had. I am not saying that this is the best way to do things. In fact, I am not even sure if propagating result values in this way actually helps solve any real world problems.