Regional tagging (e.g. safe/unsafe) would be a better approach, as it would allow developers to more naturally protect code, without redefining its structure to suit your library.
You start to grok the problem here, but primarily think about it in terms of databases, which are just one (admittedly common) type of external state:
>> If you need to perform a non-deterministic operation like accessing the database, calling a third-party API, generating a random number, or getting the local time, you shouldn't do it directly in a workflow function. Instead, you should do all database operations in transactions and all other non-deterministic operations in steps.
Note: Think you should really change "all" into "each in a separate transaction/step" there, to communicate what you're recommending?
As a thought exercise: imagine a Python program that automates a third party application via the GUI. Some UI actions cannot be undone (e.g. submit). Some are repeatable without consequence (e.g. navigating between screens).
How would your framework support that?
Because if you can efficiently support the pathological leaky-state case, you can trivially support all simpler cases.