Chapter 3. Using Plans

Plans play a central role in Jadex, because they encapsulate the recipe for achieving some state of affair. Generally, a plan consists of two parts in Jadex. The plan body is a standard Java class that extends a predefined Jadex framework class (jadex.runtime.Plan or jadex.runtime.MobilePlan) and has at least to implement the abstract body() resp. action() method which is invoked after plan instantiation. The plan body is associated to a plan head in the ADF. This means that in the plan head several properties of the plan can be specified, e.g. the circumstances under which it is activated and its importance in relation to other plans.

In contrast to other well-known PRS-like systems, Jadex supports two styles of plans. A so called service plan is a plan that has service character in the sense that a plan instance of the plan is usually running and waits for service requests. It represents an easy way to react on service requests sequentially without the need to synchronize different plan instances for the same plan. Therefore a service plan can setup its private event waitqueue and receive events for later processing, even when it is working at the moment.

A so called PRS-style or passive plan is the normal version of a plan, as can be found in all other PRS-systems. This means that usually such a plan is only running, when it has a task to achieve. For this kind of plan the triggering events and goals must be specified in the agent definition file to let the agent know what kinds of events this plan can handle. When an agent receives an event, the BDI reasoning engine builds up the so called applicable plan list (that are all plans which can handle the current event or goal) and candidate(s) are selected and instantiated for execution. PRS-style plans are a good choice, when the parallel execution of one kind of task is needed or is at least not disturbing. For more detailed information about plans have a look in the [Jadex User Guide].

Often a plan does some action and then wants to wait until the action has been done before continuing (e.g. dispatching a subgoal, sending a message and waiting for the reply). Therefore a plan can use one of the various waitFor() methods, that come in quite different flavors. Coming back to the examples mentioned, e.g. the dispatchSubgoalAndWait(IGoal subgoal [, long timeout]) can be used to dispatch a subgoal and wait for its completion (optionally with some timeout). Similar, for sending a message and waiting for a reply the sendMessageAndWait(IMessageEvent me [, long timeout]) method can be used. For an extensive overview of all available methods, please refer to the [Jadex User Guide] or the API documentation.

3.1. Exercise B1 - Service Plans

In this exercise we will use a service plan for translating words from English to German. Create a new TranslationB1.agent.xml file by copying the TranslationA1.agent.xml file and modify all occurrences of "A1" to "B1".

Create a new file called EnglishGermanTranslationPlanB1.java responsible for a basic word translation with the following properties: 

  • Create the plan as extension to the jadex.runtime.Plan class:

    public class EnglishGermanTranslationPlanB1 extends Plan {
        // Plan attributes.
    
        public EnglishGermanTranslationPlanB1() {
            // Initialization code.
        }
    
        public void body() {
            // Plan code.
        }
    }

  • Import the needed classes:

    import java.util.*;
    import jadex.runtime.*;

  • Let the no argument constructor print out the text "Created:"+this.

  • Implement the plan's body() method as infinite loop. At the beginning of this loop the plan should wait for translation requests:

    IMessageEvent me = waitForMessageEvent("request_translation");

    Instead of performing a database query let us use a simple hashmap for the word lookup. The creation and initialization of this word table with a few word pairs can already be done in the constructor. As result the plan should print "Translating from English to German: "+eword+" - "+gword or "Sorry word is not in database: "+eword. To get the content from the request-event use me.getContent().

Add the plan to the agent by putting it into the agent definition file: 

  • Therefore, a new plans section is introduced (lines 13-20), in which all plans for the agent have to be declared. In this simple example only one plan named here "egtrans" is added (lines 14-19). In line 15 the Java expression for creating the plan body is stated. Note that it is allowed to use any Java mechanism to create the plan (e.g. one could use a static method instead of a constructor call). In the next lines the plan's waitqueue is declared to handle all message events of type "request_translation". This means that the plan has its own event waitqueue in which all matching events are dispatched, even when the plan is busy and currently waits for other events. These events are collected in its queue till it calls a suitable waitFor() matching one of the collected events. In this case this collected event is directly dispatched to the plan.

  • The plan should be started when the agent is born. For this purpose a configuration has to be declared within the ADF (lines 30-36). It is sufficient in this case to define one configuration (named "default") with an initial plan (lines 31-35). The initial plan simply references the plan for which an instance should be created (line 33).

  • Besides the introduction of the new plan we also need to make explicit what exactly a request_translation event means. For this purpose a new events section is introduced (lines 22-28). In this section the request_translation event is declared being a message event with one parameter (lines 23-27). This parameter specifies that its performative has the fixed value request. Whenever the agent receives a message it will search its declared events for the best matching event type. In this case all messages with performative request it will be treated as request_translation events.

      1 <!--
      2     Creating an initial plan.
      3     The agent has one initial plan (created when the agent is born)
      4     for translating words from English to German.
      5 -->
      6 <agent xmlns="http://jadex.sourceforge.net/jadex"
      7     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      8     xsi:schemaLocation="http://jadex.sourceforge.net/jadex
      9                         http://jadex.sourceforge.net/jadex-0.96.xsd"
     10     name="TranslationB1"
     11     package="jadex.tutorial">
     12 
     13     <plans>
     14         <plan name="egtrans">
     15             <body>new EnglishGermanTranslationPlanB1()</body>
     16             <waitqueue>
     17                 <messageevent ref="request_translation"/>
     18             </waitqueue>
     19         </plan>
     20     </plans>
     21 
     22     <events>
     23         <messageevent name="request_translation" direction="receive" type="fipa">
     24             <parameter name="performative" class="String" direction="fixed">
     25                 <value>jadex.adapter.fipa.SFipa.REQUEST</value>
     26             </parameter>
     27         </messageevent>
     28     </events>
     29 
     30     <configurations>
     31         <configuration name="default">
     32             <plans>
     33                 <initialplan ref="egtrans"/>
     34             </plans>
     35         </configuration>
     36     </configurations>
     37 
     38 </agent>

    Figure 3.1. B1 XML ADF

