Chapter 9. Plans

Plans represent the agent's means to act in its environment. Therefore, the plans predefined by the developer compose the library of (more or less complex) actions the agent can perform. Depending on the current situation, plans are selected in response to occuring events or goals. The selection of plans is done automatically by the system and represents one main aspect of a BDI infrastructure. In Jadex, plans consist of two parts: A plan head and a corresponding plan body. The plan head is declared the the ADF whereas the plan body is realized in a concrete Java class. Therfore the plan head defines the circumstances under which the plan body is instantiated and executed.

9.1. Defining Plan Heads in the ADF

The Jadex plans XML schema part

Figure 9.1. The Jadex plans XML schema part

In Figure 9.1, “The Jadex plans XML schema part” the XML schema part for the plans section is shown. Inside the <plans> tag an arbitrary number of plan heads denoted by the <plan> tag can be declared. For each plan head several attributes (as shown in Table Table 9.1, “Important attributes of the plan and the body tag”) and contained elements can be defined. For each plan a name has to be provided. The priority of a plan describes its preference in comparison to other plans. Therefore it is used to determine which candidate plan will be chosen for a certain event occurence, favouring higher priority plans (random selection, if activated, applies only to plans of equal priority). Per default all applicable plans have a default priority of 0 and are selected in order of appearance (or randomly when the corresponding BDI flag is set).

Table 9.1. Important attributes of the plan and the body tag

TagAttributeRequiredDefaultPossible Values
plannameyes  
planpriorityno0any positive or negative integer
bodytypenostandard{standard, mobile}

For each plan the corresponding plan body has to be declared using the <body> element. Within this element a Java expression has to be provided for the creation of the plan body (in most cases a simple constructor call like new PingPlan() is used). The type attribute determines which kind of plan body is used. Currently, the options are standard vs. mobile plan bodies as further described in Section 9.2, “Implementing a Plan Body in Java”. To clarify things, a simple example ADF is given below that shows the declaration of a plan reacting on a ping message.

<agent ...>
    ...
    <plans>
        <plan name="ping">
            <body>new PingPlan()</body>
            <trigger>
                <messageevent ref="query_ping"/>
            </trigger>
        </plan>
    </plans>
    ...
    <events>
        <messageevent name="query_ping" type="fipa">
            ...
        </messageevent>
    </events>
    ...
</agent>

Figure 9.2. A plan reacting on a ping message

9.1.1. Plan Triggers

To indicate in what cases a plan is applicable and a new plan instance shall be created the <trigger> tag can be used (see Figure 9.3, “The Jadex plan trigger XML schema part”). Its subtags specify the internal-, message, or goal events for which the plan is applicable. These events or goals can be further restricted, by requiring certain parameter values. When parameter specifications are included in the trigger tag, the plan will only be selected for those goals or events matching all parameter specifications. Restrictions of parameter set values are currently not supported. For backwards compatibility to older Jadex versions, additionally, a filter instance can be used, although its use is discouraged, because of the lack of declarativeness and readability.

The Jadex plan trigger XML schema part

Figure 9.3. The Jadex plan trigger XML schema part

In addition to the reaction on certain event or goal types, it is also possible to define data-driven plan execution by using the <condition> tag. A trigger condition can consist of arbitrary boolean Jadex expressions, which may refer to certain beliefs when their states needs to be supervised. If only some specific belief needs to be monitored the <beliefchange> tag can be used. In this respect a belief change is reported whenever the belief's new fact value is different from the value held before. Similarly, belief sets can be monitored with the <beliefsetchange> tag, or more specifically for addition or removal of facts by using the tags <factadded> and <factremoved> respectively.

9.1.2. Defining Plan Applicability with Pre- and Context Conditions

To find out if the plan is applicable not only with respect to the current event or belief change but also considering the current situation, the pre- and context conditions can be used. The precondition is evaluated before a plan is instantiated and when it is not fulfilled this plan is excluded from the list of applicable plans. In contrast, the context condition is evaluated during the execution of a plan and whenever it is violated the plan execution is aborted and the plan has failed. Both conditions can be specified in the corresponding tags supplying some boolean Jadex expression. The following example shows how to execute a "repair" plan whenever the belief "out_of_order" becomes true, and as long as the agent believes to be repairable.

<plans>
    <plan name="repair">
        <body> new RepairPlan() </body>
        <trigger>
            <condition> $beliefbase.out_of_order </condition>
        </trigger>
        <contextcondition> $beliefbase.repairable </contextcondition>
    </plan>
</plans>

Figure 9.4. Example of a plan with context condition

9.1.3. Waitqueue

When an event occurs, and triggers an execution step of a plan, it may take a while, before the plan step is actually executed, due to many plans being executed concurrently inside an agent. Therefore, it is sometimes possible, that a subsequent event, which might be relevant for a plan, is not dispatched to that plan, because it is still executing a previous plan step, and does not yet wait for the event. To avoid this, each plan has a waitqueue to collect such events. The waitqueue for a plan is set up using the <waitqueue> tag or the getWaitqueue() method in plan bodies. The waitqueue of a plan is always matched against events, although the plan may not currently wait for that specific event. The <waitqueue> tag provides the same event and goal options as the <trigger> tag described above, but does not support the <condition> and the belief(set) change tags. Events that match against the waitqueue of a plan are added to the plans internal waitqueue. They will be dispatched to the plan later, when it calls waitFor() or getWaitqueue().getEvents() with optionally a matching filter that restricts the returned events. You may have a look at the jadex.runtime.IWaitqueue interface for more details.

