AFAPL2 Programmers Guide

From Agent Factory

Jump to: navigation, search

Contents

Introduction

This guide introduces the Agent Factory Agent Programming Language Version 2.0 (or AFAPL2 for short), a variant of the pre-existing AFAPL programming language that combines:

  • the basic reasoning mechanisms of AFAPL with various extended plan operators that were introduced in the ALPHA variant, together with
  • the inclusion of Roles as a run-time construct.

Some papers describing the AFAPL2 language can be found on the Publications page of this website. In this guide, we focus on a practical introduction to the language.

Requirements for Running Examples

To complete this guide, it is recommended that you download, install and use the latest version of the AF Netbeans Module (we recommend using version 6.0.1alpha2 or later) from Sourceforge. In fact, all of the examples in this guide assume the use of this IDE.

Note: If you are using an older version of Netbeans and wish to use one of the older modules, you will need to download the following packages and replace the default libraries that are referenced when a new Agent Factory project is created:

  • Agent Factory Runtime Environment (AF-RTE), version > 1.1.0
  • AFAPL2 Development Kit (AFAPL2-DK), version > 1.0.0
  • Local Message Transport Service (Local-MTS), version > 1.0.0

Also, for any distributed platform examples, you will also need to download the following packages:

  • FIPA HTTP Message Transport Service (HTTP-MTS), version > 1.0.0

Running Examples from the Command Line

It is possible to use a number of command line tools to compile and deploy AFAPL2 programs, details of how to do this can be found in the following examples, which were originally part of this document:

Note: the above examples relate to a prior version of Agent Factory and, as such, some of the code may no longer compile (due to changed package structures). They are provided only for illustrative purposes, and you should use them in tandem with this updated guide.

Editorial History

???: Version 1 - Previously this guide underwent a significant number of edits.

05/04/2008: Version 2 - Introduced section on Persistence of Beliefs

08/04/2008: Version 3 - Persistence of Beliefs subsumed within larger "Advanced Belief Operators" section that also includes the AFAPL2 comparison operators.

11/04/2008: Version 4 - Modification of "Generation of Beliefs" section to introduce new PERCEPTOR construct format; addition of brief description of how to configure actuators, perceptors, and modules; introduction to ontologies.

A Brief Overview of AFAPL2

AFAPL2 supports the implementation of intentional agents. Intentional agents are a class of agent that reasons about what to do through a set of mental models such as beliefs, goals, obligations, commitments, and intentions. As is illustrated in figure 1 below, AFAPL2 agents maintain a mental state that consists of three parts: beliefs, goals, and commitments.

Figure 1: Schematic of the key components of an AFAPL2 agent together with the main interactions between those components
Figure 1: Schematic of the key components of an AFAPL2 agent together with the main interactions between those components

The beliefs component contains a model of the agents' environment. For example, if an agent was controlling a robot that is playing soccer, then the belief model may consist of information about the current location of the ball, the robot, and the robots team-mates. The model may also include information about the current score, and the current task that the robot has been assigned to (e.g. defending or attacking). Finally, the model may also contain “internal state information” such as its battery level.

In AFAPL2 beliefs take the form of a first-order structure that is wrapped within a BELIEF operator. For example, BELIEF (likes(Rem, Beer)), defines the belief that Rem likes Beer.

The goals component models the future activities of the agent. That is, it determines a partial idealised future belief state that the agent must try to bring about. In this quick start guide, we will not discuss goals in detail. Instead, we will discuss them in later guides.

Finally, the commitments component models the current activities of the agent. That is, they describe what activities the agent is currently carrying out and are the mental equivalent of a contract. A commitment identifies some activity that the agent has decided/agreed to perform, a time at which the activity is to be performed, the name of an agent to whom the commitment was made, and a set of conditions under which the commitment must be maintained. For example, our robot soccer agent may include a commitment to kick the football. This commitment would be made to itself and should be realised immediately (i.e. now). Finally, the commitment is to be maintained only so long as the agent believes that it is able to kick the ball (i.e. this means that the agent will stop trying to kick the ball if it is tackled or if it looses control of the ball).

AFAPL2 agents function through the manipulation of their mental state by a purpose-built interpreter. The algorithm underling this interpreter is quite complex, but basically, as is shown below, involves 4 steps: belief update, role management, goal management, and commitment management. These steps are performed repeatedly, in the specified order, until the agent is terminated.

Figure 2: Overview of the AFAPL2 Interpreter Cycle
Figure 2: Overview of the AFAPL2 Interpreter Cycle

Further details of this algorithm can be found in relevant papers that are available from this website. Instead, this guide will focus on the practicalities of building agents using the AFAPL2 language.

Generating Beliefs About the Environment

The first step in understanding how to program AFAPL2 agents is to understand how beliefs are used to construct models of the current state of the environment. Without this model, the agents will not be aware of what is happening in the environment, and consequently, will not be able to act in a meaningful way.

The key step underpinning the generation of an agents' belief model is perception. This is the process by which an agent converts raw environment data (sensor readings, ACL messages, address books, etc.) into various beliefs that provide a higher-level representation of this data (and consequently, the state of the environment). As is shown in figure 2, perception is an integral part of the belief update phase of the AFAPL2 interpreter cycle.

The principle building block of the perception process is the perceptor unit. This is a Java class that collates any relevant raw data and generates a corresponding set of beliefs. Perceptors are associated with agents via the PERCEPTOR construct. This construct generates a mapping between specific perceptor units and a given AFAPL2 agent program. For example, when developing a robot soccer agent, a ball perceptor unit may be created that uses visual information to decide whether or not the robot has the ball. In the AFAPL2 program, this perceptor would be declared through the following statement:

 PERCEPTOR BallWatcher {
     CLASS perceptor.BallPerceptor;
 }

Where perceptor.BallPerceptor is the Java class that implements the perceptor unit. This unit would then be responsible for generating a corresponding belief about the presence of the ball ( e.g. BELIEF(hasBall) or BELIEF(noBall) )

NOTE: Prior to version 1.2.0 perceptors were declared using a simpler format, namely: PERCEPTOR <perceptor-class>; This has been changed in order to facilitate the introduction of a number of more advanced features for perceptors (including configuration, and ontology associations).

A key feature of AFAPL2 that differentiates it from other AOP languages is the way in which it handles beliefs. AFAPL2 has been designed to support the fabrication of agents that exist within highly dynamic environments. As such, agents may adopt beliefs that quickly become invalid. For example, consider a robot soccer agent that has a perceptor which generates a belief about whether the agent can see the ball or not. If the ball passes quickly in front of the agent, then it may see the ball only for one or two iterations of the interpreter cycle.

Rather than implement a complex belief revision algorithm that tries to understand when a belief has become invalid, the approach adopted in AFAPL2 is to assume that, by default, all beliefs become invalid at the end of a given iteration of the AFAPL2 interpreter cycle. In this way, perception becomes the process by which the agent generates a snapshot of the current state of the environment. This snapshot is then thrown away immediately before the next snapshot is generated.

While this approach helps to simplify the maintenance of an agents beliefs, it is not always appropriate (sometimes we need beliefs that persist for longer). To handle this requirement, AFAPL2 also provides a number of temporal operators that can be used to define beliefs that persist for more than one iteration. However, we leave discussion of temporal beliefs to later in this guide.

Example 1: Implementing a Perceptor

This first example illustrates how to create your first AFAPL2 agent. this agent will containa single perceptor, which will generate a single belief, representing the fact that the agent is “alive”. Later in this guide, we will explore some more advanced features of AFAPL2 that allow beliefs to persist over multiple iterations of the interpreter cycle.

Implementing the Agent

To implement a perceptor, we start by creating a Java class that is a subclass of the com.agentfactory.logic.agent.Perceptor class, and implementing the perceive() method:

import com.agentfactory.logic.agent.Perceptor;

public class AlivePerceptor extends Perceptor {
   public void perceive() {
       adoptBelief( "BELIEF(alive)" );
   }
}

The above perceptor generates a single belief that represents the fact that the agent is "alive". This belief is added to the agent's belief set at the start of each iteration of the interpreter cycle.

To create our first agent, we must first create a new Agent Factory Project in Netbeans. You can do this by selecting the File Menu, then "New Project". Once the project wizard is open, select the "Agent Factory" category and choose the "Agent Factory Application" as is illustrated below:

Click the next button, and give the project the name "Alive" as is shown below:

Before clicking on the finish button, notice that the "Create Main Platform Script" and "Create Main Platform Configuration File" options have been selected. Selecting these options causes two key files to be created:

  • Platform Configuration File (.cfg): used to specify a configuration for an agent platform
  • Agent Platform Script (.aps): used to specify an initial agent community for an agent platform

These files are necessary to deploy an agent system. We will discuss then in more detail later in this example.

Now, to implement the above perceptor, you simply expand the Alive Project Folder and the "Source Packages" folder, and then right-click on the alive folder and select New->Empty Perceptor File. Enter the name "AlivePerceptor" as is shown below:

Next, click the finish button and copy the code from the above example into the perceive() method, and compile normally (pressing F9 should do the trick), and you have now compiled your first AFAPL2 Perceptor!

The next part of this example is concerned with creating our first AFAPL2 agent, which will be stored in a file named "alive.afapl2" To create this file, right click on the aliver folder within the project "Source Packages" folder. Select New->Empty AFAPL2 File, and enter the correct filename ("alive.afapl2"). Now copy the following line of code into the empty file:

PERCEPTOR alive {
    CLASS AlivePerceptor;
}

This file is compiled in exactly the same way as the Java perceptor code was compiled - support for compiling AFAPL2 code is integrated into the Netbeans IDE via the module. Simply compile the code as normal (for example, press F9 or right click over the file in the Projects Navigator and select "Compile"). Whichever way you compile the file, afterwards, Netbeans should look something like this:

The purpose of the compilation step is to check that the syntax and (some) of the semantics of your program is correct. In this case, we see that we get an error stating that we cannot find the AlivePerceptor perceptor. This is because the perceptor is in the alive package, so update your code as follows, and recompile:

PERCEPTOR alive {
    CLASS alive.AlivePerceptor;
}

Now, the compiler finds no errors, and creates a ".agent" file. This file is also a text file (just like the ".afapl2" file). If you open it, you will see that it basically contains the same code as the ".afapl2" file. This will change later on, when you use some of the more advanced features of AFAPL2.

Configuring the Platform

To deploy an instance of our newly created agent program, we need to set up a configuration for the agent platform, and specify an initial agent community. As was mentioned earlier, this is done via: the platform configuration file (a file with a ".cfg" extension) and the agent platform script file (a file with a ".aps" extension). When we created the Alive project, I highlighted a screen in which we checked two boxes. By checking these boxes, blank versions of the these two files were created and they are located in the alive package.

