AFAPL2

From Agent Factory

Jump to: navigation, search

This language has now been deprecated and there will be no further development work on it. Instead we refer you to our more recent work on the Common Language Framework and some of the languages we have built using it, such as AF-AgentSpeak and the new version of AFAPL.

Contents

Editorial History

13/02/2007: 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.

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. For more information and a general introduction to the AFAPL2 language, please read the AFAPL2 page. Alternatively, a number of papers describing the AFAPL2 language can be found on the Publications page of this website.

Running the Examples

To complete this guide, it is recommended that you download, install and use the latest version of the AF Netbeans Plugin from Sourceforge. In fact, all of the examples in this guide assume the use of this IDE.

It is also 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.

Overview

AFAPL2 is an extended version of the original Agent Factory Agent Programming Language (AFAPL) that combines the basic features of AFAPL:

With an additional set of features that have been motivated by the use of AFAPL in a number of Irish and European-funded research projects:

  • Additional practical Plan operators
  • A Role programming construct
  • A notion of a Goal
  • Support for specifying ontologies that the agent uses via an Ontology construct.

As is highlighted in the figure on the right-hand side, the language is based on a mental state model that consists of a number of primary mental attitudes together with a set of meta attitudes that support the primary mental attitudes. Further, the interface between the agent and its environment is implemented as a set of Java classes, together with a support class that enables the creation of internal private resources that can be used by the agent to implement any additional data structures that may be required.

Finally, as can be seen, the underlying environment is implemented as a combination of Platform Serivces; other external systems whose interfaces can either be accessed directly or alternatively via some Module; and any other agents deployed in the system.

This page provides an overview of concepts that underpin the AFAPL2 language, which is executed on a purpose-built agent interpreter. For information on how to use the language to develop agents, please read the AFAPL2 Programmers Guide.

Mental State Model

The mental state of an AFAPL2 agent consists of the following components:

  • A Belief set: this component is used to build a model of the state of the agents environment.
  • A Commitment set: this component is used to represent the decisions that the agent has made about how to act.
  • A Plan Library: this component holds a set of pre-written plans that the agent may commit to.
  • An Action set: this component holds the set of primitive activities that the agent may perform.
  • A Commitment Rule set: this component holds a set of rules that encapsulate the decision-making process of the agent (i.e. in what situations the agent should make a Commitment to a given plan or action).
  • A Role Library: this component holds a set of pre-written roles that the agent may play.
  • An Ontology set: this component holds a set of ontologies that model the kinds of facts and objects that the agent can use to reason about its environment.

The Core Java Components

In addition to the mental components listed in the previous section, AFAPL2 also makes use of three Java components:

  • Perceptors: These components implement the basic sensory apparatus of an agent, in that they are used to generate beliefs about the current state of the agent and its environment.
  • Actuators: These components implement the basic capabilities of an agent. That is, they are implementations of the Actions of an agent.
  • Modules: These components are private resources that can be used to implement additional data structures that an agent needs or to provide interfaces to external systems.

A Reuse Mechanism

AFAPL2 provides a simple mechanism for reusing code that is based on the #include statement of C, namely the IMPORT statement.

The AFAPL2 Interpreter

AFAPL2 agents function as a result of their mental state being manipulated by an underlying interpreter whose implementation is a variation on the basic execution cycle of a deliberative agent, namely:

perceive -> deliberate -> act

As can be seen in the diagram on the right-hand side, for AFAPL2, this basic cycle is implemented as a four-stage process:

In terms of the basic cycle, the perceive functionality is implemented as part of the belief update process, and the act functionality is implemented as part of the commitment management process (specifically, actions are performed in the commitment realisation phase of this process).

These steps are performed repeatedly, in the specified order, until the agent is terminated.

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.

Example Program

To illustrate the AFAPL2 programming language, two example agent program that implement a Vickrey-style auction are outlined. This example combines a number of concepts, such as agent communciation, plans, beliefs and commitment rules. The AFAPL2 Programmers Guide also includes a number of worked examples that illustrate these concepts in a more focused way.

