Hmmm, I see your point.
Frankly, what I tried to accomplish is to keep original (entered by admin) content unmodified while keep cached the result of substitution (and use exactly this result when page to be rendered). The idea is to have whole page looking good in any case. Smart replacements (like doing nice quotes (“” instead of "") or right sized dashes) should be done on the
whole page after all snippets etc. is there and processed.
What I can invent is to have extra field added to modx_content table (let’s name it like ’content_cache’), and on
OnBoforeDocFormSave I will simple repopulate it with result of substitution. This way the overhead will be there (I will store twice as much content), but the bad news I need to reinject my "cached content" into page rendering later. Really no idea how to do that

.
Another way I see (dumb one but surely to work) is to create some new ("cache") table, where keys will be
md5’s of page sources and values will be the same sources after replacements. On
OnWebPagePrerender we hook simple function that will query DB with key=md5 of page source to be sent out, and if there is no such a key, we’ll simple do our replacement and add the result to out cache table. It it’s there we don’t need further replacement and simple output the query result. Sound messy, sorry for that, hope you see the idea.
That you for spending time for my question!