Start and test the agent.  Create a translation agent via the Control Center and observe the standard output, if the initial plan is created at startup. Use the Conversation Center to send a translation request to the TranslationAgent by setting the performative to request and the content to some word to translate. Observe the TranslationAgent's output on the console when it receives the request.

3.2. Exercise B2 - Passive Plans

In constrast to the last exercise we will now use a passive plan to react on translation requests. To show the difference between the two forms of plans we now modify the service plan slightly to become a passive plan. Create the files EnglishGermanTranslationPlanB2.java and TranslationB2.agent.xml by copying the files from exercise B1.

Modify the copied file TranslationPlanB2.java. 

  • Replace all occurrences of "B1" in the Plan with "B2"

  • In contrast to the initial plan, the passive plan's body method is only invoked, when an event matches the plan's trigger. So use the method getInitialEvent() to retrieve the event that caused the execution. Because we know that only certain messages activate the plan the event can directly be cast to type jadex.runtime.IMessageEvent and the content can be retrieved. The infinite loop in the body should be discarded, because for each event a new plan instance is created, which only handles a single message.

Modify the copied file TranslationB2.agent.xml. 

  • Replace all occurrences of "B1" in the ADF file with "B2"

  • Modify the plan declaration in the ADF by removing the configurations section. Additionally a passive plan needs a trigger, that specifies under what circumstances a new plan instance is created. Therefore remove the waitqueue statement and add a new statement for the plan trigger:

    <trigger>
    	<messageevent ref="request_translation"/>
    </trigger>

Start and test the agent.  Start the agent as explained in the preceding exercise. Observe that a new instance of the translation plan is created everytime an appropriate event arrives. The passive plan is instantiated and each instance processes a different message event. Many different plan instances may remain active while processing their triggers.

3.3. Exercise B3 - Plan Parameters

In this exercise we will use plan parameters to supply the plan with arguments. Plan parameters can directly be accessed from within the plan body via the getParameter("paramname") and getParameterSet("paramsetname") methods. Generally parameters can have the directions in (default), out and inout describing parameters that are used for supplying values or resp. gathering return values from the plan. Plan parameters can be supplied with fixed values via the <value> or <values> tags. More interestingly parameter values can be mapped from and to the triggers by using parameter mappings. If a plan could be activated by more than one trigger (e.g. two different messages, or a message and a goal, etc.) multiple goal mappings (one for each trigger type) have to be used to unify the plans view on its arguments.

Create the files EnglishGermanTranslationPlanB3.java and TranslationB3.agent.xml by copying the files from exercise B2. Apply the same replacements B2->B3 as in the previous exercise.

Modify the EnglishGermanTranslationPlanB3.java. 

  • Instead of using the getInitialEvent() method to retrieve the English word, we use the the statement:

    String eword = (String)getParameter("eword").getValue();

Modify the copied file TranslationB3.agent.xml to include the new plan parameter. 

  • Add the new plan parameter with a message event mapping to the ADF:

    <plan name="egtrans">
        <parameter name="eword" class="String">
            <messageeventmapping ref="request_translation.content"/>
        </parameter>
        <body>new EnglishGermanTranslationPlanB3()</body>
        <trigger>
            <messageevent ref="request_translation"/>
        </trigger>
    </plan>

Start and test the agent.  Test and verify that the agent behavior is the same as in the last exercise.

3.4. Exercise B4 - Plan Selection

In this exercise we will use plan priorities to establish a plan selection order. Create the files EnglishGermanTranslationPlanB4.java and TranslationB4.agent.xml by copying the files from exercise B2. Apply the same replacements B2->B4 as in the previous exercise.

