Hello everyone. Starting with Clara Rules. It's an amazing tool. Thanks for all the work done here. However, I'm having trouble configuring the linters with clj-kondo so it doesn't complain about the defrule macro. Running clj-kondo --lint $(clj -Spath) --copy-configs in the project root configures the linter to the point where it recognizes everything except the docstring. clj-kondo complains with: Function name must be simple symbol but got: "this is the docstring". Any help? Thanks!
Juan if you want to provide some example code I can try to help, I wrote the clj-kondo linter for Clara and I thought it was pretty up to date, but all my contributions in recent years are here instead: https://github.com/k13labs/clara-rules, you can always copy the linter and add it to your project and make any changes you want. Last I checked though, docstrings work fine, but maybe you have run into an edge case?
Just FYI: I forked off with https://github.com/mrrodriguez/concrete-rules which I removed CLJS support for and made a deployment with the lint fixes (I know that jgomez did similar earlier). See the README there. I am interesting in exploring some future usages of the CLJ-side, but for now it is the same except with linting cleanup and the CLJS removal.
yeah, my fork lives here now: https://github.com/gateless/clara-rules, I've gone a bit beyond just removing CLJS at this point, if you'd like to chat sometime @mikerod let me know maybe our goals align?
Sure. Feel free to DM if that's easier to discuss draft thoughts.
Discuss here! I doubt anyone is looking at it, but for reference, the one big feature I've always wanted from Clara Rules is the ability to assert new rules at runtime. Some older, larger forward-chaining systems have this.
Yeah, we can keep things updated here if anything interesting is arrived at. I think at first it was just a sync to see if goals aligned well enough etc. Dynamically adding rules is an interesting idea too. I think that's probably feasible, but would have to consider the implications with TMS and durability etc. I'm open to issues/prs on the concrete-rules in the meantime to consider things, but it's worth considering changes that Jose has made on the gateless fork for those interested I think.
> consider the implications with TMS and durability etc. Gut says that TMS is the sticky bit here, as it would somewhat imply that you’d have to make a decision between: 1. Keep all facts for ever, regardless of matching 2. Agree that only facts seen after the rule is added should remain the former causes bloat in memory, the latter makes it challenging to track/understand why specific logic did/didn’t apply
Ah yeah. I forgot about that implication we discussed long ago. I think I originally considered that you'd have to start the network in a "mode" that supports adding rules. In that "mode" it would retain all facts in memory - therefore causing the bloat to be aware of. It could also have an option to not retain all facts if you thought the domain would not be confusing with that logic.
The trick would be to do it this without rippling tons of complexity all over the impl I think.
“static” vs “dynamic” rulebase mode is an interesting thought, and would likely suffice. I would imagine that additional rules with the current implementation would encounter some interesting performance characteristics if the 8-ball was needed to be shaken entirely again….
I'd like to understand the problem a bit better, is it truly about just modifying an existing session or more about modifying a running session while it is in the middle of running (thus at runtime)
@eraserhd can you explain a bit better what you mean just to make sure I don't misunderstand it? Maybe provide an example, I'm not promising to work on it but good to understand the problem first.
Good point. Just a few more notes:
1. My thoughts on it before that I didn't mention was that I was only considering adding rules/queries to a session in a stable state - not during the fire-rules loop.
2. I've often opted-out of this approach by instead suggesting that you have one session return suitable higher-level facts from the existing rules and send those facts to a new session with your new rules that can further refine the criteria, rather than to update-in-place the existing session's productions and memory.
yeah that's why I wanted some clarification, because if one feels like one needs to change the running rules during fire-rules it is often another problem masking as the need to change the running rules at runtime, if it is however changing a persistent session before fire-rules is invoked again that's probably doable but it wouldn't be free either, we solve that problem by just creating a fresh session whenever rules change at runtime. In a way clara already supports changing the rules at runtime because you can implement your own rules loader, which could load rules from a database (or in our case we load them from S3 blobs), then run rules with that, which would in turn create a new session with the updated rules. The fork of clara that I maintain adds two key things to support rules changing at runtime as described above:
• a first class session cache (using clojure.core.cache that can be provided), which caches the session as long as rules don't change.
• a first class compiler cache (also using clojure.core.cache), which caches compiled expressions, this one is an optimization that makes the mk-session faster when some expressions change but not all, making it so that only the LHS/RHS expressions that changed are re-compiled.
the key thing is to realize one does not have to load rules from namespaces
We had complicated sets of rules that we built from macros on top of macros, which consumed static data, and this just felt wrong. While I generally am happy to slather a thin layer of macros on top of some nice but verbose abstractions, leaving the more verbose API available for others to build on, stacking macros tends to suggest something wrong... I did not know about rules loaders, is that in the official Clara?
In any case, I did not consider about the fact retention problem, but I was thinking about asserting rules as facts in some way. I forgot which system I read about that does this? S-something? This seemed like a more Clojure-like workflow that doesn't enforce a ... sort of compile/runtime separation.
yeah rules loader is a protocol that anything can implement, then you can just pass that instead of a namespace symbol, in fact here's the symbol/namespace implementation: https://github.com/oracle-samples/clara-rules/blob/878c877ad4165386c1771a86c8343e29b494adf9/src/main/clojure/clara/rules.cljc#L274 (loads rules and queries I changed this a bit of my fork even going as far as renaming the protocol because I load not only rules but also it is the same idea: https://github.com/gateless/clara-rules/blob/main/src/main/clojure/clara/rules.clj#L144 - loads rules, queries and hierarchies (derive/underive fact hierarchy)
for example in our production rules pipeline we have an implementation of the above that loads rules from S3, if we want to update the rules we can just upload a new blob to S3 without re-deploying the service
I haven’t looked into this but thought there was work done here not long back. I suspect if you have a PR do fix it that would be applied without much friction.
There is already a PR to fix it: https://github.com/oracle-samples/clara-rules/pull/505 But it's already 1.5 years old...
I think only Ethan is left with maintainer access to that repo
Yeah it is @ethanc I believe. I approved the lint PR. I'll see if he wants to look at it/merge.