9.1.4. Parameters, Binding, and Parameter Mapping

Similar to goals, plans may have parameters and parameter sets, which can store local values, required or produced during the execution of the plan. Plan parameters can be accessed from plan bodies for read and write access depending on the parameter direction attribute: in parameters (cf. Section 8.1, “Common Goal Features”) allow only read access, out parameters can only be written, while inout parameters allow both kinds of access. Default values for any of these parameters and parameter sets can be provided in the ADF. Just like facts for belief sets, initial values for parameter sets can be either specified as a sequence of <value> tags, or as a single <values> tag. The parameter(set)s of a plan can also be accessed from the body tag or the context condition, by referencing the plan via the reserved variable $plan concatenated with the parameter(set) name, e.g. $plan.result. The precondition and the trigger condition are evaluated before the plan is instantiated, therefore from these conditions no parameters and parameter sets can be accessed.

The Jadex plan parameters XML schema part

Figure 9.5. The Jadex plan parameters XML schema part

For (single valued) parameters it is possible to use binding options instead of an initial value. A binding option is an expression, that will be evaluated to a collection of values (supported are arrays or an object implementing Iterator, Enumeration, Collection, or Map). The binding options of a parameter therefore represent a set of possible initial values for that parameter. The cartesian product [1] of all binding parameters (if there is more than one parameter with binding otpions) determines the number of candidate plans that is considered in the event dispatching process. Please note that the calculation of the cartesian product can easily lead to large numbers of applicable plans so that binding options should always be used with care. For example Figure 9.6, “ Example binding parameter (from the puzzle example) Example binding parameter shows a plan from the “puzzle” example, where for each possible move a plan instance is created. In addition to accessing the binding values like other parameters by writing $plan.paramname, it is also possible to access the binding value directly via its name via paramname. This allows binding values also to be considered for evaluating the pre- and trigger condition, before the plan instance is created.

<plan name="move_plan">
    <parameter name="move" class="Move">
        <bindingoptions>$beliefbase.board.getPossibleMoves()</bindingoptions>
    </parameter>
    ...
</plan>

Figure 9.6.  Example binding parameter (from the puzzle example) Example binding parameter

A common use case for plan parameter(set)s is to capture parameter(set)s from a goal or event that triggered the plan. To make this relationship between event and plan parameters explicit, the <internaleventmapping>, <messageeventmapping>, and <goalmapping> tags can be used. A mapping definition contains a ref attribute denoting the event or goal parameter to be mapped. The reference is given in the form type.param, where type is the name of the goal or event, and param is the name of the goal or event parameter. When a plan parameter is mapped, the parameter properties like class and direction are ignored, as the values from the mapped parameter are used. Depending on the direction of the parameter, the default values of the plan parameter are automatically assigned from the event or goal (direction in, inout), and/or written back to the goal or event (direction out, inout), when the plan has finished. Note that when a plan reacts to more than one goal or event, you cannot just provide a mapping for one of these events. If you want to use a mapping for a parameter, you have to provide mappings for all events or goals handled by the plan.

9.2. Implementing a Plan Body in Java

A plan body represents a part of the agent's functionality and encapsulates a recipe of actions. In Jadex, plan bodies are written in pure Java and therfore it is easily possible to write plans that access any available Java libraries, and to develop plans in your favourite Java Integrated Development Environment (IDE). The connection between a plan body and a plan head is established in the plan head, thus plan bodies can be reused within different plan declarations. For developing reusable plans, plan parameters in combination with parameter mappings from some triggering event or goal to/from the plan should be used.

As mentioned earlier, currently two types of plan bodies are supported in Jadex, which are both implemented as conventional Java classes. The standard plans inherit from jadex.runtime.Plan. The mobile plans inherit from jadex.runtime.MobilePlan and allow agents to be migrating, even while plans are executing (e.g., supported by the JADE platform). The code of standard plans is placed in the body() method, while the code of mobile plans goes into the action(IEvent) method.

Plans that are ready to run are executed by the main interpreter (cf. Section 3.3, “Execution Model of a Jadex Agent”). The system takes care that only one plan step is running at a time. The length of a plan step depends on the plan itself. For mobile plans the action() method is always executed as a whole, for the first and again for all subsequent steps. The body() method of standard plans is called only once for the first step, and runs until the plan explicitly ends its step by calling one of the waitFor() methods, or the execution of the plan triggers a condition (e.g., by changing belief values). For subsequent steps the body() method is continued, where the plan was interrupted.