The specific auction implemented in the example below is a single-shot, highest bidder wins auction. The implementation is separated into two AFAPL2 programs: Auctioneer.afapl2 and Bidder.afapl2, that implement the auctioneer and bidder behaviours respecively. An Agent UML Protocol Diagram for this auction is outlined to the right-hand side. We will explore these two programs in the following sections.

Bidder.afapl2

Let us start by exploring the simpler Bidder.afapl2 agent program:

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

ONTOLOGY Bidder {
    PREDICATE bid(?item, ?amt);
}

ACTION randomBid(?item) {
    PRECONDITION BELIEF(true);
    POSTCONDITION BELIEF(true);
   
    USES Bidder;
   
    CLASS actuator.RandomBid;
}

BELIEF(fipaMessage(cfp, sender(?name, ?addr), auction(?item))) =>
COMMIT(?self, ?now, BELIEF(true),
    PAR(randomBid(?item),
        DO_WHEN(BELIEF(bid(?item, ?amt)),
            propose(agentID(?name, ?addr), bid(?item, ?amt))
        )
    )
);

The first statement of this program imports a pre-existing AFAPL2 agent program that specifies all of the actions and perceptors necessary to support FIPA ACL-based agent communication. This includes a Perceptor that generates beliefs about messages received by the agent, and Actions that support sending of messages using any of the standard FIPA performatives.

The next statement declares an Ontology that includes one predicate - bid(?item, ?amt) - that can be used to represent the bid that an agent makes for an item. Beliefs of this form are generated by the randomBid(...) Action, which is declared in the third statement. The pre- and post-conditions associated with this Action are default values that are often used, and which always evaluate to true. The USES statement associates this Action with the Bidder Ontology. This is used to simplify the declaration of beliefs in the corresponding Actuator which is declared on the final line (CLASS) of the ACTION statement. This Actuator is implemented as follows:

package actuator;

import com.agentfactory.logic.agent.Actuator;
import com.agentfactory.logic.agent.Belief;
import com.agentfactory.logic.lang.FOS;
import java.util.Random;

public class RandomBid extends Actuator {
    static final Random random = new Random();
  public boolean act(FOS action) {
    String item = action.argAt(0).toString();

    int amt = random.nextInt();
    Belief belief = createBelief("bid");
    belief.bind("?item", item);
    belief.bind("?amt", "" + amt);
    adoptBelief(belief);
    return true;
  }
}

Note that the createBelief(...) method creates a template belief based on the PREDICATE defined in the associated Ontology, in this case the Bidder Ontology. Individual variables can the be bound to values using the bind(...) method, and the Belief adopted via the adoptBelief(...) method.

The last statement of the Bidder.afapl2 agent program is a Commitment Rule, which defines the behaviour of this agent. Specifically, the rule states that: in the situate where the agent believes that it has received a cfp message from another agent that has the content action(?item), then the agent should adopt the following Plan to generate and return a random bid, and then send a propose message to the agent that sent the cfp message with the coresponding bid for the item.

Auctioneer.afapl2

In contrast, the Auctioneer.afapl2 agent program is more complex:

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

ONTOLOGY Auctioneer {
    PREDICATE wantToAuction(?item);
    PREDICATE duplicateAuction(?item);
    PREDICATE unknownAuction(?item);
    PREDICATE client(?name, ?addr);
    PREDICATE winner(?item, ?agentID, ?amt);
    PREDICATE loser(?item, ?agentID);
}

LOAD_MODULE auction module.Auctions;

PERCEPTOR auctionView {
    USES Auctioneer;

    CLASS perceptor.AuctionView {
        module=auction;
    }
}

ACTION startAuction(?item) {
    PRECONDITION BELIEF(true);
    POSTCONDITION BELIEF(true);
    
    USES Auctioneer;
    
    CLASS actuator.StartAuction {
        module=auction;
    }
}