First, we will modify the platform configuration file. This file allows you to configure each agent platform in a number of ways. It supports the definition of: platform services and their associated access rights, agent architectures / interpreters, and graphical interfaces. Lets examine the sample configuration file below:

 SERVICE fipa.std.service.ams com.agentfactory.service.ams.AgentManagementService
 
 PLATFORM_GUI com.agentfactory.debugger.Debugger
 
 AGENT_INTERPRETER com.agentfactory.afapl2.interpreter.Agent agent

In the above code, the first line declares a platform service, known as the Agent Management Service, which is required for all agent platforms, and provides support for creating, suspending, resuming, and terminating agents. The second line declares that we will use the Agent Factory debugger, and the third line declares that we will use the AFAPL2 Interpreter (mapped on to files with a .agent extension). Of these three lines, the first line is actually optional, and may be removed. This is because, the Agent Management Service is loaded by default by the platform if it is not specified explicitly in the platform configuration file. For this example, simply copy the above three lines into the default.cfg file.

Next, lets discuss how to specify the initial agent community. The second file you need to modify is the agent platform script file. This file is used to specify the initial set of agents that will be created when the agent platform is started. Again, a blank agent platform script was created when we created the project. This file is called default.aps and is located in the alive package. For the purposes of our example, this script should contain the following:

CREATE_AGENT myfirstagent alive/alive.agent

Informally, the above script creates an agent called "myfirstagent" based on the alive/alive.agent AFAPL2 agent design.

Now we are ready to run our first agent!

Running the example

To run this example, simple run the project. You can do this in the same way as you run any Netbeans project (e.g. click the run button on the button bar), or you can highlight the default.aps and default.cfg files, right-click, and select execute. For the former approach, the default aps and cfg file is selected (this is specified in the project properties), while for the latter approach, the highlighted aps and cfg files are used.

Whichever approach you use, the AF Debugger GUI should appear, with the agent list containing a single agent, "myfirstagent". Double click on this agent to open the agent inspector, and select the beliefs tab. The debugger should now look something like this:

To run your first agent, simply click the "Start" button. This button looks like a start button that would normally appear on a media player. You can use either the one at the top (but you must select the agent in the list on the left-hand side before doing this), or the one in the AFAPL2 inspector). This should result in the belief BELIEF(alive) appearing in the belief view of the AFAPL2 inspector.

Defining and Performing Actions

So, now we know how to generate beliefs about the current state of the environment. The above example illustrates this though a simple AFAPL2 agent which has a single perceptor that generates a belief that the agent is "alive". Unfortunately, for many agent-oriented applications, this is not enough - the agent needs to be able to control its environment. To do this, we introduce two additonal, but related, concepts: actions and actuators.

An action is something that the agent is able to do directly. That is, actions are the primitive, non-divisible capabilities of an agent. Examples of actions include: sending a message, kicking a ball, or indexing a document. In AFAPL2, primitive capabilities are specified in two parts: there is the high-level description of the action (which we call an action), and then there is the corresponding Java code that implements that action (which we call an actuator).

However, being able to define actions in AFAPL2 is only part of the story. An action describes what an agent can do, but not when it should do it. To define when an agent should perform an action, we must introduce another AFAPL2 concept, namely commitments.

As stated earlier in this guide, for an AFAPL2 agent, a commitment is the mental equivalent of a contract. It defines some activity (in this case an action) that the agent has decided (commited) to perform, and also describes various constraints/properties that are associated with that decision (i.e. for whom the commitment was made, when it should be considered, and under what conditions it must be maintained). In short, the commitments of an agent describe what the agent is going to do.

So, if an agent wants to perform an action, then the only way it can do so is by adopting a commitment to that action. Commitments may be adopted in one of three ways:

  • By explicitly specifying a commitment as part of the AFAPL2 program (it is added to the initial mental state of the agent)
  • Through the firing of commitment rules
  • Via the agents attempts at realising its goals

In this section, we will start by looking at the first of these ways, namely by explicitly specifying commitments that are to be added to the initial mental state of the agent. This can be achieved via the AFAPL2 COMMIT construct. Take, for example, our robot soccer agent. Once the agent starts up, its initial task will normally be to move to its starting position. Within AFAPL2, this can be achieved by getting the agent to adopt an initial commitment of the form:

 COMMIT(?self, ?now, BELIEF(true), moveTo(startingPosition));

Here, moveTo(?x) is a primitive action that moves the agent to a pre-specified position (specified by the argument ?x).

Example 2: Implementing an Action

This second example illustrates how to specify an action (and the corresponding actuator) in AFAPL2. It then shows how an agent is able to perform this action through the adoption of an initial commitment to that action. To implement an action, we must do two things: first, we need to create an actuator that contains the implementation of the action. After this, we need to specify our action in an AFAPL2 file, using the ACTION construct.

So, lets start with the actuator. Actuators are - Java classes that subclass the com.agentfactory.logic.agent.Actuator class and implement the act(..) method. Upon creation, the agent creates on instance of each specified actuator. Thus, the same instance is used even when the action is being performed concurrently. Consequently, actuators must be implemented as thread-safe classes.

To illustrate how to create an actuator, we will develop a "helloWorld" action that prints the String "Hello World" to the console. After this, we will explore a slightly more complex version of "helloWorld" that includes some more advanced features of actuator implementations.

import com.agentfactory.logic.agent.Actuator;
import com.agentfactory.logic.lang.FOS;

public class HelloWorldActuator extends Actuator { 
   public boolean act( FOS action ) {
        System.out.println( "Hello World" );
        return true; 
    }
}

What the above actuator implementation does is fairly obvious. The only "issue" is the return value of an actuator. This is used to define whether the commitment to the corresponding action failed or succeeded. It is useful where it is possible for the actuator to complete unsuccessfully, for example, updating a table in a database. In such cases, the actuator can indicate its failure by returning false instead of true.

Before we create an example AFAPL2 program to illustrate how to use this actuator, we must compile it. As with the previous example, you should start by creating a new Agent Factory project in Netbeans. This time, give it the name "HelloWorld". To create the actuator, highlight the "helloworld" package in the newly created project, right-click, and select New->Empty Actuator file. Copy the above code into the act(...) method and compile in the usual way (pressing F9 will do it).

Next, we need to create an AFAPL2 agent program, which will be stored in a file named "helloworld.afapl2". Again, you should create this file by highlighing the "hellowworld" package in the project, right-click, and select New->Empty AF-APL2 Agent Design file.

This program contains two parts: an action definition and a commitment that will cause the agent to perform that action. The action definition specifies an action called "helloWorld" and links the action to the HelloWorldActuator. In addition, this definition requires that you specify any pre and post conditions that apply to the action.

Pre-conditions are used to ensure that the action is only performed when it is possible, for example a robot soccer agent program may include a "kick" action. The pre-condition for this action would be that the agent has the ball (i.e. BELIEF(hasBall) ). Conversely, post-conditions are used to identify which actions can be used to achieve the goals of the agent. How to do this will be described later in this guide. For this example, we will declare both the pre- and post- condition of our action to be true (this is a default that means "no precondition or postcondition").

The second part of the program is a commitment declaration that will be added to the initial commitments of the agent. This commitment specifies that the agent has made a commitment to itself, at the current time point, to perform the action "helloWorld". The third parameter of the COMMIT statement (shown in the code fragment below) is the maintenance condition. This condition defines what beliefs must remain true for the agent to maintain its commitment to the action. If the condition becomes false at any time, then the agent will fail the commitment (i.e. it is equivalent to an actuator returning false).

ACTION helloWorld {
    PRECONDITION BELIEF(true);
    POSTCONDITION BELIEF(true);

    CLASS helloworld.HelloWorldActuator;
}

COMMIT(?self, ?now, BELIEF(true), helloWorld);

Copy the above code into the "helloworld.afapl2" file and then compile it in the same way that you compiled the previous example (again, pressing F9 will do the trick).

Finally, we need to deploy our example program. As with the previous example, we need to modify our default.aps and default.cfg files. If you didn't check the relevant tick boxes in the project creation wizard, then don't worry, you can create these files manually by highlighting the helloworld package and selecting New->Empty Platform Configuration File and New->Empty Agent Platform Script file respectively.

If you do need to create these files manually, you should also configure the project to use them as default. To do this, select the project, right-click, and select Properties. Once in the project properties screen, select the "Run" section and you will see the relevant fields (as is illustrated below).

The cfg file contents should be the same as the previous example:

SERVICE fipa.std.service.ams com.agentfactory.plugins.services.ams.AgentManagementService

PLATFORM_GUI com.agentfactory.debugger.Debugger

AGENT_INTERPRETER com.agentfactory.afapl2.interpreter.Agent agent

Conversely, the aps file should declare a single agent:

CREATE_AGENT hello helloworld/helloworld.agent

To deploy this second example, you simply run the project. This will cause the AF Debugger GUI to be started with a single agent in the list, namely hello. When you start the agent, you should see that the "Hello World" message has been printed out in the Netbeans output window.

Example 3: Adopting beliefs within an Actuator

Actions can be broken down into two broad types:

  • Physical (external) actions that have a explicit effect on the state of the agents environment (e.g. a block is moved, a file is copied, an object state is changed).
  • Mental (internal) actions that have no effect on the state of the agents environment, but do have an effect on the mental state of the agent (e.g. performing a calculation, dynamically constructing a plan of action).

Of course, the line between these two types of action is not clear. Some mental actions will utilise resources in order to achieve the chosen effect (e.g. the sum or average of the values stored in some array), while some physical actions will include explicit feedback on the outcome (e.g. where the file was moved). Actuators provide direct support for accessing and effecting the state of the agents environment. However, the ability to effect the agents mental state is more complex. The internal state of the agent is managed by the AFAPL interpreter. Actions are executed linearly, but may be required to alter the mental state that caused the action to be performed in the first place. Within AFAPL, we manage this process by introducing a belief buffer, into which actuator generated beliefs are placed during the current iteration. At the start of the next iteration, the belief manager flushes the buffer, adding any constituent beliefs to the agents next belief set. Additionally, a support method, called the adoptBelief(...) method, is provided as part of the actuator class that can be used to insert beliefs into this buffer.

Lets look at a simple example that is a variant of the previous example and which illustrates how the adoptBelief(...) method works. Specifically, we will adapt the HelloWorldActuator example so that the agent adopts a belief of the form BELIEF(helloWorld):

 import com.agentfactory.logic.agent.Actuator;
 import com.agentfactory.logic.lang.FOS;
 
 public class HelloWorldActuator extends Actuator { 
     public boolean act( FOS action ) {
          adoptBelief("BELIEF(helloWorld)");
          return true; 
     }
 }

