I can't be arsed to write down the AME recap. New piece of AME: "unprotected {" is a way to incorporate legacy code or things that aren't reversible (e.g., synchronous I/O). These blocks have no transactional conflict support and aren't abortable. They have to be serialized manually or use only local state. unprotected blocks are by definition yielding; a new transaction starts after them. To get around making all legacy code in the universe yielding, you might wrap synchronous I/O in an asynchronous model. e.g.: -startOpen(File f) async call doOpen() which yields, but that doesn't matter f.opened = true To use in some other piece of code, you would do something like: OpenAndRead(char * name) { f = StartOpen(name); async DoRead(f); } DoRead(File f) { BlockUntil(f.opened); } You could make this look blocking by yielding before DoRead(). Don't use yield. For the rest of today, we'll try to implement tbb::pipeline using AME. Reminder of pipeline: add_filter(Filter * f); We'll call the first filter "generative" and the last one "consumptive". run(size_t numTokens); serial filters see all items in the same order! => we need sequence numbers. pair will represent items WARNING: I haven't been paying attention because I'm trying to find documentation on whether glibc malloc is thread-safe. struct Filter { Filter *nextFilter; friend class pipeline; int nextElt; Filter() : nextElt(0) {} }; //assume this is not the generative state void doStage(pair elt, Filter f) { //if we didn't do this sync, we'd have to yield and wait for it elt.second = f(elt.second); if(f.nextFilter) async doStage(elt, f.nextFilter); else { } } Insert looooooong digression about other stuff.