ACTION recordBid(?item, ?name, ?bid) {
    PRECONDITION BELIEF(true);
    POSTCONDITION BELIEF(true);
   
    USES Auctioneer;
   
    CLASS actuator.RecordBid {
        module=auction;
    }
}

ACTION endAuction(?item) {
    PRECONDITION BELIEF(true);
    POSTCONDITION BELIEF(true);
    
    USES Auctioneer;
   
    CLASS actuator.EndAuction {
            module=auction;
    }
}

// Start an auction for the given item
BELIEF(wantToAuction(?item)) =>
COMMIT(?self, ?now, BELIEF(true),
    auction(?item)
);

PLAN auction(?item) {
    PRECONDITION BELIEF(true);
    POSTCONDITION BELIEF(true);
   
    BODY
        SEQ(startAuction(?item),
            FOREACH(BELIEF(client(?name, ?addr)),
                PAR(cfp(agentID(?name, ?addr), auction(?item)),
                    OR( DO_WHEN(BELIEF(fipaMessage(propose, sender(?name, ?addr2), bid(?item, ?amt))),
                            recordBid(?item, agentID(?name, ?addr2), ?amt)
                        ),
                        SEQ(DELAY(5),
                            recordBid(?item, agentID(?name, ?addr), 0)
                        )
                    )
                )
            ),
            PAR(FOREACH(BELIEF(winner(?item, ?agentID, ?amt)),
                    accept-proposal(?agentID, auction(?item, ?amt))
                ),
                FOREACH(BELIEF(loser(?item, ?agentID, ?amt)),
                    reject-proposal(?agentID, auction(?item))
                )
            ),
            endAuction(?item)
        );
}

Similarly to the #Bidder.afapl2 agent program, this program starts by importing the FIPACore AFAPL2 program, which defines any actions, perceptors and ontologies associated with FIPA ACL-based agent communication. The second statement then defines the associated "Auctioneer" Ontology of predicates that will be used to form beliefs in the program. This includes:

  • wantToAuction(?item): represents the fact that the agent wants to auction a specific item.
  • duplicateAuction(?item): represents the fact that there is already an auction taking place for that item.
  • unknownAuction(?item): represents the fact that there is no current auction for that item.
  • client(?name, ?addr): represents the fact that ?name is a client of the auctioneer and should be involved in any auction that takes place. Further, the client can be contacted via any of the transport addresses specified in ?addr.
  • winner(?item, ?agentID, ?amt): represents the fact that the agent with identifier ?agentID won the auction for ?item with a bid of ?amt.
  • loser(?item, ?agentID): represents the fact that the agent with identifier ?agentID lost the auction for ?item.

Following the Ontology declaration, a number of actions and perceptors are declared. All of these components make use of an internal Module which is declared first via the LOAD_MODULE construct. The module has an id "auction" and is implemented by the module.Auctions Java class, which is outlined below:

public class Auctions extends Module {
    Map<String, List<Bid>> auctions;

    @Override
    public void init() {
        auctions = new HashMap<String, List<Bid>>();
    }
   
    public boolean auctionExists(String item) {
        return auctions.containsKey(item);
    }
   
    public void startAuction(String item) {
        auctions.put(item, new LinkedList<Bid>());
    }
   
    public void endAuction(String item) {
        auctions.remove(item);
    }
   
    public void recordBid(String item, Bid bid) {
        auctions.get(item).add(bid);
    }
    
    public Collection<String> getItems() {
        return auctions.keySet();
    }
    
    public List<Bid> getBids(String item) {
        return auctions.get(item);
    }
}

This uses an auxhiliary class to store the bid information:

public class Bid {
    public String agentID;
    public int amount;
}

The auctionView Perceptor generates beliefs about the current state of any currently active auctions. As is indicated, via the USES statement, the beliefs that Perceptor generates should come from the "Auctioneer" Ontology. Specifically, as can be seen in the implementation below, this Perecptor generates beliefs using the winner and loser predicates.

--INSERT PERCEPTOR CODE HERE--