Compile and deploy this example in the same way that you compiled and deployed the previous example. Before you step through the iterations of the AFAPL interpreter, select the "Beliefs" tab. One one of the iterations, you will see that the agent adopts the belief BELIEF(helloWorld)!

Specifying Behaviours with Commitment Rules

Adopting initial commitments allows developers to kickstart the behaviour agent. This is particularly useful for scenarios in which the agent must configure access to resources, for example, binding to a platform service (platform services will be discussed later in this guide). However, many scenarios require that an agent act in response to some change in its environment (e.g. the receipt of a message from another agent, the triggering of a sensor, the location of additional resources for processing, ...).

In such cases, we require a mechanism that allows the developer to define situations in which the agent must commit to some activity (i.e. some action or plan). AFAPL2 supports this through the use of commitment rules. Commitment rules specify situations (defined as a belief sentence) in which the agent should adopt a commitment. Informally commitment rules take the form of a logical implication:

<conjunction of positive and negative beliefs> => <commitment>;

For example, if we wished to program our robot soccer agent to move towards the ball whenever it sees it, we could do so through a rule of the form:

 BELIEF(seesBall) =>
 COMMIT(?self, ?now, BELIEF(seesBall), moveTo(ball));

This rule states that, if the robot soccer agent sees the ball, then it should commit to the action of moving to that ball. As can be seen, the convention in AFAPL2 is for the commitment to be written on the line below the implies operator. This is done to improve the readability of the rule and does not affect how the rule is processed. Also, note that the rule is terminated by a semi-colon (;).

Two key points to take from the above example are:

  • The introduction of two pre-defined variables, ?self and ?now, which are bound to constants representing the agents name and the current time respectively.
  • The use of a maintenance condition to constrain the persistence of the commitment when adopted. The agent maintains the commitment to move to the ball until either the moveTo(ball) action completes or the agent no longer believes that it sees the ball.

Should our robot soccer agent ever come to believe that it sees the ball (i.e. it has the belief BELIEF(seesBall) ), then the commitment rule would be fired. This would cause the agent to adopt the corresponding commitment. So, if our agent was called striker, and it saw the ball at 11:46am, then it would adopt a commitment of the form:

 COMMIT(striker, 11:46, BELIEF(seesBall), moveTo(ball))

The above commitment rule specifies a behaviour that is realised through the adoption of a single commitment. Commitment rules can also be used to drive the adoption of multiple commitments simultaneously. This can be achieved by introducing variables into the situation definition.

For example, consider an agent-based security system that includes a monitoring agent that is responsible for monitoring what Radio Frequency IDentification (RFID) tags that enter or leave a specified region (which is monitored by one or more RFID antennas). This agent may be designed to handle beliefs of the form BELIEF(newTag(?tagID)) where ?tagID is a unique code that is assigned to every RFID tag, and the belief itself is used to represent the fact that an new RFID tag has entered the monitored region.

The expected behaviour of this agent is that it will perform a security check whenever a tag enters the monitored region. The agent uses the result of the security check to determine whether or not it should raise an alarm (i.e. the agent raises an alarm if it detects a tag that should not be in the region).

To implement this behaviour within AFAPL2, we add a commitment rule of the form:

 BELIEF(newTag(?tagID)) =>
 COMMIT(?self, ?now, BELIEF(true), checkTag(?tagID));

