I'm taking a look at implementing this but wanted to run the approaches I could think of by you:
The first thing I tried was a listener approach. I added the ability to set listeners on GremlinExecutor that match the life cycle methods like beforeEval and withResult.
In TraversalOpProcessor I get the gremlin executor from the context, iterate through any added listeners and call the matching lifecycle method. In AbstractEvalOpProcessor I added similar calls when the LifeCycle gets built.
In our provider we then create a class that implements the listener and call gremlinExecutor.addListener(...). This seems to work but there's no 1:1 matching arguments between TraversalOpProcessor and AbstractEvalOpProcessor that could call the listeners
The other approach I tried is to create a new setting called queryNotifications (for now) that works similar to graphManager where I can provide a class I want to use to deal with notifications. In Context I conditionally create the instance if the setting is present and provide a getter for it. Then I follow a similar pattern in AbstractEvalOpProcessor and TraveraslOpProcessor but I get the notification class from context and add a null check before calling it.
I'm not sure if you have a preferred approach or if my description of both approaches make senses or if there is a different way to approach this