A useful note here is how the Perceptor gains access to the information stored in the Module. This is primarily through the getModuleByName(...) helper method that is provided by the AFAPL2 API, and which locates a Module instance based on a given Module identifier. In this case, the Module identifier is retrieved from the configuration of Perceptor which is declared in the curly brackets {...} at the end of the CLASS statement in the PERCEPTOR construct. As can be seen above, a configuration is declared that associates the key "module" with the value "auction", which is the identifier of the Module.


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' beliefs 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 (logical) model of this data (and consequently, the state of the environment).

The principle building block of the perception process is the Perceptor. This is a Java class that collates any relevant raw data and generates a corresponding set of beliefs. For instance, the example perceptor below generates a Belief that the agent has the ball:

package perceptor;

import com.agentfactory.logic.agent.Perceptor;

public class PlayerState extends Perceptor {
   public void perceive() {
       adoptBelief( "BELIEF(has(ball))" );
   }
}

This would generate a Belief of the form:

 BELIEF(has(ball))

In terms of an actual soccer playing agent, this Perceptor is not particularly useful because it always generates the belief that the agent has the ball, whether or not it actually has the ball. For a more realistic solution, the agent would need to have access to some underlying interface to the physical sensors of the robot (or virtual) soccer player. To do this, we need to make use of a Module, which is discussed in the Implementing a Module section of this guide. However, for the purposes of this example, lats assume the existence of a RobotBody Module and look at how it might be used to build a more realistic Perceptor:

package perceptor;

import com.agentfactory.logic.agent.Perceptor;

public class PlayerState extends Perceptor {
   public void perceive() {
       RobotBody body = (RobotBody) getModuleByName("body");
       if (body.hasBall()) {
           adoptBelief( "BELIEF(has(ball))" );
       }
   }
}

Now, we can see that the agent will only belief that it has the ball when it actually has the ball!

Finally, given the adoptBelief(...) method takes strings as input, it is quite easy to create beliefs that are based on variable data. For example, if we wanted to include a belief about the location of our soccer playing agent on the pitch, then we could use a Perceptor of the form:

package perceptor;

import com.agentfactory.logic.agent.Perceptor;

public class PlayerState extends Perceptor {
   public void perceive() {
       RobotBody body = (RobotBody) getModuleByName("body");
       adoptBelief("BELIEF(location(" + body.getX() + "," + body.getY() + "))");
       if (body.hasBall()) {
           adoptBelief( "BELIEF(has(ball))" );
       }
   }
}

Perceptors are associated with agents via the PERCEPTOR construct. This construct generates a mapping between specific Perceptors and a given AFAPL2 agent program. For example, when developing our robot soccer agent, the Player State Perceptor may be associated with the agent through the following statement:

 PERCEPTOR PlayerState {
     CLASS perceptor.PlayerState;
 }

This separation of concerns allows us to reuse our Perceptors in many different agent programs, even within a single application. Further, as is discussed in the Configuring Actuators, Perceptors, and Modules section, AFAPL2 allows developers to create quite generic actuators that can easily be configured for different scenarios. Another final twist to the Perceptor model is the recent introduction of support for Ontologies and the new belief creation mechanism that builds on this. Details of how to use an Ontology can be found in the Creating and Using Ontologies section.

Example 1: Implementing a Perceptor

This first example illustrates how to create your first AFAPL2 agent. this agent will contain a single perceptor, which will generate a single belief, representing the fact that the agent is “alive”.

Start this example by creating a new Basic Agent Factory Project in Netbeans via the "New Project" dialog box. For the purposes of the example, call the project "AliveExample". To follow the example with pre-written code, simply create a new "Example Alive Application" project.

Implementing the Perceptor

The main objective of this example is to create a simple AFAPL2 agent that uses a single perceptor. As a first step, we will begin by creating the perceptor and then discuss (in the next section) how to link the perceptor to 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. To do this, go to the File menu and select "New File...". Now, choose the "Agent_Factory" category and select "AFAPL2 Perceptor" in the File Types list. You will then be prompted to enter the name of the class "AlivePerceptor" and the package "alive". Once you click finish, template code will be generated as follows:

