Hello Lee,
I've faced a similar issue before, and I used the solution of "persisting through the gateway", as you proposed. I do not think of it as dirty, because the application does not know which implementation of the gateway interface is actually being used. It could be an "in-memory cache", or an actual database persistence implementation.
My experience was implementing a sales application. A sales order has lots of information to be filled in the "header" alone, and also carries a collection of items, each with lots of information as well.
I decided to break the process of filling all this information into small use-cases, most of which produced an "invalid" or "transient" state in the system, because some of the required would be filled by the next use-case, until the final use-case "approves" the complete sales order and persists it in the database.
This means that I had an "in-memory cache" gateway implementation that would be sent to the "transient" use-cases, and an actual persistence gateway that was sent to the final use-case of "sales order approval".
Personally, I like the solution because the application only has to know of one way to deal with keeping information between use-cases, and that is the gateway.
However, we can argue that it adds complexity to the consumer of the application-core (delivery mechanism), because now the consumer has to decide which gateway to use for which use-case.
I believe, however, that this is indeed the responsibility of the application consumer. I'd rather add this decision-making process to the consumer, than force my application to use concepts like transient and persistent data.