In best insertion, the jobs are evaluated in sequence. For half of the time, they are sorted based on priority, and for the other half of time, the sequence is random.
In regret insertion, the jobs are not evaluated in sequence. Instead, a regret score is calculated for all the jobs, and the ones with higher score are inserted first. In the scorer, you can see that the score is also related with job priority.
Therefore, I think, if you set high priority for service A, it might help, but not guaranteed.
In the state updater which implements StateUpdater and ActivityVisitor, you need to maintain a HashSet of job ids as follows:
In the begin method, load the HashSet by stateManager.getProblemState(…), and if it is null, initialize it to an empty HashSet;
In the visit method, add the job id of the activity to the HashSet;
In the finish method, update the state with the HashSet by stateManager.putProblemState(…).
Then, in the hard route constraint, if the new job insertionContext.getJob() is job A (or B), then load the HashSet by stateManager.getProblemState(…) and check if the id of job B (or A) is in it: if yes, return false; otherwise, return true.
There are quite a few state updaters in the state package and you can take a look.
This sounds similar to driver break. You can take a look at how it is implemented in jsprit.