Yeah, these kinds of "orthogonal" things that you want to set up "on the outside" and then have affect the "inner" code (like allocators, "io" in this case, and maybe also presence/absence of GC, etc.) all seem to cry out for something like Lisp dynamic variables.
It depends on how you do it. XSLT 2.0 had <xsl:tunnel>, where you still had to declare them explicitly as function (well, template) parameters, just with a flag. No explicit control over levels, you just get the most recent one that someone has passed with <xsl:with-param tunnel="yes"> with the matching qualified name.
For something like Zig, it would make sense to go one step further and require them to be declared to be passed, i.e. no "tunneling" through interleaving non-Io functions. But it could still automatically match them e.g. by types, so that any argument of type Io, if marked with some keyword to indicate explicit propagation, would be automatically passed to a call that requires one.