Informally, this rule states that, if the agent detects that a new RFID tag has entered the monitored region, then it should perform a check to see whether that tag is allowed to be in the monitored region. What the agent does when the tag has been checked can be specified through the introduction of additional commitment rules. For example:

 BELIEF(illegalTagMovement(?tagID)) &
 BELIEF(tagAuthority(?agentName, ?agentAddress)) =>
 COMMIT(?self, ?now, BELIEF(true),
     inform(agentID(?agentName, ?agentAddress), illegalTagMovement(?tagID));

This second rule states that if the agent believes that a tag is not allowed to be in the monitored region (this is the first of the beliefs on the left hand side of the belief rule) and it knows a tag authority agent (this is the second of the beliefs on the left hand side of the belief rule), then it informs the tag authority agent that there it has detected an illegal tag movement (this happens through the adoption of the commitment on the right hand side of the commitment rule).

A key point to make here is how AFAPL2 processes these rules. The truth of the belief sentence on the left hand side of the rule is evaluated based upon the current beliefs of the agent using resolution-based reasoning (the goal-based querying mechanism that is employed within Prolog interpreters). The result of the query process is either failure (that is, the belief sentence was evaluated to false) or a a set of zero or more bindings that cause the belief sentence to be evaluated to true.

The query process returns zero bindings when the belief sentence on the left hand side of the commitment rule contains no variables (this is the case with first example commitment rule that was presented earlier in this section of the quick guide). When the belief sentence does contain variable, then a set of bindings is the set of valid bindings of variables to constants that, when applied to the belief sentence, cause it to be evaluated to true.

To illustrate this, let us consider the RFID scenario in more detail. The agent is responsible for monitoring the movement of objects in a physical space of a building using RFID tags. In such systems, the actual monitoring of the space is carried out by an one or more RFID antennas. The corresponding agent is then linked to that antenna (or set of antennas) via some form of interface that generates events when RFID tags enter or leave the monitored space. To make the agent aware of these events, we introduce an event perceptor that generates beliefs based on the events that are produced by the interface. For events where an object that has an RFID tag enters the monitored space, the perceptor generates beliefs of the form BELIEF(newTag(?tagID)), which corresponds to the belief on the left hand side of the second commitment rule (see above).

So, let us consider the case where a single tagged object (with a unique identifier of 101 - for simplicity) enters the region that is monitored by an agent with identifier "lobby". The entry of this tag is detected by the antennas and passed to the agents perceptor via the interface. This causes the perceptor to generate the belief BELIEF(newTag(101)). The adoption of this belief causes the second commitment rule to be triggered. That is, the belief sentence on the left hand side of this commitment rule is evaluated to true when the variable binding { ?tagID / 101 } is applied. This result in the adoption of a single commitment of the form:

 COMMIT(lobby, 9:28, BELIEF(true), checkTag(101))

If, at the same time, a second tag, with identifier 320, also entered the monitored region, then the agent would have a second belief of the form BELIEF(newTag(320)). This would cause the query process to generate two variable bindings for the second commitment rule: { tagID / 101 } and { ?tagID / 320 }. Based on these bindings, two commitments would now be adopted by the agent: the commitment above, and a second commitment of the form:

 COMMIT(lobby, 9:28, BELIEF(true), checkTag(320))

So, what this example highlights is that, the AFAPL2 interpreter generates every possible variable binding for the belief sentence component of each commitment rule. These bindings are then applied to the commitment component of each commitment rule, and the resultant commitments are adopted.

The main drawback of this approach is scalability - consider what happens if the agent detects 2000 new tags in the monitored region (it will have 2000 commitments to process). However, basic support for managing the how many concurrent instances of an action can be executed has been provided through the introduction of a CARDINALITY statement within ACTION declarations. Further details of how to use this feature can be found here.

Example 4: Implementing a Commitment Rule

Commitment Rules are the heart of the AFAPL2 programming language. They are the construct by which the developer is able to define complex agent behaviours. In this first example, we will illustrate the how to create a simple commitment rule through the adaptation of the perceptor and actuator that were created in example one and example two respectively.

Before we commence the example, lets start with a brief recap of what these two earlier examples did.

  • Example 1: Implementing a Perceptor: This example showed you first how to create a perceptor, and then how to create a simple agent program that employs that perceptor. Specifically, you learnt how to use the PERCEPTOR statement. The perceptor you developed generated the belief: BELIEF(alive). It did this for each iteration of the AFAPL2 interpreter.
  • Example 2: Implementing an Action:This example showed you how to create an action. As you will recall, this involved the creation, in Java, of an actuator, by subclassing the Actuator class. Following this, you then created a simple agent program that employed this actuator. This program consisted of two parts: (1) an ACTION statement, which was used to define an action and link that action to the actuator; and (2) a commitment that referenced the action. In the example, the actuator that was implemented caused the text "Hello World" to be displayed. When the program was run, the commitment was processed, the action was performed, and the text appeared on the console. This happened only once - when the action was performed successfully, the commitment was fulfilled, and subsequently dropped.

In this example, we will combine these two earlier examples to create an agent that prints out the text "Hello World" repeatedly. To do this, you will need to copy the HelloWorldActuator into the alive package within the Alive project. Once you have done this, you should create a new agent program, called "firstrule.afapl" within the alive package:

 PERCEPTOR alive.AlivePerceptor;

 ACTION helloWorld {
   PRECONDITION BELIEF(true);
   POSTCONDITION BELIEF(true);
 
   CLASS alive.HelloWorldActuator;
 }
 
 BELIEF(alive) =>
 COMMIT(?self, ?now, BELIEF(true), helloWorld);

If you compare this program to the one you wrote in example two, you will see that the main differences are as follows:

  1. The program now contains a PERCEPTOR statement, which declares that the agent will use the AlivePerceptor.
  2. The commitment has been replaced by a commitment rule, which informally states that: if the agent believes that it is alive, then it should adopt a commitment to the "helloWorld" action.

As was discussed in the Specifying Behaviours with Commitment Rules section of this guide, this rule will be triggered whenever the agent has the belief BELIEF(alive), and each triggering of this rule (which occurs once per iteration of the agent interpreter) results in the agent adopting a single commitment to the "helloWorld" action.

In this program, this will happen for every iteration of the agent interpreter. Why? Well, because the agent program includes the AlivePerceptor, and this perceptor generates the "alive" belief for every iteration of the agent interpreter. This means that, during each iteration of the AFAPL interpreter, the agent will adopt a commitment to the "helloWorld" action. The interpreter will also attempt to fulfil this commitment in the same iteration in which it was adopted. This will happen successfully, resulting in the text "Hello World" being displayed in the console.

To check that this program works as expected, simply modify the default.aps file to contain the folowing line of code:

 CREATE_AGENT firstRule alive/firstRule.agent

Now, run the program and start the agent. You should see that "Hello World" is now printed out an infinite number of times.

As an aside, it is not necessary to start each agent manually every time. You can start an agent automatically at run time by adding a START_AGENT instruction into the aps file as follows:

 CREATE_AGENT firstRule alive/firstRule.agent
 
 START_AGENT firstRule

Before you close the example, spend some time using the Agent Inspector. In particular, watch how the agent repeatedly adopts the commitment to the action "helloWorld".

Sending Messages between Agents

The introduction of commitment rules gives us the ability to write a range of simple agent programs. In this section, we will switch from discussing core AFAPL2 constructs to looking at one of the key libraries of perceptors and actions that is provided with the language, namely the FIPA ACL library.

This library provides support for the transmission of messages between agents that understand the FIPA Agent Communication Language. Specifically, Agent Factory comes with a prefabricated of the FIPA Agent Message Transport Protocol for HTTP Specification, the FIPA Agent Message Transport Envelope Representation in XML Specification, and the FIPA ACL Message Respresentation in String Specification.

From an implementation perspective, the HTTP Message Transport Protocol has been implemented as a class of Platform Service (see the Platform Service Development Guide) that is known as a Message Transport Service (MTS). Agents are automatically bound to any MTS that is deployed on their local agent platform (i.e. the agent platform that they are running on).

All MTS are capable of receiving and sending FIPA ACL messages. The default representation for these messages is Strings, and XML for the envelope. When a MTS receives a message, it checks the receiver identifiers and forwards the message to any specified agents that are running on the local platform.

Each AFAPL2 agent maintains an internal message queue, and any received messages are added to this queue. Support for generating beliefs about the contents of this queue (i.e the messages that the agent has received) is implemented via the com.agentfactory.plugins.core.fipa.acl.perceptor.MessagePerceptor perceptor. This perceptor generates a single belief for each message and then removes that message from the message queue. The format of beliefs about messages takes the form:

 BELIEF(fipaMessage(?performative, ?sender, ?content))

In this belief, the ?performative parameter corresponds to the type of message that was received, and can be any one of the performatives that are specified in the FIPA Communicative Act Library Specification. The ?sender parameter corresponds to the agent identifier of the agent that sent the message. Specifically, this parameter takes the form sender(?name, ?addresses), where ?name is the unique name of the agent, and ?addresses is a first-order structure whose parameters are addresses that can be used to contact that agent. Finally, the ?content parameter corresponds to the actual content of the message, and is in the same format as the AFAPL2 content language (i.e. a first-order structure).

A key feature of the Agent Factory FIPA ACL Messaging Infrastructure is the definition of agent identifiers (AID). As is described in the FIPA Agent Management Specification, an AID normally consists of three components:

  • a name parameter, which is a globally unique identifier that can be used as a unique referring expression of the agent.
  • the address parameter, which is a list of transport addresses through which a message can be delivered to the agent.
  • the resolvers parameter, which is a list of name resolution service addresses.

In AFAPL2, AIDs are represented by the first-order structure "agentID(?name, ?addresses)". This corresponds to the first two parameters of the FIPA specified AID. Currently, support for the third parameter is not included because it is not used. An example of an AID that includes an address that is based on the HTTP MTS, takes the form:

 agentID(rem, addresses(http://localhost:4444/acc))

To provide support for the sending of messages by an agent, we have developed the AFAPL2 FIPA ACL Library. Specifically, this library defines at least one AFAPL2 action per communicative act specified in the FIPA Communicative Act Library Specification]. For the purposes of this quick start guide, we will describe two in more detail: the "request(?receiver, ?content)" and the "inform(?receiver, ?content)" actions. A complete list of communicative actions provided by the AFAPL2 FIPA ACL Library can be found here.

The "inform(?receiver, ?content)" action is provided to enable the sending of inform messages. An inform message is used to transmit information between agents. For example, in a robot soccer application, containing two soccer agents called leftback and leftcentremidfielder, the leftback agent may inform the leftcentremidfielder whenever it has the ball (allowing the leftcentremidfielder to adapt its position as necessary). To send a message of this form, the leftback agent would have to adopt the following commitment:

COMMIT(?self, ?now, BELIEF(true),inform(agentID(leftcentremidfielder, addresses(http://localhost:4444/acc)), haveBall));

This commitment would result in a message being send to the HTTP-MTS on the local machine (not platform), to an agent platform that is listening on port number 4444. Upon receipt of this message, the MessagePerceptor of the leftcentremidfielder agent would generate a belief of the form:

BELIEF(fipaMessage(inform,
                   sender(leftback, addresses(http://localhost:4445/acc)),
                   haveBall))

Notice that the AID for the sender has a different port number. In this case, the leftback and the leftcentremidfielder agents would reside on different agent platforms that are running on the same physical machine.

In contrast, the "request(?receiver, ?content)" action is provided to enable the sending of request messages. A request message is used when an agent is asking another agent to do something. For example, in our robot soccer example, the leftcentremidfielder agent may send a request to the leftback agent asking it to pass the ball. To send this message, the leftcentremidfielder agent would need to adopt the following commitment:

COMMIT(?self, ?now, BELIEF(true),request(agentID(leftback, addresses(http://localhost:4445/acc)), passBall));

This commitment would result in a message being send to the HTTP-MTS on leftback agents' local agent platform, which is listening on port 4445. Upon receipt of this message, the MessagePerceptor of the leftback agent would generate a belief of the form:

BELIEF(fipaMessage(request,
                   sender(leftcentremidfielder, addresses(http://localhost:4444/acc)),
                   passBall))

Again, notice that the AID specified in this belief matches the AID of the leftcentremidfielder agent, which is running on an agent platform whose HTTP-MTS service is listening on port 4444.

Before moving on to illustrate how to use the AFAPL2 FIPA ACL Library, it is important to point out that our implementation of the FIPA ACL is only at the syntactic level, it does not enforce any semantics onto the interpretation of the message. While this is not necessarily a good thing, it does allow the developer greater flexibility in their implementations. However, our main reason for not enforcing semantic constraints specified for the FIPA ACL is that some of these constraints, such as an agent believing everying it is informed of, are not necessarily beneficial to the design of intelligent agents.

Okay, so how do we implement an AFAPL2 agent that uses the FIPA ACL Library? Well, based on what this guide has described so far, you would need to start by declaring that you agent will use the MessagePerceptor and some (or all) of the communicative actions. However, being required to repeatedly write this code is not a particularly appealing, and offers an increased potential for errors in your code.

Instead, we make use of the AFAPL2 IMPORT statement. The IMPORT statement is part of a basic reuse mechanism for AFAPL2, which is a lot like the #include statements of C and the import statement of Java. Specifically, AFAPL2 lets you break your code up into reusable chunks, and provides the IMPORT statement as a mechanism for declaring which chunks of code you wish to reuse in a particular agent design. Further, as a result of this mechanism, AFAPL2 comes with a pre-defined partial agent program that declares that the agent will use, amongst other things, the MessagePerceptor and all the FIPA ACL communicative acts. To make use of this partial agent program, all you need to do is include the following line in you AFAPL2 program:

 IMPORT com.agentfactory.plugins.core.fipa.acl.agent.FIPACore;

Remember - you must terminate this statement with a semicolon (;).

Example 5: Sending a Message to Another Agent

This example is our first illustration of how to use the AFAPL2 FIPA ACL Library that comes pre-packaged with Agent Factory. In this example, we develop a single agent program that is able to:

  1. Send a "ping" request message to a specified agent
  2. Respond to a "ping" request message by sending a "pong" inform message.

To implement this agent, we will need to start by importing the AFAPL2 FIPA ACL Library into our new agent program, which we will call "ping.afapl2". To do this, we need to start with a program that looks like this:

 IMPORT com.agentfactory.core.fipa.agent.FIPACore;

This agent program now includes all of the necessary actions and perceptors that are required to allow our agent to send and receive FIPA ACL messages. Next, we need to start implementing the first of the two behaviours that we described above, namely the behaviour where our agent sendd a "ping" request message to another specified agent.

To achieve this, we need to write a commitment rule. However, the problem that we face is - how do we specify an agent that we want to send a "ping" message to? Well, basically, we need to define a new term within our inner content language. In this example, we will introduce the "wantToPing(?name, ?addresses)" term. This term will be used to define a belief about an agent that we want to "ping". For example:

 BELIEF(wantToPing(responder, addresses(http://localhost:4444/acc)))

Later, we will describe where the agent gets this belief from, but for now, lets assume that at some point, the agent will have a belief of this form. When this happens, we want the agent to send a "ping" request message to the specified agent. Remember, to send such a message, the agent must adopt a commitment to the "request(...)" action. To do this, we need to add a commitment rule to our program. The revised program is shown below:

 IMPORT com.agentfactory.core.fipa.agent.FIPACore;
 
 BELIEF(wantToPing(?name, ?addresses)) =>
 COMMIT(?self, ?now, BELIEF(true),
     request(agentID(?name, ?addresses), ping));

Informally, this rule states that, should the agent believe that it wants to ping agent ?name with addresses ?addresses, then it should adopt a commitment to perform the request action. This action takes two parameters - the first is the agent identifier of the receiver agent, which here is agentID(?name, ?addresses), and the second is the content of the message, which here is "ping".

So, now we have an agent that can send a "ping" request message. What we need to do next is extend this program to describe how our agent responds to the receipt of a "ping" request message. To achieve this, we need to add a second commitment rule:

 IMPORT com.agentfactory.core.fipa.agent.FIPACore;
 
 BELIEF(wantToPing(?name, ?addresses)) =>
 COMMIT(?self, ?now, BELIEF(true),
     request(agentID(?name, ?addresses), ping));
 
 BELIEF(fipaMessage(request, sender(?name, ?addresses), ping)) =>
 COMMIT(?self, ?now, BELIEF(true),
     inform(agentID(?name, ?addresses), pong));

This second rule states that - if an agent receives a ping request message from another agent with name ?name and addresses ?addresses, then it responds by adopting a commitment to inform that agent of "pong". This is realised by a commitment to the "inform(...)" action where the first parameter is the agent identifier of the receiver agent (which in this case is the agent that sent the "ping" inform message), namely agentID(?name, ?addresses), and the second parameter is the content of the message, which here is "pong".

Note that, although our two commitment rules make use of the ?name and ?addresses variables, there is no direct link between them. That is, variables have a scope that is local to each commitment rule.

So, we now have an agent program that can be used to create agents that are able to realise the two behaviours we specified at the start of this example, namely sending a "ping" request message to a specified agent, and sending a "pong" inform message in response to the receive of a "ping" inform message. Lets see what happens when we run this program.

In order to be able to run this agent program, we must first compile it. Notice that the compiler generated a warning message, this time stating that the term wantToPing(?name, ?addresses) has not been specified. As was explained in the previous example, this happens because there are some more advanced features of AFAPL2 that haven't been explained yet, but which do not affect the operation of any AFAPL2 agent programs (so basically, for the time being ignore this warning whenever you see it).

Next, we need to specify our deployment files. For this example, we need to make a slight change to the platform configuration file we have been using previously. Specifically, we need to associate a message transport service with the agent platform. The revised cfg file is as follows:

 SERVICE fipa.mts.mtp.http.std com.agentfactory.plugins.services.mts_http.HTTPMessageTransportService 4444
 SERVICE fipa.std.service.ams com.agentfactory.plugins.services.ams.AgentManagementService

 PLATFORM_GUI com.agentfactory.debugger.Debugger

 AGENT_INTERPRETER com.agentfactory.afapl2.interpreter.Agent agent

The only change is the inclusion of a second platform service that is an implementation of the FIPA HTTP Message Transport Service. The key point to note here is that the service is running on port 4444.

Finally, we need to create a new agent platform script file that contains the relevant initial agent community. In this ea As is the case for many of these examples, you can reuse the platform.cfg file. However, if you don't have this file, you can get it here. On the other hand, you need to create a new agent platform script (APS) file for this example. In addition to the CREATE_AGENT and START_AGENT commands, this script will require that we use the INITIALISE command to add initial beliefs to a specified agent.

Why do we need this command? Well, basically, an AFAPL2 agent program is a lot like a class (or any other program for that matter). Specifically, it is a generalised description of how a certain type of agent should behave. When we use the CREATE_AGENT command in the APS file, we are basically creating an instance of this type of agent. It is this instance that we view when we run our example applications. As such, we must be careful, when writing AFAPL2 programs to ensure that we keep our design sufficiently general, so as to ensure that we can create multiple instances of it when we deploy our application.

So, when we write our APS file, we are basically declaring an initial community of agents that will be deployed on the agent platform. Some agent instances, such as the ones we will create as part of this example, may need additional information in order to be able to carry out the specified behaviour. In this example, we are going to create two instances of our "ping.afapl2" agent program, namely an "initiator" and a "responder", and we will initialize our agent community in a way that causes the initiator agent to send the "ping" request message to the responder agent. To do this, we use the following APS file, named "ping.aps" :

 CREATE_AGENT initiator ping.agent
 CREATE_AGENT responder ping.agent
 
 INITIALISE initiator BELIEF(wantToPing(responder, addresses(http://localhost:4444/acc)))

The above APS file includes three instructions. The first two instructions cause two agents to be created - out initiator and responder agents. The third instruction causes the initiator agent to be given an initial belief, which informally states the the informer wants to ping the responder agent who is at the specified address. As was discussed earlier, the condition component of the first commitment rule includes a belief that is required to trigger the adoption of the commitment to perform the "inform(...)" communicative act. In this example, we kickstart this behaviour by giving the initiator agent a relevant initial belief. As is shown above, this is done through the APS file.

The second interesting point about the APS file, and this belief in particular, is where the address comes from. In short, the format of each address is dependant upon which MTS is to be used to contact the agent. In this case, we are using the HTTP-MTS, which has the following format:

 http://<ip-address>:<port-number>/acc

In this case, the receiver agent is on the same local agent platform as the sender agent. This explains the <ip-address> part being set to "localhost", but does not fully explain the <port-number> part being set to 4444. This second setting is defined in the platform.cfg file, which is where the HTTP-MTS service is configured. Specifically, platform.cfg includes the following line of code:

 SERVICE fipa.mts.mtp.http.std com.agentfactory.plugins.services.mts_http.HTTPMessageTransportService 4444

What this line says is that an instance of the service that is implemented by the class com.agentfactory.plugins. services.mts_http.HTTPMessageTransportService should be deployed on the agent platform, and this service should be accessible via the unique identifier "fipa.mts.mtp.http.std". Also, any parameters that appear after the class name are interpreted as service configuration parameters. In the case of the HTTP-MTS, one additional parameter is required - namely the port number on which the service should listen for incoming messages, which is this case is 4444.

So, we have created our "ping" agent design, and we have an APS file that specifies two agents and then gives one of the those agents (the initiator agent) an initial belief to kick start the ping behaviour.

When you run this example, the key things to observe are:

  • the adoption of a commitment by the initiator agent to send a message to the responder,
  • the perception of the message and the adoption of a corresponding belief, and
  • the subsequent adoption of a commitment by the responder agent to send a message back to the initiator.

The best way to see this is to start off by stepping the initiator agent (until you see that the commitment to send the message is satisfied), and then switch to the responder agent. Finally, once the responder agent has responded, switch back to the initiator agent to see that the response was received.

Example 6: Deploying over Multiple Agent Platforms

Now that we have discussed basic support for inter agent communication, it is worth taking a quick look at how to distribute an agent-oriented application over multiple platforms, and what effect that has on how we implement and deploy our agents.

An agent platform is the equivalent of an Application Server in Enterprise Java. That is, an agent platform is a software framework that provides support for the deployment of agents (just like application servers provide support for the deployment of Enterprise Java Beans). As such, it provides:

  • an Agent Container that holds deployed agents;
  • an Interpreter Manager that is used to support the configuration and deployment of multiple agent architectures (interpreters) on the agent platform;
  • a Platform Service Manager that is responsible for deploying and configuring platform services;
  • a Security Module that controls access to the platform services, and
  • a Script Handler that processes the APS file on startup.

In addition, an instance of the Agent Management Service is also deployed on each agent platform. This service provides support for the creation, suspension, resumption, and termination of agents. More details on the structure of the agent platform will be described in the Agent Factory Platform Administrators Guide and the Agent Factory Platform Service Developers Guide. Also, the advanced users guide will include a section describing how to use the AMS to create and manage agents at run-time.

Okay, so lets discuss how to go about distributing an agent-oriented application over multiple agent platforms that will run on the same physical machine (i.e. two agent platforms running on one computer). How do we do this?

Each agent platform is configured via a specified platform configuration file (the platform.cfg file that you have been using in the examples). This file specifies the various platform services, interpreters, security settings, and viewers that are to be made available when the platform is run. So, if we want to have two platforms running, then we need to have two platform configuration files - one for each platform.

Why do we need two files? Well, the platform configuration file contains settings that are specific to a given agent platform. The most obvious example of such settings is the one for the HTTP-MTS (which is used in the examples). This setting specifies what port the HTTP-MTS will listen on for incoming messages from other agents residing on other agent platforms. Obviously, it would not be possible to have two agent platforms running on the same physical machine, and have both instances of the HTTP-MTS listening on the same port - the OS does not allow two separate processes to bind to the same socket / use the same port number.

So, we need one platform configuration file per agent platform, to ensure that each platform is configured in a way that does not conflict with the operation of the other deployed agent platforms.

We finish by revising example four so that the ping application is deployed over multiple agent platforms. To do this, we need to take a quick look at the default platform configuration file that we have been using in all of the examples so far:

PLATFORM_DOMAIN domain.tld
PLATFORM_NAME alive

SERVICE fipa.mts.mtp.http.std com.agentfactory.plugins.services.mts_http.HTTPMessageTransportService 4444
SERVICE fipa.std.service.ams com.agentfactory.plugins.services.ams.AgentManagementService

SECURITY_POLICY ALLOW * fipa.mts.mtp.http.std
SECURITY_POLICY ALLOW * af.mts.mtp.udp.std

PLATFORM_GUI com.agentfactory.debugger.Debugger

AGENT_INTERPRETER com.agentfactory.plugins.interpreters.afapl2.AFAPLAgent agent SLEEPTIME=500 THREADING=true

These settings are used to configure an instance of an Agent Factory agent platform. Specifically, the PLATFORM_NAME setting is used to give the agent platform a unique name that is associated with the specified PLATFORM_DOMAIN. In addition, two platform services are deployed on this agent platform - namely an instance of the FIPA HTTP Message Transport Service and an instance of the FIPA Agent Management Service. Specifically, the HTTP-MTS service is configured to listen on port number 4444. Access to these services is then specified by the fifth and sixth lines of the configuration file. In this case, access is unconstrained (any agent can bind to and use these services). The seventh line of the platform configuration file specifies that the AFAPL2 Debugger graphical interface should be used on this platform, and the final line of the configuration file states that the AFAPL2 interpreter should be available on this agent platform.

If we wanted to create a second agent platform that could be used in tandem with this agent platform, then we must alter two of these settings: the PLATFORM_NAME and the port on which the HTTP-MTS is listening. An example of such a platform configuration file, named "platform2.cfg", is:

PLATFORM_DOMAIN domain.tld
PLATFORM_NAME responder

SERVICE fipa.mts.mtp.http.std com.agentfactory.plugins.services.mts_http.HTTPMessageTransportService 4445
SERVICE fipa.std.service.ams com.agentfactory.plugins.services.ams.AgentManagementService

SECURITY_POLICY ALLOW * fipa.mts.mtp.http.std
SECURITY_POLICY ALLOW * af.mts.mtp.udp.std

PLATFORM_GUI com.agentfactory.debugger.Debugger

AGENT_INTERPRETER com.agentfactory.plugins.interpreters.afapl2.AFAPLAgent agent SLEEPTIME=500 THREADING=true

In actual fact, we didn't really need to change the name of the platform because this name is not used in these examples - it is really used by the agent-oriented application infrastructures that can be used with Agent Factory (these are not discussed in this guide).

Now that we have two platform configuration files, what else do we need to do? Well, each platform is going to have an initial community of agents deployed on it. In this example, the initiator agent will be deployed on the "alive" platform, and the responder agent will be deployed on the "responder" platform. To do this, we also need to create two seperate agent platform script (aps) files - one for each platform - that specify this revised initial configuration. Remember, the original APS file used in example four took the form:

CREATE_AGENT initiator ping.agent
CREATE_AGENT responder ping.agent

INITIALISE initiator BELIEF(wantToPing(responder, addresses(http://localhost:4444/acc)))

In this example, we need to break this file into two - one for the alive platform and one for the responder platform. Lets start with the responder platform. The responder agent will be deployed on this platform, so lets create an APS file, called "responder.aps" that does this:

CREATE_AGENT responder ping.agent

Now, lets consider the "alive" platform, on which the initiator agent will be deployed. For this platform we need a seperate APS (in this case called "initiator.aps"). In contrast with "responder.aps", this file will need two commands - one to create the initiator agent and one to give that agent an initial belief that will kickstart the ping behaviour. In example 4, this belief contained the information that the responder agent could be contacted via the HTTP-MTS that was listening on port 4444. However, this is no longer the case because the responder agent is now deployed on the responder platform, whose HTTP-MTS is listening on port 4445. As a result, we need an APS file that looks like this:

CREATE_AGENT initiator ping.agent

INITIALISE initiator BELIEF(wantToPing(responder, addresses(http://localhost:4445/acc)))

Again, notice that the port number in the addresses parameter of the wantToPing belief has changed from 4444 to 4445.

So, what else do we have to do? Nothing! The agent program that we wrote (ping.afapl2) was designed two send a request message to an agent whose agent identifier was specified via the wantToPing belief. This works no matter what agent platform our initiator and responder agents are deployed on. All we have to do now is deploy the application...

To do this, you need to start up two agent platforms: one that uses responder.aps and platform2.cfg; and one that use initiator.aps and platform.cfg. When you run these programs, first step through the initiator agent to see that it sends the message, and then step through the responder agent to see that it has received the message and made a response. Finally, step through the initiator agent again to see that the response is received from the responder.

Example 7: Using the AFAPL2 Address Book

As was discussed earlier in this guide, the basic message passing infrastructure provided by Agent Factory (and used in AFAPL2) is the FIPA Agent Communication Language together with the FIPA HTTP Message Transport Service.

Central to inter-agent communication with FIPA is the concept of an agent identifier. This identifier is a composite that combines a unique name for the agent together with a set of network addresses at which that agent can be contacted and (optionally) a set of name resolution services.

In AFAPL2, basic support for agent identifiers is provided through the pre-defined agentID(?name, ?addresses) predicate, which is used as part of the ?sender field of the various communicative actions that are contained within the FIPA ACL Library. Examples of how this predicate is used can be found in the previous section and also in example 4.

As a support mechanism for FIPA ACL based inter-agent commucation, AFAPL2 includes an agent identifier address book. This address book maintains mappings between agent names and agent addresses. Once an agent identifier is added to the address book, the ?sender cparameter of any communicative actions only need the name of the agent in order to send a message to that agent.

Earlier, we illustrated the request communicative action through a simple example of a leftcentremidfielder agent requesting that the leftback agent pass the ball. This request was realised through the adoption of the following commitment:

 COMMIT(?self, ?now, BELIEF(true),
   request(agentID(leftback, addresses(http://localhost:4445/acc)), passBall));

If, prior to the sending of this message, the leftback's agent identifier was added to the leftcentremidfielders address book, then it would have been possible to achieve the same result from the following commitment:

 COMMIT(?self, ?now, BELIEF(true),
   request(leftback, passBall));

In this second variant, the message passing infrastructure would resolve the agent name "leftback" into its corresponding agent identifier by looking up the identifier in the agent's private address book.

So, the question to be asked is - how do we add agent identifiers to the address book? Well, basically, this is done through one of two purpose-built actions:

  • addAgentID(?name, ?addresses) - this action creates an entry in the address book that maps ?name to the agent identifier agentID(?name, ?addresses)
  • addAgentID(?agentID) - this action creates an entry in the address book that maps the name component of ?agentID to ?agentID.

In fact, this action illustrates a quite important feature of AFAPL2 - namely the ability to overload actions, just as methods can be overloaded in OOP languages such as Java and C++. However, while we mention this feature here, we will leave a detailed discussion to the advanced guide.

TODO (Rem Collier): Complete the example so that it modifies the ping agent of Example 4 to make use of the address book...

Specifying Complex Activities

In addition to the provision of support for specifying actions, AFAPL2 also supports the specification of plans. Plans may be specified either explicitly within the activity field of a commitment, or implicitly, through the use of a plan operator.

Plans are a key tool in the implementation of many agent behaviours. They enable the primitive activities specified by the actions of the agents to be composed into more complex activities that often necessary for AFAPL2 programs that are applied to non-trivial problems.

Currently, AFAPL2 supports the following plan operators:

  • SEQ(X, Y): Specifies a sequence of activities that should be performed sequentially by the agent. Here X should be performed successfully before Y can be performed. The plan is considered to be completed when Y is successfully performed.
  • PAR(X, y): Specifies a sequence of activities that should be performed in parallel by the agent. Here, X and Y should be performed at the same time (the order does not matter). The plan is completed when both X and Y have been performed successfully.
  • OR(X, Y): Specifies a sequence of activities, one of which should be performed. OR is a non-deterministic choice operator, and is very similar to PAR, with the exception that it is considered complete when one of the sub-activities is completed successfully (e.g. the plan is complete when either X or Y completes).
  • XOR(X, Y): A variation of OR in which only one of the operations is performed. That is, the agent will try to perform X. If X fails, it will then try to perform Y. If Y fails, then the plan has failed. [NOTE: This plan operator is a little outdated - in reality, it needs to be refactored so that, once one of the sub-activities is started, all other actions are suspended until the outcome of that sub-activity is known].
  • FOREACH(C, X): Here, an activity, X, is performed for each variable binding that matches the specified condition, C. This operator is evaluated immediately. If no variable bindings exist for C, then the operator is completed. If a variable binding exists, then a corresponding commitment is adopted for each variable binding. The operator is completed when all of these commitments are successfully performed.
  • DO_WHEN(C, X): Do activity X when condition C arises. This operator waits for C to become true (i.e. the agent will be bindly committed to the corresponding commitment). When C does become true, the associated variable bindings are used to create a corresponding set of commitments.
  • AWAIT(C): Wait for C to become true.
  • DELAY(T): Delay for T iterations.
  • ATTEMPT(X, P, F): Attempt to perform activity X. If X succeeds, then perform P, conversely, if X fails, perform F.

As was mentioned at the start of this section, these operators can be used either explicitly or implicitly. Lets have a look at both of these variations, starting with explicit plans.

An explicit plan is a plan that is specified explicitly in the activity field of a commitment, for example:

 COMMIT(Self, Now, BELIEF(true), SEQ(doA, doB));

or

 BELIEF(stateX) =>
 COMMIT(?self, ?now, BELIEF(true), PAR(doA, SEQ(doB, doC)));

As can be seen in the above examples, plan operators can be embedded within one another to form for complex behaviours. One common composite activity structure is as follows:

 OR(
    DO_WHEN(BELIEF(a), doA),
    DO_WHEN(BELIEF(b), doB),
    DO_WHEN(BELIEF(c), doC),
    SEQ(DELAY(5), doTimeOut))

This composite plan uses an OR operator together with the DO_WHEN operator and the SEQ operator to set out a time-bounded set of choices. Here, the agent will perform either the doA, doB, or doC activity if the corresponding belief is adopted by the agent. However, should that belief not be adopted within 5 iterations, then the agent will perform the doTimeOut activity. This is the AFAPL2 equivalent of an if.. .else / switch statement.

Finally, plans can also be defined implicitly (here the term implicit is used with respect to the commitment as the implementation of the plan is not defined explicitly) via the PLAN construct:

 PLAN myPlan(?x, ?y) {
     PRECONDITION BELIEF(true);
     POSTCONDITION BELIEF(true);
 
     BODY SEQ(doA, doB(?x), doC(?x, ?y));
 }

This above example defines a plan whose body involves a sequence of actions doA, doB(?x) and doC(?x, ?y). In addition, the example specifies a pre-condition that must be met for the plan to be executed and a post-condition that signified the expected state of affairs that should exist after the plan is completed.

Example 8: The Chatter Agent

This example is a simple extension of the ping agent example in which the agents continue to "chatter" with one another indefinitely. As a precursor to completing this example, please create a Netbeans AF project entitled Chatter.

In this example, we view an agent that implements the "chatter" behaviour to be one that:

  • prints out the name of the agent with whom it is chattering whenever it receives a message from that agent, and
  • sends a chatter message back to that agent in order to continue "chattering".

The first part of this behaviour requires a slightly modified version of the HelloWorld actuator that we explored earlier in this guide. Specifically, this actuator assumes that the corresponding action identifier includes a single parameter: an agent name. The actuator such print out the text "Chattering with: " followed by the name of the agent. This can be implemented through the following actuator code:

 import com.agentfactory.logic.agent.Actuator;
 import com.agentfactory.logic.lang.FOS;

 public class ChatterActuator extends Actuator {
   public boolean act(FOS action) {
     String name = action.argAt(0).toString();
 
     System.out.println("Chattering with: " + name);
     return true;
   }
 }

As can be seen in the above code, it is possible to pass parameters to an actuator via the corresponding action identifier. The corresponding AFAPL2 action definition for this actuator is:

 ACTION chatter(?name) {
   PRECONDITION BELIEF(true);
   POSTCONDITION BELIEF(true);
   
   CLASS chatter.ChatterActuator;
 }

This action definition would then be used as part of a commitment, such as:

 COMMIT(Rem, 19:22, BELIEF(true), chatter(Bob));

The above commitment would result in the ChatterActuator actuator being fired, and the name local variable being bound to the string "Bob". The subsequent console output would then be:

 Chattering with: Bob

However, the actual output of a "chatter" agent will depend upon who it is chattering with. As a first step, we can encode this using the following commitment rule:

 BELIEF(fipaMessage(inform, sender(?name, ?addr), chatter)) =>
 COMMIT(?self, ?now, BELIEF(true), chatter(?name));

This rule states that, if the agent is informed of chatter by another agent, then it should invoke the chatter action. However, the other part of our behaviour involves continuing the chat. This can be achieved by informing the sender agent of chatter. One way of implementing this is via a second commitment rule of the form:

 BELIEF(fipaMessage(inform, sender(?name, ?addr), chatter)) =>
 COMMIT(?self, ?now, BELIEF(true), inform(agentID(?name, ?addr), chatter));

However, a better solution is to combine the two rules into a single rule, and because we want both activities to take place simultaneously, we use a PAR plan operator to do this.

 BELIEF(fipaMessage(inform, sender(?name, ?addr), chatter)) =>
 COMMIT(?self, ?now, BELIEF(true),
   PAR(chatter(?name),
       inform(agentID(?name, ?addr), chatter)));

as with the ping agent example, the above commitment rule is not enough. This rule allows an agent that is already in a chatter behaviour to continue chattering. It does not allow an agent to start a new chatter behaviour. We can achieve this by adding the following commitment rule (which is similar to that used in the ping agent example):

 BELIEF(wantToChat(?name, ?addr)) =>
 COMMIT(?self, ?now, BELIEF(true), inform(agentID(?name, ?addr), chatter));

Now, that our agent is able to both start and continue a chatter behaviour, lets look at the complete agent code:

 IMPORT com.agentfactory.core.fipa.agent.FIPACore;
 
 ACTION chatter(?name) {
   PRECONDITION BELIEF(true);
   POSTCONDITION BELIEF(true);
   
   CLASS chatter.ChatterActuator;
 }
 
 BELIEF(fipaMessage(inform, sender(?name, ?addr), chatter)) =>
 COMMIT(?self, ?now, BELIEF(true),
   PAR(chatter(?name),
       inform(agentID(?name, ?addr), chatter)));
 
 BELIEF(wantToChat(?name, ?addr)) =>
 COMMIT(?self, ?now, BELIEF(true), inform(agentID(?name, ?addr), chatter));

Save and compile the above code in an AFAPL2 Agent Design file called chatter.afapl2 that is part of the chatter package within the project. The platform configuration file is the same was used in the earlier communication examples, and the agent platform script is similar. Copy out the sample script below and run the example in the usual way.

 CREATE_AGENT Rem chatter/chatter.agent
 CREATE_AGENT Bob chatter/chatter.agent
 
 INITIALISE Rem BELIEF(wantToChat(Bob, addresses(http://localhost:4444/acc)))

As a final step, you can play around with this code, by building more complex initial communities. For example, to create a community of three agents that all chat with one another, you should use the following APS file:

 CREATE_AGENT Rem chatter/chatter.agent
 CREATE_AGENT Bob chatter/chatter.agent
 CREATE_AGENT Fred chatter/chatter.agent
 
 INITIALISE Rem BELIEF(wantToChat(Bob, addresses(http://localhost:4444/acc)))
 INITIALISE Bob BELIEF(wantToChat(Fred, addresses(http://localhost:4444/acc)))
 INITIALISE Fred BELIEF(wantToChat(Rem, addresses(http://localhost:4444/acc)))

Which agent initiates the conversation is not really important, it would have been quite acceptable to have rem also start a conversation with Fred.

Implementing a Module

Modules are internal agent components that provide a mechanism for storing data or providing interfaces to external resources. The data or functionality provided by a modules is accessed/modified via either some of the agents actuator and perceptor units. In contrast with platform services, which provide potentially shared (by multiple agents) access to data stores and external resources via the underlying agent platform, modules are internal components that are private to the individual agent that instantiates that module. Because module instances are internal to an agent, no security model is necessary.

As a general rule, modules are useful for providing an agent with abstract data types such as queues and stacks, or for providing graphical interfaces through which the agent can interact with users.

Module instances are specified as part of the agent design via the LOAD_MODULE construct:

 LOAD_MODULE <identifier> <implementing-class>;

For example, a queue module that is used by an Indexer agent to handle incoming documents may be specified as follows:

 LOAD_MODULE incomingDocumentQueue module.QueueModule;

Modules are implemented by extending the default com.agentfactory.logic.agent.Module class. In addition, the developer has the option of specifying an init() method that can be used to initialize the module.

Example 9: A Queue Agent

To illustrate how a module can be created, we will implement a simple queue agent. This agent will manage an internal queue resource, that is implemented as a Module. In addition to the module, it will be necessary to provide actuators that allow the agent to:

  • add an item to the queue (enqueue)
  • remove the head of the queue (dequeue)

Also, a perceptor will be provided that generates beliefs about the state of the queue, including:

  • the current size of the queue
  • the current head of the queue (the equivalent of the peek() operation that is normally associated with a queue).

While the above example is relatively simplistic, it provides a basis for later on in the guide, where we discuss how to implement an Interface Agent (here the queue is an event queue that is populated by the interface).

Module Definition

As was stated earlier, Modules are implemented by extending the com.agentfactory.logic.agent.Module class, as is done below:

 package module;
 
 public class Queue extends Module {
   private List<String> queue;
   
   public void init() {
     queue = new LinkedList<String>();
   }
     
   public void enqueue(String item) {
     queue.add(item);
   }
   
   public String dequeue() {
     return queue.remove(0);
   }
   
   public String head() {
     return queue.get(0);
   }
   
   public int size() {
     return queue.size();
   }
   
   public boolean isEmpty() {
     return queue.isEmpty();
   }
 }

As can be seen in the above code, for the purposes of this example, we have chosen to implement a queue that holds string objects. In particular, we we use strings whose contents are first-order structures (e.g. basket(beer, chips), flight(dublin, london), event(holiday_selected, details(1, hawaii)) ). We do this for simplicity, as if we employed other more complex objects in the queue, we would have to convert those objects into first-order structures anyway - which is not really the point of this example. Finally, the convention for organising AFAPL agent components is that modules are stored in a module package, hence the above class may take the form: module.Queue (or some more complex variant).

Next, we must implement the associated perceptor and actuator units. In this example, we will design our queue module to support multiple internal queues within an agent. For this, we will use the module name as the unique identifier of each queue. Remember, module names are assigned during the definition of the module in the AFAPL code:

 LOAD_MODULE <name> <class>;

Perceptor Definition

First, let us look at the Perceptor, this is implemented below by extending the com.agentfactory.logic.agent.Perceptor class. The convention for organising agent components is that all perceptor code is stored in a perceptor package.

 package perceptor;
 
 import com.agentfactory.logic.agent.Perceptor;
 import module.Queue;
 
 public class QueueState extends Perceptor {
   public static final String QUEUE_CLASS = "module.Queue";
 
   public void perceive() {
     int size = 0;
     Queue queue = null;
 
     List queues = this.getModulesByClass(QUEUE_CLASS);
     Iterator it = queues.iterator();
     while (it.hasNext()) {
       queue = (Queue) it.next();
       size = queue.size();
       adoptBelief("BELIEF(queueSize(" + queue.getName() + "," + size + "))");
       if (size > 0) {
         adoptBelief("BELIEF(queueHead(" + queue.getName() + "," + queue.head() + "))");
       }
     }
   }
 }

As can be seen in the above code, this perceptor retrieves a list of modules that match the specified class via the getModulesByClass(...) method, passing in a fully-qualified class name, which in this case is the module.Queue class. The perceptor then iterates through this list, and for each queue module returned, generates either one or two beliefs:

  • A belief about the size of each queue is returned
  • For those queues that are not empty, the head of the queue is also returned

These beliefs are generated at the start of each iteration of the AFAPL interpreter.

Actuator Definition

Next we need to specify the actions associated with the queue, namely the enqueue(?queue, ?item) and dequeue(?queue, ?item) actions. To do this, we will start with the enqueue actuator. In a similar manner to perceptors and modules, actuators are typically stored in stored in an actuator package.

 package actuator;
 
 import com.agentfactory.logic.agent.Actuator;
 import com.agentfactory.logic.lang.FOS;
 import module.Queue;
 
 public class Enqueue extends Actuator {
   public boolean act(FOS action) {
     String queueName = action.argAt(0).toString();
     String item = action.argAt(1).toString();
 
     Queue queue = (Queue) this.getModuleByName(queueName);
     if (queue == null) {
       adoptBelief("BELIEF(enqueueFailed(" + queueName + "," + item + "))");
       return false;
     } else {
       queue.enqueue(item);
       adoptBelief("BELIEF(enqueued(" + queueName + "," + item + "))");
     }
     
     return true;
   }
 }

As can be seen in the above code, the core of the actuator implementation is the act method, which takes a single FOS object as a parameter. This object is a representation of the action that the agent committed itself to, and for the Enqueue actuator, this object is required to have two parameters:

  • A queue name (the name of the module that implements the relevant queue)
  • An item (the item to be added to the queue)

The main functionality of this code involves using the getModuleByName(...) method to retrieve a reference to the relevant queue module. If no reference is returned, then there is no queue of that name, and the enqueuing operation fails, and a corresponding belief is generated. If a reference is returned, then the item is enqueued, and a belief about the success of that action is generated. These beliefs are added at the start of the next iteration of the AFAPL interpreter cycle.

In contrast with enqueue, the dequeue action takes only one parameter - the name of the queue that is to be dequeued. The code for the dequeue actuator is given below:

 package actuator;
 
 import com.agentfactory.logic.agent.Actuator;
 import com.agentfactory.logic.lang.FOS;
 import module.Queue;
 
 public class Dequeue extends Actuator {
   public boolean act(FOS action) {
     String queueName = action.argAt(0).toString();
 
     Queue queue = (Queue) this.getModuleByName(queueName);
     if (queue == null) {
       adoptBelief("BELIEF(dequeueFailed(" + queueName + ", noSuchQueue))");
       return false;
     } else {
       String item = queue.dequeue();
       if (item == null) {
         adoptBelief("BELIEF(dequeueFailed(" + queueName + ", emptyQueue))");
         return false;
       } else {
         adoptBelief("BELIEF(dequeued(" + queueName + "," + item + "))");
       }
     }
     
     return true;
   }
 }

The above code is broadly similar to the Enqueue actuator code, with the exception that the item is returned from the queue as opposed to being added to the queue. This leads to two potential failure points:

  • Use of a name that does not correspond to a queue
  • An attempt to remove an item from a queue that is empty

In both cases, a "dequeueFailed" belief is generated to report this failure. In the event that the item is successfully dequeued from the queue, then a belief about the dequeued item is also generated. Again, these beliefs are adopted at the start of the next iteration of the AFAPL interpreter cycle.

A Basic AFAPL Queue Agent

Finally, we will now put all of the bits together to implement a simple Queue agent. This agent will be very basic, and will employ the following behaviour for each Queue module that is declared.

  • If the queue is empty, add "item" to the queue
  • If the queue is not empty, perform a dequeue action

This can be encoded through the following commitment rules:

 BELIEF(queueSize(?queue, 0)) =>
 COMMIT(?self, ?now, BELIEF(true), enqueue(?queue, item));
 
 BELIEF(queueHead(?queue, ?item)) =>
 COMMIT(?self, ?now, BELIEF(true), dequeue(?queue));

However, the above rules alone are not enough, we must also declare the components (perceptors, actuators and modules) that will be used by the agent:

 /**
  * Basic Queue Agent Test Implementation
  */
 
 // Embodiment Configuration
 PERCEPTOR perceptor.QueueState;
 
 ACTION enqueue(?queue, ?item) {
   PRECONDITION BELIEF(true);
   POSTCONDITION BELIEF(true);
 
   CLASS actuator.Enqueue;
 }
 
 ACTION dequeue(?queue) {
   PRECONDITION BELIEF(true);
   POSTCONDITION BELIEF(true);
 
   CLASS actuator.Dequeue;
 }
 
 LOAD_MODULE testQueue module.Queue;
 
 // Key Agent Behaviours
 BELIEF(queueSize(?queue, 0)) =>
 COMMIT(?self, ?now, BELIEF(true), enqueue(?queue, item));
 
 BELIEF(queueHead(?queue, ?item)) =>
 COMMIT(?self, ?now, BELIEF(true), dequeue(?queue));

As can be seen in the above code, we must first declare the embodiment configuration, which specifies any relevant actuators, perceptors and modules. Next we declare the commitment rules that specify the key behaviours of the agent.

To run this sample program, you should create and compile all of the Java agent components. Next you should create and compile the above AFAPL program (use the filename "Queue.afapl2"). Finally, you need to write a deployment file (you can use the standard platform configuration file here).

 CREATE_AGENT test Queue.agent

When you deploy this agent system, use the debugger to step through the test agent's mental state, you should see that the agent repeatedly enqueues and then dequeues "item" from the testQueue queue...

Source Code Download

The source code for this example can be downloaded [here].

Example 10: Managed Service Provider

This second example attempts to illustrate how the above queue module can be used to implement a more meaningful agent. For this example, we reuse all of the agent components that were developed in the previous example, together with the associated AFAPL2 embodiment configuration. In fact, all that changes is the actual behaviours that are associated with the agent.

The specific focus of this example is to implement an generic behaviour for an agent that implements a service. The agent will receive requests from other agents to make use of the service. The service agent responds by confirming that it has received the request and adds the request to an internal queue. Independently, the service agent processes individual service requests one at a time, removing each request from the queue once it has completed the previous request.

To implement this behaviour, it is necessary that the agent be able to interact with other agents. This requires that it import the com.agentfactory.core.fipa.agent.FIPACore AFAPL2 program. So, as a starting point, we have the following AFAPL2 program:

 IMPORT com.agentfactory.core.fipa.agent.FIPACore;
 
 // Embodiment Configuration
 PERCEPTOR perceptor.QueueState;
 
 ACTION enqueue(?queue, ?item) {
   PRECONDITION BELIEF(true);
   POSTCONDITION BELIEF(true);
 
   CLASS actuator.Enqueue;
 }
 
 ACTION dequeue(?queue) {
   PRECONDITION BELIEF(true);
   POSTCONDITION BELIEF(true);
 
   CLASS actuator.Dequeue;
 }
 
 LOAD_MODULE testQueue module.Queue;

Next, we need to think about the associated behaviours. Lets start by looking at how the agent receives and responds to a request for the service. A key first step that we need to think about is, how is the agent aware of the services that it provides. One approach is to give the agent a belief about the services that it provides, this can be achieved by specifying a predicate of the form:

 serviceProvider(?name, ?queue)

where ?name is the name of the service, and ?queue is the name of the queue that service requests should be stored on. Service requests will come in the form of request messages, whose content will need to specify the service requested, and also give any additional details required by that service. Lets represent this inner content using a predicate of the form:

 service(?name)

where ?name is the name of the service. Thus, when our service provider receives a request for a service, it will have a belief of the form:

 BELIEF(fipaMessage(request, sender(?rname, ?rAddr), service(?name)))

Finally, when we store a service request on the associated queue, we can use another predicate that takes the following form:

 serviceRequest(?name, ?requestorAgentID)

where ?name is the name of the service, and ?requestorAgentID is an agent identifier, which must have the form:

 agentID(?agentName, ?addresses)

Now, lets put all this together to create the first commitment rule:

 BELIEF(fipaMessage(request, sender(?rName, ?rAddr), service(?name))) &
 BELIEF(serviceProvider(?name, ?queue)) =>
 COMMIT(?self, ?now, BELIEF(true),
   SEQ(enqueue(?queue, serviceRequest(?name, agentID(?rName, ?rAddr))),
       inform(agentID(?rName, ?rAddr), serviceConfirmation(?name))));

Notice that we use a SEQ operator to specify that the agent should enqueue the service request and, only if this is successful, does it confirm receipt of the service request to the other agent.

Next, we need to consider the case where the agent does not provide the requested service. This can be dealt with by the following commitment rule:

 BELIEF(fipaMessage(request, sender(?rName, ?rAddr), service(?name))) &
 !BELIEF(serviceProvider(?name, ?queue)) =>
 COMMIT(?self, ?now, BELIEF(true),
   inform(agentID(?rName, ?rAddr), noSuchService(?name)));

Lets put this all together to see what our AFAPL2 program looks like at this point:

 IMPORT com.agentfactory.core.fipa.agent.FIPACore;
 
 // Embodiment Configuration
 PERCEPTOR perceptor.QueueState;
 
 ACTION enqueue(?queue, ?item) {
   PRECONDITION BELIEF(true);
   POSTCONDITION BELIEF(true);
 
   CLASS actuator.Enqueue;
 }
 
 ACTION dequeue(?queue) {
   PRECONDITION BELIEF(true);
   POSTCONDITION BELIEF(true);
 
   CLASS actuator.Dequeue;
 }
 
 LOAD_MODULE testQueue module.Queue;
 
 BELIEF(fipaMessage(request, sender(?rName, ?rAddr), service(?name))) &
 BELIEF(serviceProvider(?name, ?queue)) =>
 COMMIT(?self, ?now, BELIEF(true),
   enqueue(?queue, serviceRequest(?name, agentID(?rName, ?rAddr))),
   inform(agentID(?rName, ?rAddr), serviceConfirmation(?name))));
 
 BELIEF(fipaMessage(request, sender(?rName, ?rAddr), service(?name))) &
 !BELIEF(serviceProvider(?name, ?queue)) =>
 COMMIT(?self, ?now, BELIEF(true),
   inform(agentID(?rName, ?rAddr), noSuchService(?name)));

So, now we have implemented a behaviour in which an agent is able to handle and enqueue incoming requests for one the services that it offers. The next step of the example involves developing behaviours to actually process the service requests. For the moment, lets keep it simple, and consider a testService that prints out the name of the service requestor to the console. This will require a simple actuator that prints out the name of the agent plus some explanatory text. We can adapt the ChatterActuator for this:

 import com.agentfactory.logic.agent.Actuator;
 import com.agentfactory.logic.lang.FOS;
 
 public class TestServiceActuator extends Actuator {
   public boolean act(FOS action) {
     String name = action.argAt(0).toString();
     System.out.println("This is the test service for: " + name);
     return true;
   }
 }

Now, the associated AFAPL2 code will take the form:

 ACTION testService(?agentName) {
   PRECONDITION BELIEF(true);
   POSTCONDITION BELIEF(true);
   
   CLASS TestServiceActuator;
 }
 
 BELIEF(queueHead(testService, serviceRequest(?name, agentID(?agentName, ?addr)))) =>
 COMMIT(?self, ?now, BELIEF(true), testService(?agentName));

Advanced Belief Operators

In addition to the basic boolean operators & (and), ! (not) and => (implies), the AFAPL2 language also includes a number of additional operators. Broadly speaking, these operators fall into one of two categories:

  • Comparison Operators: These operators allow you to perform comparisons between first order structures (e.g. equality check).
  • Temporal Operators: These operators are used to attribute greater levels of persistence to certain of the agents beliefs.

each of these categories of operator are discussed in more detail below.

Comparison Operators

In addition to the usual boolean operators & (and) and ! (not), AFAPL2 also includes three basic comparison operators:

  • EQUAL(?x, ?y): Checks that ?x and ?y are equal.
  • GREATER_THAN(?x, ?y): Checks whether ?x is greater than ?y
  • LESS_THAN(?x, ?y): Checks whether ?x is less than ?y
  • IN_RANGE(?val, ?lower, ?higher): Checks whether the integer ?val is in the range [?lower, ?higher].

These operators can be used as part of any belief sentence within:

  • Commitment and Belief Rules
  • Commitment Maintenance Conditions
  • Activity Pre- and Post- Conditions

Each operator performs a comparison between the two arguments that are passed as parameters. The EQUAL operator checks whether the two operators are equal, while the GREATER_THAN and LESS_THAN operators currently check whether the first parameter is less than / greater than the second parameter. Finally, the IN_RANGE operator checks whether the first parameter is in a range specified by the second and third parameters. In all cases, any variables must be bound prior to the evaluation of the operator.

For example, if we want define a behaviour in which the agent tells all other agents apart from Rem not to disturb it, we could use the following commitment rule:

 BELIEF(fipaMessage(?perf, sender(?name, ?addr), ?content)) & !EQUAL(?name, Rem) =>
 COMMIT(?self, ?now, BELIEF(true),
     inform(agentID(?name, ?addr), doNotDisurb));

Temporal Operators

By default, all beliefs of the agent are not persistent. That is, they exist for only one (the current or the next) iteration of the AFAPL interpreter. As a result, beliefs must be regenerated (re-perceived) at the start of each iteration. Within AFAPL, explicit support exist for the creation of more persistent beliefs. This support is realised through a number of temporal operators that can be applied to individual beliefs so that they persist for varying lengths of time. These temporal beliefs, while part of the agents belief set, are stored separately. Their impact on the current beliefs of the agent is managed via an internal belief management process that is part of the belief update process (the process that triggers the perceptors of the agent).

Configuring Actuators, Perceptors, and Modules

As of version 1.2.0, the AFAPL2 language provides support for configuring actuators, perceptors and modules. Specifically, each of these components, which is implemented by extending the relevant abstract base class, maintains a map of key-value pairs that corresponds to the configuration of that component. The objective behind the introduction of this feature is to improve the configurability and reusability of the underlying components.

Access to this configuration is via the "component" field of the specific base class, for example, to access the "module" parameter of an actuators configuration, you would use something like the following snippet of code:

 public boolean act(FOS action) {
     String id = (String) configuration.get("module");
     MyModule module = (MyModule) getClassByName(id);
     ...
 }

Initial configurations for components are provided via a extension to the standard AFAPL2 actuator, perceptor, and module declarations. Specifically, for actuators (which are declared implicitly as part of actions), you use the following format:

 ACTION <id> {
     PRECONDITION <belief-sentence>;
     POSTCONDITION <belief-sentence>;
     CLASS <actuator-class> {
         key1=value1;
         key2=value;
     }
 }

If no configuration is necessary, then the normal format of an action can still be used:

 ACTION <id> {
     PRECONDITION <belief-sentence>;
     POSTCONDITION <belief-sentence>;
     CLASS <actuator-class>;
 }

A similar extension is provided for perceptors:

 PERCEPTOR <id> {
     CLASS <perceptor-class> {
         key1=value1;
         key2=value2;
     }
 }

Finally, for modules, the corresponding format for defining a configuration is as follows:

 LOAD_MODULE <id> <module-class> {
     key1=value1;
     key2=value2;
 }

Modifying Component Configurations at Runtime

TBC

Example

TBC

Creating and Using Ontologies

AFAPL2 includes support for defining ontologies within an agent program. Briefly, ontologies are used to specify the inner content language of the beliefs of the agent. Currently, an ontology takes the form of a list of valid terms. Ontologies are declared through the use of the ONTOLOGY construct:

ONTOLOGY <id> {
    PREDICATE <fos>;
    PREDICATE <fos>;
}

For example, the FIPACore partial agent program (which implements support for FIPA ACL based agent communication), sp