Create a new plan file named SearchTranslationOnlineB4.java. 

  • This plan should be used when the agent cannot find the word in its (currently very small) dictionary. In this case the online search plan will try to connect to a web dictionary and report the found translations. The address of a simple English-German dictionary is http://wolfram.schneider.org/dict/dict.cgi (you may use any other dictionary for this purpose if you are not afraid of parsing the result HTML page). To issue a query against this online database you need to create a URL and read the data from there as outlined below:

    URL dict = new URL("http://wolfram.schneider.org/dict/dict.cgi?query="+eword);
    BufferedReader in = new BufferedReader(new InputStreamReader(dict.openStream()));
    String inline;
    while((inline = in.readLine())!=null) {
        if(inline.indexOf("<td>")!=-1 && inline.indexOf(eword)!=-1) {
            try {
                int start = inline.indexOf("<td>")+4;
                int end = inline.indexOf("</td", start);
                String worda = inline.substring(start, end);
                start = inline.indexOf("<td", start);
                start = inline.indexOf(">", start);
                end = inline.indexOf("</td", start);
                String wordb = inline.substring(start, end==-1? inline.length()-1: end);
                wordb = wordb.replaceAll("<b>", "");
                wordb = wordb.replaceAll("</b>", "");
                System.out.println(worda+" - "+wordb);
            }
            catch(Exception e) {
                System.out.println(inline);
            }
        }
    }
    in.close();

Modify the EnglishGermanTranslationPlanB4 having a static dictionary. 

  • Make the variable for the dictionary static and initialize it in a static block instead of in the constructor:

    static {
        wordtable = new HashMap();
        wordtable.put("coffee", "Kaffee");
        wordtable.put("milk", "Milch");
        wordtable.put("cow", "Kuh");
        wordtable.put("cat", "Katze");
        wordtable.put("dog", "Hund");
    }

  • Provide a public static method for testing if a word is contained in the dictionary:

    public static boolean containsWord(String name) {
    	return wordtable.containsKey(name);
    }

Modify the copied file TranslationB4.agent.xml to include the new plan. 

  • Add the new online search plan to the plan declarations using a low priority:

    <plan name="searchonline" priority="-1">
        <body>new SearchTranslationOnlineB4()</body>
        <trigger>
            <messageevent ref="request_translation"/>
        </trigger>
    </plan>

  • Modify the applicability of the translation plan by introducing a precondition

    <plan name="egtrans">
        <body>new EnglishGermanTranslationPlanB4()</body>
        <trigger>
            <messageevent ref="request_translation"/>
        </trigger>
        <precondition>
            EnglishGermanTranslationPlanB4.containsWord((String)$event.getContent())
        </precondition>
    </plan>

Start and test the agent.  When the agent receives translation request it searches applicable plans to handle this request. If the word is contained in the dictionary both plans are applicable and the one with the higher priority is chosen (in this case it is the egtrans plan because the standard priority is 0). When the word is not contained in the dictionary only the searchonline plan is applicable and will be used.

3.5. Exercise B5 - BDI Debugger

Using the Jadex introspector tool agent to control the execution of an agent.

  • Prepare the agent debugging by setting the debugging flag in a new properties section (at the end of the file) of the ADF to true, e.g. in the B4 ADF. Therefore the agent will be started in step mode and will only process events when the execution is manually requested in the introspector tool.

    <properties>
        <property name="debugging">true</property>
    </properties>

    Note that you can also freeze the execution of the translation agent by setting execution mode to "step" in the tool. Using the debug flag is preferable when the agent directly starts with executing some actions and you want to observe it right from the start.

  • Start the translation agent of the last exercise from the Control Center.

  • Switch to the introspector perspective in the Control Center open an introspector for the translation agent and activate debugging by choosing the debugger tab and clicking the start button.

  • Use the Conversation Center to send some translation requests to the translation agent (as in B4).

  • Press the "step" button several times in the dispatcher and observe how an action from the agenda is executed. If the mode is "cycle" instead of step process event actions are executed in one step. Otherwise actions for all intermediate steps - searching applicable plans, selecting candidates from this list and scheduling the candidates for execution - are generated.

The Jadex debugging perspective is conceived to support you in the debugging of agents and helps you to understand what happens inside an agent, e.g. you could use it for the agent from excercise B1 too to grasp the differences between B1 and B2.

3.6. Exercise B6 - Log-Outputs

In this exercise we will use log-outputs instead of printing console outputs. Create the files EnglishGermanTranslationPlanB6.java and TranslationB6.agent.xml by copying the files from exercise B2.

Modify the copied file TranslationPlanB6.java. 

  • Replace all occurrences of System.out.println(..) to getLogger().info(..).

Modify the copied file TranslationB3.agent.xml. 

  • Add an imports section and the import statement for the java.logging classes to the imports section.

    <imports>
        <import>java.util.logging.*</import>
    </imports>

  • Introduce a properties section at the bottom of the ADF to specify the logging behavior. Insert the following code:

    <properties>
        <property name="logging.level">Level.INFO</property>
        <property name="logging.useParentHandlers">true</property>
    </properties>

    These properties can be used to control the agent logging. The log-level decides what kind of log-outputs shall be considered for logging, according to the java.util.logging level hierarchy. Increasing the level value, e.g. to warning means, that only log-outputs at this or a higher level are conisdered by the logger. The useParentHandlers property can be used to turn on or off the standard console logging handler (per default it is set to true).

Start and test the agent.  Start the translation agent. Send a translation request to the translation agent and watch the console and logger output. To turn off the console output simply set the property useParentHandlers in the ADF to false.