Thanks, this sounds like exactly the kind of problem that structured concurrency is designed to solve. You might find some of the discussion of robust cancellation interesting over at the Trio forum.
As I understand it, the main idea is to scope the lifetime of child tasks along with the function call stack so that tasks started by a function don’t outlive that function. (More generally, to be explicit about the context in which a task is started so that the task doesn’t outlive that context. Trio calls those contexts “nurseries”.)