My ideas turned out to be less of a boilerplate increase, and more of a boilerplate consolidation. Specifically, the boilerplate got consolidated into the underlying libraries that other stuff is based on, so it's... basically fine. The changes I made were focused around picking an interface for the streams I'm manipulating and sticking to it.
Previously, I was trying to pass around iterators, which is kind of fragile, because they can only be used once, and kind of silly, because currently everything is ultimately backed by one of Python's string types, which are reusable. So, the new interface is based around iterables instead. Now, every iterator is an interable in Python, but not vice-versa, so if that were all, I'd have only gained a little. But also, I added the requirement that the iterables make a best effort to estimate their own size. The details of this are a little involved, but thankfully not in a way that most code has to care about. (It's basically a union of two custom protocols.)
The big cost of this approach is that it means I need to reimplement basic primitives like map(), because stdlib map doesn't bother to estimate its own length, according to basic manual testing I did.
The thing I really like about the outcome is that it makes it really obvious where logic and flow control should go, to satisfy these constraints. Because any fresh implementation of a stream transformation would require an implementation of the corresponding length estimation, it's "obviously better" to satisfy the requirements by breaking down complicated functions into simpler primitives with simpler reasoning. Then, I can replace the implementation of generator functions with something that just glues together the corresponding primitives.
(This does have the slight possible disadvantage that tracebacks are going to mostly ignore these helper functions, so that the relationship between frames is a little magic. But, I mean, it's not as bad as the time I figured out how to make tail-call elimination work with context managers. Which, now that I bring it up, I think could have improved ergonomics.)
Anyway, I made a little more basic progress, which meant bringing in some third-party libraries. I'm a little tempted to believe that I can implement the cryptographic primitives needed for this using my stream primitives, but that's not a good use of my time. I'll just try and get caught up to my second attempt. (My third attempt is just the vaporwave shitpost version of my second attempt.)