All things Artificial Intelligence related: Rules, Processes, Events, Agents, Planning, Ontologies and more :)
Tuesday, March 27, 2007
JBoss Rules 3.0.6 was released
This is a maintenance release, with a few bug fixes. We strongly advise all people using JBoss Rules versions 3.0.x to upgrade to version 3.0.6, specially those using dynamic rules.
Our special thanks to Frederic Burlet and Einat Idan for providing patches and all the community users for using, testing and reporting bugs and suggestions.
Follows the list of fixed bugs.
Release Notes for JBoss Rules 3.0.6
Bug
[ JBRULES-442 ] Building from source failed: Serialisation / externalisation Error
[ JBRULES-592 ] Problem with WorkingMemory serialization
[ JBRULES-598 ] Core is not indexing beta memory for Strings and Dates
[ JBRULES-603 ] AbstractRuleBase.removeRule(String, String) does not release the lock when failing removing rule from the package
[ JBRULES-612 ] NPE on asserting facts before reading rules with retract
[ JBRULES-615 ] Declared imports should override the default java.lang.* imports
[ JBRULES-641 ] Support for primitive array throws an org.drools.rule.InvalidRulePackage exception
Patch
[ JBRULES-607 ] Working memory is not disposed when releasing the RuleSession
Sunday, March 18, 2007
Writing Rules for Monitoring and Time-Sensitive Applications
I sent an e-mail to the users list asking for cases that users would like to discuss in more detail and some suggestions started to arrive. In this post I will talk about a subject that was suggested by Neil Goldman that was also kind enough to share his thoughts about how to model rules for Monitoring and Time-Sensitive Applications.
It is not the scope of this post to talk about Event Stream Processing, that besides being related to the subject, have different requirements and are too extensive to be included in this single post.
I will use JBoss Rules (a.k.a. Drools) for the examples, but the ideas should possibly be used with any other engine.
Monitoring Applications
Monitoring Applications are applications that monitor and eventually take actions based on inputs from sensors (temperature, weather, movement, etc) or agents (network elements, managed systems, managed beans, etc). They usually have requirements of working as Near-Real-Time Applications and are designed to react to system state changes.
Question is: how do I architect my application leveraging the advantages of the rules engines to monitoring applications?
Lets discuss the case using a simple practical example: let's say you have a monitoring application for your Air Conditioning System (ACS) that must control it to keep the temperature under a certain range.
One simple way of doing it using a Rules Engine is to work with a statefull working memory that reflects the state of the system and has rules that react to changes in this state. The state of the system, in the above example, is provided by the sensors, so it makes perfect sense to have your sensors modeled as facts. The rules will rely on these "sensor facts" to make decisions. So, your rules could be:
global org.drools.examples.acsmonitoring.AirConditioningSystem acs;
rule "turn ACS on"
when
TemperatureSensor( celsiusGrade > 25 )
then
acs.turnOn();
end
rule "turn ACS off"
when
TemperatureSensor( celciusGrade < 20 )
then
acs.turnOff();
end
The above are very simple rules, but they demonstrate the concept. A more elaborate rule could be able to work with different threasholds according to the weather input given by another sensor:
rule "turn ACS on"
when
WeatherSensor( $weather : weather )
TemperatureRange( weather == $weather, $max : maxTemperature )
TemperatureSensor( celciusGrade > $max )
then
acs.turnOn();
end
Having your constraints also modeled as facts (the TemperatureRange fact in the above example) also helps writing more robust rules, avoids the need for hardcoded values, and allows a single rule to react to several different conditions.
Well, we saw the "rules side" of your problem, but what your application needs to do? The answer is: simply keeps updating the sensors. That may be done in several ways, depending on how your specific system works, but in the end, it will usually be either by poll or by event. If you are polling your ACS, code would be like:
while( /* an exit condition */ ) {
sensorReader.update( sensor );
wm.modify(sensorHandle, sensor );
wm.fireAllRules();
// wait for the next polling slot
}
Monitoring applications may also work with stateless working memories, but a statefull working memory usually makes more sense for cases like described above.
Time-Sensitive Applications
Time-Sensitive Applications are applications that reason over or react to time. For example: billing applications, applications that run simulations, task schedulers, etc.
Rules in such applications will be directly or indirectly dependent on time. Here, it is important to not confuse the use of time as "data" to your rule and the use of time as "metadata" to your rule. Rules engines usually offer features to handle time as a metadata, like allowing one to specify a rule effective and expiration time, among other things.
What we are talking here is about reasoning over Time. And if we are reasoning over Time, it is clear that Time must be modeled as a fact. Quoting Neil Goldman:
" You do not want your rule conditions reading the operating system clock
as part of their match criteria, because you have very little control
over just when those criteria are evaluated. That clock is changing at a
fairly high frequency, and the rule engine is unaware of the sensitivity
of such criteria to these changes.
You want your rules to be sensitive to a pseudo-clock that
a) you can update in a controlled manner, and
b) the rule engine can be told about. "
Yes, you want a controlled clock to reason over. A good analogy is to think about the clock as a sensor from the previous examples. In the previous examples, the application controls when and how the sensor is updated as a way to keep the system state consistent. The same way the application can control when and how the rules pseudo-clock gets updated.
There are several advantages of this approach. We can list, among others:
- system consistency: you can guarantee that no external, unpredicted or predicted event will cause inconsistencies in your facts (all sysadmins know the headaches of going back one hour in time when daylight savings period is over) and having controls inside your application to handle such situations is great.
- test automation: if your rules are reasoning over a pseudo-clock, you can easily write test that advance the clock as needed to test specific scenarios, while in production, your pseudo clock may be synched with the real clock in predefined intervals.
- ability to run simulations: so you have your Asset Management Application (considered the best of the breed) that has over 10000 rules to manage every single asset of your customer's portfolio and your multimillionaire customer comes to you and ask "What if...?". Any person used to work with simulation software knows that a basic requirement for one is a pseudo-clock. So, if your 10000 rules are written against a pseudo clock, adding simulation features to your application using the exact same 10000 rules is just a matter of manipulating the clock... on the other side, if your rules use a real clock, you either update your 10000 rules to support simulation or you manipulate the clock of your server... :)
It is important to note that, as mentioned above, there is no restriction to periodically synchronize your pseudo-clock with the system clock, as long as you have the control over when and how this update is done.
So, as promised, just to keep the post a bit more low level, a code example. Imagine you have a task scheduler application and you want to write rules that will run your tasks when the scheduled time arrives or when a previous required task is complete. Your rules could be like:
rule "Fire based on time"
when
PseudoClock( $currentTime : currentTime )
$t : Task( preReq == Task.PR_TIME, executedAlready == false, scheduledTime <= currentTime )
then
$t.execute();
end
rule "Fire based on task"
when
$pr : Task( firedAlready == true )
$t : Task( preReq == Task.PR_TASK, executedAlready == false, preReqTask == $pr )
then
$t.execute();
end
You probably noticed that the comparison between the scheduled time and the current time in the first rule above is made with a "<=" operator. This is needed because usually, there is no guarantee that your rule will be executed precisely at the given time (except when running in a real time platform), but "as soon as possible" after that time. So, it is a good practice to always constrain time using ranges or thresholds instead of absolute values.
Thank you to Neil for raising the topic. The above is just the tip of the iceberg, but I hope it is a start for deeper research for those interested.
Regards
Edson
comments
Thursday, March 15, 2007
Writing better rules (Edson Tirelli)
- What am I doing wrong? I have so much procedural code in my rules...
- How do I call a specific rule? (that comes from 2 types of people: those used to backward chaining and those used to procedural code)
- How do I iterate over a collection?
- How do I execute an action in the LHS?
All the above questions may expose some level of misunderstanding of declarative programming and/or lack of knowledge in forward chaining engines.
DISCLAIMER: This is not negative criticism. This is only an acknowledgement that we, "rules people", need to do more on the evangelization side. We need to make it easier for people to understand and learn the principles of declarative programming.
Having said that, we all know that writing rules is a creative job and there is no single way of doing it, in the same manner there is no single way of interpreting a song (unless it is a Mozart's composition). There is no right or wrong, but there are better and worst ways (even being a bit subjective).
So, I will try to give some tips on how to identify the symptoms that may indicate that something is not going well with your rules, and how to improve the rules making those symptoms disappear.
The Symptoms
1. Too much procedural code:
If your rules are requiring several functions to be created or your consequences are getting complex, it may be a sign that something is wrong. Rules are supposed to be a declarative description of a pattern you are looking for in your facts and the consequence is supposed to be a sequential list of actions to execute when that pattern is found.
2. Flow control structures in the consequence:
If your rules contains flow control structures in the consequence, like "if" or "switch", there is a high probability your rules are doing something in a really bad way. Loop structures like "for" and "while" may be required in some very rare cases, but they are often enough a sign of problems in the rules too.
3. Extensive use of eval():
Mark my words: "EVAL IS EVIL". Eval is a very flexible CE, but it is also a BIG problem too. To start with, your rules frequently become unreadable if you use it extensively. Also, since you can do almost anything inside an eval, the engine can not make any assumptions about it and so it is really difficult to provide any optimization. In JBoss Rules 3.0.x, there are some situations that require the use of eval(), but we worked a lot on improving expressiveness of the language and, in JBoss Rules 3.1.0-M1 and later, we reduced a lot the situations that require eval(). So, if you are using a lot of evals, you probably can do better.
How to improve the rules
1. Business Domain Model:
Probably the most important single item you need to worry about is how expressive is your Business Domain Model. A simple analogy is to compare it with a data model and an RDMS system: we all know that the performance, simplicity and efficiency of SQL queries are directly related to the quality of the data model. The same way, the performance, simplicity and efficiency of the rules is directly related to the quality of the business model. Different Rule Engines have different ways of representing your Business Model, but fundamentally, your Business Model will be an instantiation of the ontology of your business. In other words, you know what your business is, what the requirements to running it are and what you need from your information systems. So, formally describe that ontology using your rules engine "language".
JBoss Rules/Drools works natively with Java Object Oriented Models, but it also allows you to define higher level models using Decision Tables, Domain Specific Languages, or any other language you provide a driver for (see Mark's post about supporting CLIPS, for instance).
So, tips for you to create good models for applying rules:
- prefer flat models over deeply nested ones. It is always easier to write rules for flat models.
- ask yourself the questions your rules will ask and see if you can answer them through the use of your Business Model. If it is confuse, refactor your model and try again. Simple example: your rules will apply over orders, products, and the products in the orders (let's call them items). So, does your model allow you to know about orders? Does your model allow you to easily know about the items that compound your order? Does your model allow you to easily know the details of the products in each of the items of your order? As you can see, this is a very similar exercise we go through when designing data models.
- write your business rules in your natural language (English, Portuguese or whatever). Identify all objects referenced in your rules. For each object identify all referenced attributes. For each attribute, identify all constraints applied over that attribute. Now check if your business domain model contains those objects (facts) with those attributes and if it is possible to apply the constraints over those attribute types. It may appear like we are doing Object Oriented analysis here, but it is not really the same; we are talking in a higher conceptual level, since, whatever the tool you use, it will need the same information, being it OO based, template based, or even a higher level DSL.
It is obvious, but good to reinforce. Know the tool you are using and how to express knowledge using its "interface", be it a plain textual language, spreadsheets, web interfaces or whatever. Learning a tool takes time and investment, but it is worth it. You will write better and simpler rules using advanced features. A simple example: I often see people using eval() and functions to write constraints in JBoss Rules that would be much simpler, efficient and performant if written as simple pattern constraints. (remember: EVAL IS EVIL!)
3. Assert all your objects as facts:
Rule engines usually know how to handle a fairly heavy load of facts, so let the engine do its magic. If you want to reason over a nested set of objects, assert all of them (not only the top level ones) into the engine and make sure your business model models the relationship between them. After that, writing your rules will be easier and the engine will be able to optimize execution (when compared to using eval and other constructs to access deeply nested structures). Example: if you have an Order fact that contains a list of OrderItems, assert both Order and OrdemItems as facts (if you need to reason over OrderItems too, obviously).
All the above tips have exceptions, but for the general case, following the above tips will help you write better rules.
Resources
There are good books reference in the documentation page and I recommend those interested in learning more to read them. Writing good rules is usually very simple for those "initiated", but may not be so simple for those "initiating"... :) I guess it is like using a spreadsheet: once you know your way, it is an incredible powerful and flexible tool, but you may need a hand to start using it.
I will try to follow up this post with concrete examples of writing rules. Maybe it will be helpful for some of you.
comments
Monday, March 12, 2007
RuleFlow (Kris Verlaenen)
Figure 1: A ruleflow showing a simple sequence of two ruleflow-groups that makes sure that all rules responsible for validating orders are checked first before trying to process these orders.
Figure 2: A more advanced ruleflow showing parallelism, conditional evaluation of rule sets (e.g. the rule set responsible for processing orders should only be executed if no errors were found when checking the order) and looping.
The JBoss Rules IDE has also been extended to allow the creating of these ruleflows:
* A new editor for creating ruleflow files (*.rf), as shown in the next screenshot. Whenever you try to save such a ruleflow file, a corresponding model file (*.rfm) is created that contains the definition of the ruleflow. The ruleflow file (*.rf) contains the graphical information.
* A wizard for creating a new ruleflow file.
Ruleflow definitions can be added to a RuleBase almost the same way rules are added to one:
// create a package containing all the rules
final PackageBuilder builder = new PackageBuilder();
builder.addPackageFromDrl( new InputStreamReader(
getClass().getResourceAsStream( "rules.drl" ) ) );
final Package pkg = builder.getPackage();
// create a new process containing the ruleflow
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.addProcessFromFile(new InputStreamReader(
getClass().getResourceAsStream( "ruleflow.rfm" ) ) );
// add the package and the processes to the RuleBase
final RuleBase ruleBase = RuleBaseFactory.newRuleBase();
ruleBase.addPackage( pkg );
ruleBase.addProcess( processBuilder.getProcesses()[0]);
// create a new working memory for this RuleBase
final WorkingMemory workingMemory = ruleBase.newWorkingMemory();
Ruleflows can then be started by
// add elements to the working memory here, then start
// the ruleflow to start executing the first ruleset
workingMemory.startProcess(1);
More details on how to use ruleflow will be added in the near future. We are however already interested in getting some feedback about the usefulness and useability of ruleflows, and possibly some extension you might find interesting.
Kris
comments
Sunday, March 11, 2007
A standards based approach to natural language rules - SBVR (S Beaver) (Michael Neale)
From a users point of view, there are 2 main aspects to using SBVR (I am skipping the grander vision for SBVR, which goes beyond production rules, I believe, but for now, lets keep it simple, and relevant to us):
- The SBVR vocabulary
- The rules themselves - which are written according to the SBVR vocab.
SBVR comes under Model Driven Architecture (MDA), and is defined a "Computation Independent Model" (CIM) - what that means to you, is that SVBR is designed primarily for human consumption. However, it is defined in terms of formal logic, and detailed enough to generate models and rules for a specific platform. The next layer "lower down" to CIM in MDA is PIM (Platform Independent Model) - which is where things like RuleML come in. Lower again in MDA terms is the PSM (Platform Specific Model) - this is where rule engine specific languages kick in.
Anyway, enough of my flawed MDA tutorial (if you have followed along this far, well done !).What brought SBVR to my attention is the work of Maurizio De Tommasi and Pierpaolo Cira, and their practical work on a SBVR plug in for eclipse called "sbeaver" - you can read more about sbeaver here.
We are working with These guys
to have tooling and a SBVR framework to author rules, and manage vocabularies for JBoss Rules (narrowing SBVR down to production rules to start with). Although SBVR is a CIM (see above) - we think its possible as a format for capturing business rules, and actually generating executable rules directly (perhaps with some assumptions built in - for instance SBVR vocabularies are not strong on data type information that I am aware of).
The first steps are a proof of concept to generate DRL rules from SBVR.
I am also interestsed in the SBVR vocabulary definition "language" as a generic "business object model" for the rule engine to use (regardless of if SBVR is used) - still under investigation.
So, what can I do with SBVR?A picture says a thousand words, so take a look at the vocabulary editor:
This is where you define concepts about the business model, and things like synonyms, all of which help SBVR understand sentences.And the rules?
You can see content assistance is pretty critical - this is a structure language, but much more free form then templates allow, but still - smart content assistance like is being showed here is invaluable.I look forward to working together on integrating this !
Michael.
comments
Tuesday, March 06, 2007
update - Drools adds Clips parser
Monday, March 05, 2007
Drools adds Clips parser
So far it's parsing multiple rules with multiple patterns, it supports literal, bound variable, predicate and return value field constraints as well as & and | field constraint connectives. I don't yet have it working with functions, so I've just put text into the predicate and return value for now, for the purposes of testing. The next stage is to get nested Conditional Elements working including 'and', 'or', 'not' and 'exists'. The really adventerous can help make 'accumulate', 'collect' and 'from' available :)
Clips/Lisp is a fairly simple grammar so this is a great learning project for anyone that wants to learn ANTLR and how to write custom parsers for the Drools Rule Engine; so if you want to help out why not pop onto codehause IRC #drools, or subscribe to our developer mailing list, and we'll help you get started.
The Clips ANTLR grammar is here http://anonsvn.labs.jboss.com/labs/jbossrules/trunk/drools-compiler/src/main/resources/org/drools/clp/CLP.g
And for those interested here is the current unit test. This demonstrates the intermediate AST we use to build a language agnostic view of a grammar. This AST is "dumb" and we call it Descr, short for Description, this is because everything at this stage is held in a String format; little or not validation has been done, it's just a pure string based tree representing the rules, this is then pass to the PackageBuider which validates the descr tree and builds the resulting rule AST.
public void testRule() throws Exception {
RuleDescr rule = parse(
"(defrule xxx ?b <- (person (name \"yyy\"&?bf|~\"zzz\"|~=(ppp)&:(ooo)) )
?c <- (hobby (type ?bf2&~iii) (rating fivestar) )").rule();
assertEquals( "xxx", rule.getName() );
AndDescr lhs = rule.getLhs();
List lhsList = lhs.getDescrs();
assertEquals(2, lhsList.size());
// Parse the first column
ColumnDescr col = ( ColumnDescr ) lhsList.get( 0 );
assertEquals("?b", col.getIdentifier() );
assertEquals("person", col.getObjectType() );
List colList = col.getDescrs();
assertEquals(2, colList.size());
FieldConstraintDescr fieldConstraintDescr = ( FieldConstraintDescr ) colList.get( 0 );
List restrictionList = fieldConstraintDescr.getRestrictions();
assertEquals("name", fieldConstraintDescr.getFieldName() );
// @todo the 7th one has no constraint, as its a predicate, have to figure out how to handle this
assertEquals(8, restrictionList.size());
LiteralRestrictionDescr litDescr = ( LiteralRestrictionDescr ) restrictionList.get( 0 );
assertEquals("==", litDescr.getEvaluator() );
assertEquals("yyy", litDescr.getText() );
RestrictionConnectiveDescr connDescr = ( RestrictionConnectiveDescr ) restrictionList.get( 1 );
assertEquals(RestrictionConnectiveDescr.AND, connDescr.getConnective() );
VariableRestrictionDescr varDescr = ( VariableRestrictionDescr ) restrictionList.get( 2 );
assertEquals("==", varDescr.getEvaluator() );
assertEquals("?bf", varDescr.getIdentifier() );
connDescr = ( RestrictionConnectiveDescr ) restrictionList.get( 3 );
assertEquals(RestrictionConnectiveDescr.OR, connDescr.getConnective() );
litDescr = ( LiteralRestrictionDescr ) restrictionList.get( 4 );
assertEquals("!=", litDescr.getEvaluator() );
assertEquals("zzz", litDescr.getText() );
connDescr = ( RestrictionConnectiveDescr ) restrictionList.get( 5 );
assertEquals(RestrictionConnectiveDescr.OR, connDescr.getConnective() );
ReturnValueRestrictionDescr retDescr = ( ReturnValueRestrictionDescr ) restrictionList.get( 6 );
assertEquals("!=", retDescr.getEvaluator() );
assertEquals("ppp", retDescr.getText() );
PredicateDescr predicateDescr = ( PredicateDescr ) colList.get( 1 );
assertEquals( "ooo", predicateDescr.getText() );
// Parse the second column
col = ( ColumnDescr ) lhsList.get( 1 );
assertEquals("?c", col.getIdentifier() );
assertEquals("hobby", col.getObjectType() );
colList = col.getDescrs();
assertEquals(2, colList.size());
fieldConstraintDescr = ( FieldConstraintDescr ) colList.get( 0 );
restrictionList = fieldConstraintDescr.getRestrictions();
assertEquals("type", fieldConstraintDescr.getFieldName() );
varDescr = ( VariableRestrictionDescr ) restrictionList.get( 0 );
assertEquals("==", varDescr.getEvaluator() );
assertEquals("?bf2", varDescr.getIdentifier() );
connDescr = ( RestrictionConnectiveDescr ) restrictionList.get( 1 );
assertEquals(RestrictionConnectiveDescr.AND, connDescr.getConnective() );
litDescr = ( LiteralRestrictionDescr ) restrictionList.get( 2 );
assertEquals("!=", litDescr.getEvaluator() );
assertEquals("iii", litDescr.getText() );
fieldConstraintDescr = ( FieldConstraintDescr ) colList.get( 1 );
restrictionList = fieldConstraintDescr.getRestrictions();
assertEquals("rating", fieldConstraintDescr.getFieldName() );
litDescr = ( LiteralRestrictionDescr ) restrictionList.get( 0 );
assertEquals("==", litDescr.getEvaluator() );
assertEquals("fivestar", litDescr.getText() );
}
Thursday, March 01, 2007
BRMS Introduction - and progress update (Michael Neale)
So, introducing the BRMS: The BRMS has (roughly) 3 aspects:
- BRMS Administrators,
- Users of all kinds, and
- Developers/architects.
Of course, the pretty stuff you see is just the icing on the web cake, but there is plenty going on underneath, as has been covered before, so I won't bore you with it. Its important to note that the IDE (as shown previously by mark) forms a part of this (but I won't cover it here). This, coupled with recently described new features makes for a load of goodness.
The BRMS (starting from an empty state following a rough order in which things may happen):

Categories: As mentioned previously, generally you want to start with some categories created to help you find your rules. Categories are completely non technical, in the sense they can be called whatever you want, and do whatever you want, they have no bearing on how the rules work. This would normally be a BRMS Admin type of job.
Status Management: You can use status flags (any item can only have one status at a time, whereas it can have multiple categories) to manage, well, statuses (or statii???). This can effect deployment (if you want it to). The system starts with just a "Draft" status - any new changes are saved as draft status. You can add more to this list. Statuses are applied either on the individual "asset" level, or at a whole "package" level - if you do it at the package level it applies it to all the assets below it. This would normally be an Admin job.

Assets: I have mentioned "assets" - well the short version is that they are really just rules, or a decision table, or a chunk of rules, or a model, or a document - any thing that you want to treat as one controlled, versionable unit (an "atomic" unit if you like). This also includes other config and definition items needed for rules, and in future, things like processes and service definitions. We have a dream. This is a KEY POINT to keep in mind, if your mind is as warped as mine.

Packages: packages, in this sense, are a physical grouping of rules. This grouping is a bit like a folder, and would certainly make sense to developers and technical users. You can view the world through packages if you like, but as your rules and packages get more numerous, categories can be a more flexible and controlled way of finding rules
/assets.
The package view of the world looks something like the following:
From here you can browse for rules, or you can view and edit the package configuration (all the top level stuff you need to make a ruleset work). This is a fairly technical view of the world.Nice view: I thought I would share a nice picture of the desk I was at in the Sydney office, just to keep everyone interested:
(if you look carefully you will see the tip of the Sydney Opera House).
(showing browsing a list of "business rules")
wizards to create new things (packages, models, rules etc) from within the package view of the world. There are tool-tips to tell you what each one does.There are also some other wizards which can help you out with configuring the package, should you need help !
Uploading stuff: Should you need some jar's for a model, then that respective "asset type" will allow you to upload stuff (uploading automatically creates a new version of something - so you can't accidentally overwrite it):

Browsing rules: When you use the rules "tab" - you can use the previously defined categories to navigate lists of rules.
Authoring rules: This is one of the fun parts. There are many many ways to author rules, some formats are ideal for the IDE, some are good for the web, lets take a look at a few key ones:Phew, thats a lotta buttons. On the left hand side of the web view is all the boring "meta data" including lists of categories that the rule belongs to. The bottom right is the all important documentation.
Some people have asked how this is stored: well, as mentioned previously, it depends on the "asset" format itself - the repository is content agnostic in that regards (it can and does store anything, in any format). At the moment the modeller uses XML, but this will most likely be enhanced to be DRL itself, and RuleML (in the case of RuleML, many features will have to be disabled to produce RuleML compliant rules). In any case, you probably don't really care about that. So its time for another picture:
(even DSL rules are in on the action, complete with content assist, many people still like the personal tough you only get with plain text).


(this shows a frozen view of a package, exactly as you took it, back in the good olde days).
And finally, I will leave with a photo of the Really Cool foyer of the Sydney office:

OK, that's it, that should be sufficient for now.
Enjoy !
PS this is of course available in SVN, we always develop out in the open: drools-repository and drools-jbrms are the models for anyone crazy enough.
Michael.