The API of both plan types is very similar (both inherit from the same super class jadex.runtime.AbstractPlan), the only difference regards the waiting for events. Both plans provide several variations of the waitFor...() method, but only the standard plan will block when it is called. The different execution style is shown in a code example implementing the initiator side of a FIPA-request protocol. Remember, the body() method of a standard plan is called only once, while the action() method of a mobile plan is called for each event. The standard plan can use nested if-then-else blocks to naturally handle all cases of the protocol, while the mobile plan has no state information of previous messages, and has to handle all events at the top level of the action() method in one large if-then-else statement.

Standard PlanMobile Plan
public void body() {
    boolean agreed, informed;
    
    // Send request.
    ...
    
    // Wait for agree/refuse.
    IEvent e1 = waitFor(...);
    ...
    
    // Wait for inform/failure.
    if(agreed) {
        IEvent e2 = waitFor(...);
        ...
        if(informed) {
            ...
        }
        else {
            ...
        }
    }
    else {
        ...
    }
}
public void action(IEvent e) {
    boolean agreed, refused;
    boolean informed, failed;
    ...
    if(initial_event) {
        // Send request.
        ...
        // Wait for agree/refuse.
        waitFor(...);
    }
    else if(agreed) {
        ...
        // Wait for inform/failure.
        waitFor(...);
    }
    else if(refused) {
        ...
    }
    else if(informed) {
        ...
    }
    else if(failed) {
        ...
    }
}

Figure 9.7. Programming style of the different plan types

9.2.1. Plan Success or Failure and BDI Exceptions

If a plan completes without producing an exception it is considered as succeeded. Completion means for standard plans that the body() method returns. For mobile plans it means that the action() method returns and the plan does not wait for any more events. To perform cleanup after the plan has finished, you can override the passed(), failed(), and aborted() methods, which are called when the plan succeeds (runs through without exception), fails (e.g., due to an exception), or was aborted during execution (e.g., because the root goal was dropped or has been achieved before the plan reached its end). In Figure 9.8, “Standard plan skeleton” a plan skeleton of a standard Jadex plan is depicted including all predefined methods. The cleanup methods are also available in mobile plans. In the failed() method, a plan may call the getException() method to see which problem occured. To find out whether the plan was aborted, because its root goal was achieved, you can call the isAbortedOnSuccess() method inside the aborted() method.

public class MyPlan extends Plan {

    public void body() {
        // Application code goes here.
        ...
    }

    public void passed() {
        // Clean-up code for plan success.
        ...
    }

    public void failed() {
        // Clean-up code for plan failure.
        ...
        getException().printStackTrace();
    }

    public void aborted() {
        // Clean-up code for an aborted plan.
        ...
        System.out.println("Goal achieved? "+isAbortedOnSuccess());
    }
}

Figure 9.8. Standard plan skeleton

Regardless if standard or mobile plans are used, a plan is considered as failed if it produces an exception. To aid debugging, occurring exceptions are (by default) printed on the console (logging level SEVERE), although the agent continues to execute. Subclasses of jadex.runtime.BDIFailureException are not printed, because they are produced by the system and indicate "normal" plan failure. If you want your plan explicitly to fail without printing an exception, you can throw a PlanFailureException or, as a shortcut, call the fail() method. Other subclasses of the BDIFailureException are generated automatically by the system, to denote certain failures during plan execution. All of these exceptions can be explicitly handled if desired, or just ignored (causing the plan to fail). The GoalFailureException, already introduced in Section 8.6, “Creating and Dispatching New Goals”, is thrown, when a subgoal of a plan could not be reached or if the subgoal could not be adopted due to its uniqueness settings (i.e. there exists already a goal that is considered equal to the new one). The MessageFailureException indicates that a message could not be sent, e.g., because the receiver is unknown. A TimeoutException occurs when calling waitFor() with a timout, and the awaited event does not happen. Finally, the AgentDeathException is thrown when an operation could not be performed, because the agent has died. This usually does not occur inside plans, but only when accessing an agent from external processes (see Chapter 16, External Processes).

9.2.2. Atomic Blocks

For mobile plans, each call to the action() method is executed as an atomic block, i.e., the agent will not do other things until the action() method returns. Standard plans on the other hand, might be interrupted whenever the agent regards it as necessary, e.g., when a belief has been changed leading to the adoption of a new goal. Sometimes it is desireable that a sequence of actions is considered as a single atomic action. For example when you change multiple beliefs at once, which might trigger some conditions, you may want to perform all changes before the conditions are evaluated. In standard plans, this can be achieved by using a pair of startAtomic() / endAtomic() calls around the code you want to execute as a whole. Note that you are not allowed to end the plan step inside an atomic block (e.g., by calling waitFor()).

public void body() {
    ...
    startAtomic();    
    // Atomic code goes here.
    ...    
    endAtomic();
    ...
}

Figure 9.9. How to establish an atomic block



[1] In mathematics, the Cartesian product (or direct product) of two sets X and Y, denoted X x Y, is the set of all possible ordered pairs whose first component is a member of X and whose second component is a member of Y. Example: The cartesian product of {1,2}x{3,4} is {(1,3),(1,4),(2,3),(2,4)}. (cf. Wikipedia)