import com.agentfactory.logic.agent.Perceptor;

/**
 *
 * @author  rem
 */
public class Alive extends Perceptor {
    
    public void perceive() {
        //Perception Code goes here!
    }
}

Modify this template to match the following code:

package alive;

import com.agentfactory.logic.agent.Perceptor;

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

The above AFAPL2 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.

Compile the Java code normally (pressing F9 should do the trick).

Implementing the Agent

The second step in this example involves creating our first AFAPL2 agent. To do this, we will use Netbeans to create a template AFAPL2 program. To do this, go to "New File...", select the "Agent_Factory" category and choose the "Empty AFAPL2 File" file type. The file name for this file should be "alive.afapl2". When you click on finish, this file should be created. 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, the following output should be created:

init:
taskdefs:
deps-jar:
compile-single-afapl2:
Compiling 1 file in /Users/rem/NetBeansProjects/AliveExample/src
/Users/rem/NetBeansProjects/AliveExample/src/alive/alive.afapl2:7: PERCEPTOR declaration references unknown Perceptor class
        WARNING: Cannot find class: AlivePerceptor

Errors=0, Warnings=1
COMPILATION SUCCEEDED
Moving Files ...
destDir: /Users/rem/NetBeansProjects/AliveExample/build/classes
srcDir: /Users/rem/NetBeansProjects/AliveExample/src
includes: alive/alive.agent 
Moving 1 file to /Users/rem/NetBeansProjects/AliveExample/build/classes
BUILD SUCCESSFUL (total time: 1 second)

The purpose of the compilation step is to check that the syntax and (some) of the semantics of your program is correct. The output of the compilation process is a ".agent" file that contains a more machine readable version of your agent code. It is this file type that is used by the run-time environment to create agents.

