Ha! Adding module(user) to the list of options works. But what is the assumed module in the above case then? Maybe wait_preds should require module option? e.g. become wait_preds(Module, List)?
I have to retract this, it seems that something else caused thread_wait to behave
thread_wait/2 waits until Goal becomes true. So, the first thing it does is checking Goal, and write(x) is always true. The options are there to define when to retry evaluating Goal. In your case that is on an assert to foo/1. If you want to wait on any assert to foo/1, you could use
If the database is empty and you want to wait until there is a fact, use
?- thread_wait(foo(_), [wait_preds([+(foo/1)])]).
Note that the implementation is based on POSIX condition variables. Their semantics imply that spurious wakeups are possible. That is why you need a condition and you must be aware that the condition may be called while nothing really changed.
But in the end you are right - I realized that it would be better to refactor code away from “wait for facts to be asserted, then continue” to a “guard”-like predicate that is safe to be called multiple times.