From looking at this rule I think we have a bug. I created a more simplified example and logged
issue 321. I included links in the issue to some relevant parts of the code if you want to see why this is happening internally; feel free to seek clarification there or here if anything is unclear.
On this particular rule, from your expectation that you'd retract until only one service was present, I think you may have expected that Clara would prevent a fact from joining with itself, which is not the case. This rule could fire with a single service, so I think the correct behavior would be for it to remove all service facts from the session. If you want to normalize to a single "service view" per ID, you might consider using an accumulator or :exists condition (which is backed by an accumulator); see
extract-exists in the compiler. You could then insert a new fact of which there would be one per ID that you could use in downstream rules. The
accumulator doc discusses this some, specifically the paragraph after the first code block starting with "Note that we accumulate once per distinct set of bindings". This wouldn't free the memory consumed by duplicate service facts if that is the behavior you need though.
Regarding your first example, IMO Clara's semantics around RHS retraction aren't as well-defined as they ideally should be, with the major complicating factor here being that the RHS retraction is interacting with rules in its own salience group, namely itself. However, I see RHS retraction as a low-level operation that is sometimes useful but more often adds incidental complexity. For the most part, our rulesets are deep DAG structures terminating in queries with everything after some initial layers that filter lots of data governed by truth maintenance. This allows us to avoid the need to "think procedurally" when evaluating how a ruleset will behave. Obviously use cases vary though, and low-level operations like RHS retractions and to a lesser extent unconditional RHS insertions can certainly be useful at times.
Thanks for the examples, concrete instances of behavior demonstrating a bug or exposing insufficiently well defined behavior are helpful in making Clara more robust.