In this case, we see that we get an warning 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 (as is indicated in the sample output below:

init: taskdefs: deps-jar: compile-single-afapl2: Compiling 1 file in /Users/rem/NetBeansProjects/AliveExample/src Errors=0, Warnings=0 COMPILATION SUCCEEDED Moving Files ... destDir: /Users/rem/NetBeansProjects/AliveExample/build/classes srcDir: /Users/rem/NetBeansProjects/AliveExample/src includes: alive/alive.agent Moving 1 file to /Users/rem/NetBeansProjects/AliveExample/build/classes BUILD SUCCESSFUL (total time: 1 second)

Configuring the Platform

To deploy an instance of our newly created agent program, we need to create a Main class as is outlined in the Java-Based Deployment Guide. This class is responsible for creating and initializing the agent platform programmatically. The Main class for this example is given below:

import com.agentfactory.afapl2.debugger.AFAPL2StateManagerFactory;
import com.agentfactory.afapl2.debugger.inspector.AFAPL2InspectorFactory;
import com.agentfactory.afapl2.interpreter.AFAPL2ArchitectureFactory;
import com.agentfactory.platform.core.DuplicateAgentNameException;
import com.agentfactory.platform.core.IAgent;
import com.agentfactory.platform.core.NoSuchArchitectureException;
import com.agentfactory.platform.impl.DefaultAgentPlatform;
import com.agentfactory.platform.impl.RoundRobinTimeSliceFixedScheduler;
import com.agentfactory.service.ams.AgentManagementService;
import com.agentfactory.visualiser.Debugger;
import java.util.HashMap;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Example Main class that illustrates how to deploy a basic Agent Factory
 * application.
 *
 * @author rem
 */
 public class Main {
     public static void main(String[] args) {
         // Create a new agent platform with a basic name and domain
         DefaultAgentPlatform platform = new DefaultAgentPlatform();
         platform.setName("test");
         platform.setDomain("ucd.ie");
 
         // Install a scheduling algorithm for executing the agents
         platform.setScheduler(new RoundRobinTimeSliceFixedScheduler());
 
         // Install and register the AFAPL2 Architecture Factory:
         // This enables support for instantiating AFAPL2 agents (i.e. agents
         // whose source code is identified by a .agent extension)
         AFAPL2ArchitectureFactory factory = new AFAPL2ArchitectureFactory();
         Properties props = new Properties();
         props.setProperty("TIMESLICE", "100");
         factory.configure(props);
         platform.getArchitectureService().registerArchitectureFactory(factory);
 
         // Install and start the Agent Factory Debugger
         Debugger debugger = new Debugger( platform );
         debugger.start();
 
         // Add the AFAPL2 Inspector & State Manager for more detailed debugging
         debugger.registerInspectorFactory(new AFAPL2InspectorFactory());
         debugger.registerStateManagerFactory(new AFAPL2StateManagerFactory());

         // Get a reference to the Agent Management Service so that the default
         // agent community can be created...
         AgentManagementService ams = (AgentManagementService) platform.getPlatformServiceManager().getServiceByName(AgentManagementService.NAME);
         try {
             // 1. Create an agent
             IAgent agent = ams.createAgent("alive", "alive/alive.agent");
         } catch (NoSuchArchitectureException ex) {
             Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
         } catch (DuplicateAgentNameException ex) {
             Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
         }
     }
}

As can be seem, the example agent is created by the following line of code:

             IAgent agent = ams.createAgent("alive", "alive/alive.agent");

The arguments to the createAgent(...) method are the name of the agent (to be created) and the relative URI of the AFAPL2 ".agent" file that contains the compiled version of the alive 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. When the application is deployed, the Agent Factory Debugger 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 the fifth screenshot on the right-hand side.

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, as is illustrated in the screenshot below.

Agent Factory Debugger after one iteration with expected output.

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, 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 that 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->AFAPL2 Actuator. 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 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 the excample Main class. Basically, we need to change the initial set of agents that are deployed on the agent platform. To do this, we simply replace the following line of code:

IAgent agent = ams.createAgent("alive", "alive/alive.agent");

With the following:

IAgent agent = ams.createAgent("helloworld", "helloworld.agent");

This will cause the helloworld agent to be created instead of the alive 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 {
    CLASS alive.AlivePerceptor;
 }

 ACTION helloWorld {
   PRECONDITION BELIEF(true);
   POSTCONDITION BELIEF(true);
 
   CLASS 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 Main class to create a firstRule agent.

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.afapl2.core.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.afapl2.core.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.afapl2.core.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.afapl2.core.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).

To deploy this application, we need to create an instance of the HTTP message transport service and register it with the agent platform. This is done by adding the following lines of code to the Main class (by convention, it is placed before the code that launches the debugger):

PlatformServiceManager manager = ((PlatformServiceManager) platform.getPlatformServiceManager());
try {
    props = new Properties();
    props.setProperty("port", "4444");
    manager.addService(HTTPMessageTransportService.class, HTTPMessageTransportService.NAME, 0, props);
} catch (NoSuchServiceException ex) {
    Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}

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 an the initial community of agents to test the code. In addition to the create agent line, this time we will also have to make use of the initialise(...) method, which is associated with every agent instance. This method can be used to give agents initial beliefs. The revised community initialisation code looks something like this:

IAgent agent = ams.createAgent("initiator", "ping.agent");
agent.initialise("BELIEF(wantToPing(responder, addresses(http://localhost:4444/acc)))");
agent = ams.createAgent("responder", "ping.agent");

The above code includes three instructions. The first and last instructions cause two agents to be created - out initiator and responder agents. The middle 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.

An interesting point about this initial belief 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 when the HTTP message transport service is registered with the agent platform. Specifically, the properties object was used to set the port number to 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;

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?

Simply put, all we need is two Main classes - one for each platform (in fact, it is possible to do it all in one main class if you wish). We typically use two classes so that it is clearer what goes where and to make sure that there are no conflicts in the deployment configuration. Each Main class should specify the initial configuration of the agent platform together with the initial community that is deployed. Details on how to create a main class can be found here.

We finish by revising example four so that the ping application is deployed over multiple agent platforms. To do this, you simply need to make a copy of the Main class, change the port number that the HTTP Message Transport Service is running on, and update the initial agent communities on each platform.

For example, if our main classes are Main (HTTP-MTS on port 4444) and Main1 (HTTP-MTS on port 4445), then the first Main class should look like this:

public class Main {
    public static void main(String[] args) {
        // Create a new agent platform with a basic name and domain
        DefaultAgentPlatform platform = new DefaultAgentPlatform();
        platform.setName("main");
        platform.setDomain("ucd.ie");

        // Install a scheduling algorithm for executing the agents
        platform.setScheduler(new RoundRobinTimeSliceFixedScheduler());

        // Install and register the AFAPL2 Architecture Factory:
        // This enables support for instantiating AFAPL2 agents (i.e. agents
        // whose source code is identified by a .agent extension)
        AFAPL2ArchitectureFactory factory = new AFAPL2ArchitectureFactory();
        Properties props = new Properties();
        props.setProperty("TIMESLICE", "100");
        factory.configure(props);
        platform.getArchitectureService().registerArchitectureFactory(factory);

        PlatformServiceManager manager = ((PlatformServiceManager) platform.getPlatformServiceManager());
        try {
            props = new Properties();
            props.setProperty("port", "4444");
            manager.addService(HTTPMessageTransportService.class, HTTPMessageTransportService.NAME, 0, props);
        } catch (NoSuchServiceException ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        }

        // Install and start the Agent Factory Debugger
        Debugger debugger = new Debugger();
        debugger.init(new HashMap<String, String>(), platform);
        debugger.start();

        // Get a reference to the Agent Management Service so that the default
        // agent community can be created...
        AgentManagementService ams = (AgentManagementService) platform.getPlatformServiceManager().getServiceByName(AgentManagementService.NAME);
        try {
            IAgent agent = ams.createAgent("initiator", "ping.agent");
            agent.initialise("BELIEF(wantToPing(responder, addresses(http://localhost:4445/acc)))");
        } catch (NoSuchArchitectureException ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        } catch (DuplicateAgentNameException ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

And the second Main class (Main1) would look like this:

public class Main {
    public static void main(String[] args) {
        // Create a new agent platform with a basic name and domain
        DefaultAgentPlatform platform = new DefaultAgentPlatform();
        platform.setName("main2");
        platform.setDomain("ucd.ie");

        // Install a scheduling algorithm for executing the agents
        platform.setScheduler(new RoundRobinTimeSliceFixedScheduler());

        // Install and register the AFAPL2 Architecture Factory:
        // This enables support for instantiating AFAPL2 agents (i.e. agents
        // whose source code is identified by a .agent extension)
        AFAPL2ArchitectureFactory factory = new AFAPL2ArchitectureFactory();
        Properties props = new Properties();
        props.setProperty("TIMESLICE", "100");
        factory.configure(props);
        platform.getArchitectureService().registerArchitectureFactory(factory);

        PlatformServiceManager manager = ((PlatformServiceManager) platform.getPlatformServiceManager());
        try {
            props = new Properties();
            props.setProperty("port", "4445");
            manager.addService(HTTPMessageTransportService.class, HTTPMessageTransportService.NAME, 0, props);
        } catch (NoSuchServiceException ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        }

        // Install and start the Agent Factory Debugger
        Debugger debugger = new Debugger();
        debugger.init(new HashMap<String, String>(), platform);
        debugger.start();

        // Get a reference to the Agent Management Service so that the default
        // agent community can be created...
        AgentManagementService ams = (AgentManagementService) platform.getPlatformServiceManager().getServiceByName(AgentManagementService.NAME);
        try {
            IAgent agent = ams.createAgent("responder", "ping.agent");
        } catch (NoSuchArchitectureException ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        } catch (DuplicateAgentNameException ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

Notice that the initial belief given to the "initiator" agent has no changed to reflect the fact that the "responder" agent is now on platform main1.ucd.ie (i.e. it is accessible via a HTTP-MTS service on port 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...

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 the PLAN construct.

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.afapl2.core.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 Main class used to deploy this example is basically the same as the one used in the earlier "ping agent" example. In fact, all that needs to change is the initial community of agents that are deployed on the platform:

IAgent agent = ams.createAgent("Rem", "chatter.agent");
agent.initialise("BELIEF(wantToChat(Fred, addresses(http://localhost:4444/acc)))");
agent = ams.createAgent("Fred", "chatter.agent");

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, for example:

IAgent agent = ams.createAgent("Rem", "chatter.agent");
agent.initialise("BELIEF(wantToChat(Fred, addresses(http://localhost:4444/acc)))");
agent = ams.createAgent("Fred", "chatter.agent");
agent.initialise("BELIEF(wantToChat(Bob, addresses(http://localhost:4444/acc)))");
agent = ams.createAgent("Bob", "chatter.agent");
agent.initialise("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;

[[Module|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 queuestate {
    CLASS 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), specifies the following simple ontology:

ONTOLOGY fipa-communication {
    PREDICATE fipaMessage(?perf, ?sender, ?content);
}

This allows you to write, for example, commitment rules, of the form:

BELIEF(fipaMessage(request, sender(?name, ?addr), createAgent(?id, ?type))) =>
COMMIT(?self, ?now, BELIEF(true), createAgent(?id, ?type));

If the fipa-communication ontology was not specified, then during compilation, the above commitment rule would generate a warning that the term fipaMessage(...) was not defined.

Ontologies and the AFAPL2 Compiler

Originally ontologies were introduced as a compile-time construct. That is, they were used by the compiler to check that all beliefs in an agent program are correctly written (i.e. that the functor is a valid functor and that the term has the correct number of arguments). This feature has been a particularly effective way of reducing the number of bugs in agent programs. For example, if the earlier commitment rule were written (erroneously) as follows:

BELIEF(fipaMessage(request, ?name, ?addr, createAgent(?id, ?type))) =>
COMMIT(?self, ?now, BELIEF(true), createAgent(?id, ?type));

Then the compiler would detect that there was an incorrect number of arguments and generate a warning that the above use of fipaMessage(...) term could not be found in any of the declared ontologies.

As of version 1.2.0 of the AFAPL2-DK, ontologies have been extended to be run-time concepts, where they are used as both a mechanism for improving the creation of beliefs in perceptors and actuators, and for automatically associating messages sent by the agent with the relevant ontology (if one exists).

For the first of these improvements, the ACTION and PERCEPTOR constructs have been extended as of version 1.2.0 to support the explicit declaration of the ontologies with which any beliefs they create are associated. This is achieved through the introduction of the USES keyword. For example, the com.agentfactory.core.fipa.perceptor.MessagePerceptor perceptor, which generates the beliefs about incoming messages, has been redeclared as follows:

PEREPTOR messages {
   USES fipa-communication;

   CLASS com.agentfactory.core.fipa.perceptor.MesssagePerceptor;
}

In tandem with this, the base Perceptor class has been extended to include a createBelief(...) method. This method takes a single argument as parameter - the functor of the belief you wish to create, and retrieves the definition of the term from the relevant ontology (which is enclosed within a corresponding Belief helper class). You are then able to use a bind(...) method to bind individual variables within that term to relevant constants. Finally, the adoptBelief(...) method has been overridden to support adoption of Belief objects. For example, the "messages" perceptor has been re-written to something like the following:

Belief belief = createBelief("fipaMessage");
belief.bind("?perf", message.getPerformative().toLowercase());
belief.bind("?sender", asSender(message.getSender()));
belief.bind("?content", message.getContent());
adoptBelief(belief);

If the functor passed to the createBelief method is not specified within any of the ontologies that are used by the perceptor, then null is returned, an similarly, if any of the bindings attempted do not appear within the template term (as is specified in the corresponding ontology), then a NoSuchVariable runtime exception is generated.

Similar support is provided through the Actuator base class.