6. Using Jade Behaviours
v0.9 sep 12, 2003


    Introduction
  1. Installing the software
  2. Your first programs
  3. Parallelism and Behaviours
  4. Agent communication
  5. Using the DF
  6. --> Using Jade Behaviours
  7. Using ontologies
  8. Graphical Interfaces
  9. Mobility

In our first chapter on Parallelism & Behaviours, we relied exclusively the SimpleBehaviour class. Later we resorted to the CyclicBehaviour class. However, Jade provides many other useful Behaviours which can be extended to model the complex activity typical of real agents. Basically there are 2 kinds of behaviour classes: primitive, like the Simple or Cyclic behaviours and composite ones which can combine both simple and composite behaviours to execute in sequence or in parallel. API documentation is available for primitive classes and the composite ones. There is also a section (3.4) on behaviours in the Programmer's guide.

Primitive Behaviours

Note: TickerBehaviour, WakerBehaviour and ReceiverBehaviour are conceptually subclasses of Cyclic and OneShot classes, but they are implemented as extentions of Simplebehaviour and Behaviour

Composite Behaviours

Jade provides other Behaviour but we consider them too complex for beginners. They include:

Remember that behaviours added directly to an Agent operate in parallel by default. The ParallelBehaviour is only useful when we require phases of parallel activity within more complex patterns such as Sequential or Cyclic activity.

Behaviour Methods

Most of the time, you just add behaviours and they disappear automatically when their job is done; however, it is also possible to remove them explicitely. Note that there are 2 versions of add and remove methods depending wether the behaviours are at the top level or part of a composite behaviour.
in Agent
void addBehaviour( Behaviour )
void removeBehaviour( Behaviour )

in CompositeBehaviour
void addSubBehaviour( Behaviour )
void removeSubBehaviour( Behaviour )

TwoStep revisited

In chapter 3, we considered the problem of combining a recurrent action which printed a message every 300 ms with a sequence where we output 2 messages at specified times. Here we will see how to use Jade's advanced behaviours to solve this problem more elegantly.

First for the Looping, our first solution, which relied on block(dt) to wait for dt msec, would not work in an normal situation with messages since ALL behaviours get activated when a message arrives wether the timeout has occurred or not. What we need is Jade's TickerBehaviour where the recurring actions is specified in the onTick method. Below we show how its use resolves concisely the Looping part of the problem:

   Behaviour loop = new TickerBehaviour( this, 300 )
      {
         protected void onTick() {
            System.out.println("Looper:" + myAgent.getLocalName());
         }
      });
      
   addBehaviour( loop );

The full program is in code/ch_6/Complex1.java and here is the output:

	jean% java jade.Boot fred:Complex1
	
	324: fred
	628: fred
	932: fred
	1242: fred
	1546: fred
	1849: fred
	2153: fred
	2457: fred
	.... stopped with CTL-C 

Now for the other part of our TwoStep, we want the agent to do a 4 things in order:

  1. wait 0.25 sec
  2. print out a first message
  3. wait 0.5 sec
  4. print out a second message
At first glance, it would seem an obvious application for a SequentialBehaviour combining WakerBehaviours for delays and OneShots for messages:
	SequentialBehaviour seq = new SequentialBehaviour();
	seq.addSubBehaviour( new WakerBehaviour( 250 ms) ...);
	seq.addSubBehaviour( <... OneShot to Print Msg 1 ...> );
	seq.addSubBehaviour( new WakerBehaviour( 500 ms) ...);
	seq.addSubBehaviour( <... OneShot to Print Msg 2 ...> );
	addBehaviour( seq );
However, this turns out to be overly complicated because the WakerBehaviour combines 2 actions: a delay followed by an action so that we just need two Wakerbehaviour. Here's our next attempt:

SequentialBehaviour seq = new SequentialBehaviour();
seq.addSubBehaviour( new WakerBehaviour( this, 250 )
   {
      protected void handleElapsedTimeout() {
         System.out.println( "... Message1" );
      }
   });

seq.addSubBehaviour( new WakerBehaviour( this, 500 )
   {
      protected void handleElapsedTimeout() {
         System.out.println( "... Message 2" );
      }
   });

addBehaviour( seq );

Execution of Sequence, an agent using this code gives the following incorrect result:

	jean% java jade.Boot s:Sequence
	
	254: ... Message1
	501:   ...and then Message 2
Incorrect because Message 2 should have been printed 500 ms after message1 not at t=500. Upon further reflection, we realized that the timeout specified for a WakerBehaviour represents a delay from the time the Behaviour is CREATED, not from the time it STARTS executing. In this simple case, it is easy to see that the second message should be printed at t=750 and changing the timeout for the second Waker to 750 solves the problem; however this defeats the whole purpose of the SequentialBehaviour which ensures sequentiality irrespective of the duration of each step. Here are two simpler solutions which don't use a SequentialBehaviour. In the first we just create two parallel Wakers to trigger at 250 ms and 750 ms respectively; to underline that sequence depends on the timestamps and not the order of creation, we choose to create the behaviour for the second message first. Note that we use addBehaviour directly on the agent and not addSubBehaviour on a sequence.
   addBehaviour( new WakerBehaviour( this, 750 )
      {
         protected void handleElapsedTimeout() {
            System.out.println( "... Message 2" );
         }
      });
   addBehaviour( new WakerBehaviour( this, 250 )
      {
         protected void handleElapsedTimeout() {
            System.out.println( "... Message1" );
         }
      });
In this second solution (used in Sequence1.java), we achieve sequentiality by creating the second Waker when the first finishes. In this case the timeout, can be set to the delay (500ms) and not to the absolute time (750ms).

addBehaviour( new WakerBehaviour( this, 250 )
   {
      protected void handleElapsedTimeout() 
      {
         System.out.println( "... Message 2" );
         addBehaviour( new WakerBehaviour( myAgent, 500 )
            {
               protected void handleElapsedTimeout() {
                  System.out.println( "... Message1" );
               }
            });
      }
   });

Note: the use of myAgent and not this when creating the second Waker.

This technique of creating the next Behaviour when the previous one finishes is quite common and reduces the need for SequentialBehaviour.

DelayBehaviour

If you wish to introduce delays as steps in SequentialBehaviours, the WakerBehaviour is not really suitable: its delay is computed from the time the behaviour is created. What we need is for the delay to apply from the time the behaviour starts. This can be achieved by modifying the code of the WakerBehaviour to obtain the DelayBehaviour class which computes the wakeuptime in the onStart() method. Whenever the behaviour is activated, we compute dt, the time remaining to wakeup. If it is negative, we call handleElapsedTimeout and "finish"; otherwise we block for the remaining time.

public class DelayBehaviour extends SimpleBehaviour 
{
   private long    timeout, 
                   wakeupTime;
   private boolean finished = false;
   
   public DelayBehaviour(Agent a, long timeout) {
      super(a);
      this.timeout = timeout;
   }
   
   public void onStart() {
      wakeupTime = System.currentTimeMillis() + timeout;
   }
      
   public void action() 
   {
      long dt = wakeupTime - System.currentTimeMillis();
      if (dt <= 0) {
         finished = true;
         handleElapsedTimeout();
      } else 
         block(dt);
         
   } //end of action
   
   protected void handleElapsedTimeout() // by default do nothing !
      { } 
            
   public boolean done() { return finished; }
}

The Sequence2 example uses the SequentialBehaviour with our Delay to solve the problem of the previous section. It also includes our CyclicBehaviour Looper to handle the whole TwoStep Problem.


Buyers and Sellers

In order to ilustrate the use of other Behaviours in Jade's library, we shall consider a simplified E-commerce situation where Buyer agents ask for quotes from Seller Agents and then proceed to purchase at the cheapest price - if it's within budget; if not, they ask for another round of quotes. Sellers answer requests with a (random) price which is valid for a limited time. The Sellers keep tabs on what price they quoted to each client and accept a purchase if the offered price is equal to the amount quoted AND if the request is received in time. Sellers must be able to handle several enquires in parallel. We model transmission delays by inserting delays between reception of a message and the reply. Here is a sketch of the messages exchanged.

Buyer                 Seller
                
  + ---> QUERY_REF ----> +
                         |
                       delay
                         |
  +  <--- INFORM <------ +
  |
delay 
  |
  +  --->  REQUEST ----> +
                         |
            AGREE        |
  +  <-----   or  <----- +
            REFUSE        

The knowledgeable reader will have noticed that this is a kind of Contract Net Protocol for which Jade provides some protocol classes but we will solve it step by step with available Behaviours.

In our case, there are several interesting problems that have to be solved.

  1. providing timeouts to limit the time spent waiting for answers.
  2. chaining Behaviours to achieve coherent conversations
  3. handling parallel requests
  4. handling parallel conversations
  5. dealing with unwanted messages left in the input queue
We now deal with these one by one.

Using timeouts

In our application, we don't want to wait forever for answers to requests. So we need a behaviour with a timeout mechanism which waits until either the required message arrives or a specified delay has passed. This is what Jade's ReceiverBehaviour was designed to do. There is an example of its use in the TestReceiverBehaviourAgent which is part of the Jade distribution. We will use extracts from that example to show how it works.

Basically, when you create the Behaviour, you specify a template for the message you wish to receive and a timeout. The Behaviour terminates when either the message arrives or the delay elapses. The problem we have with this class is that there is no way to specify what to do when the behaviour is done... You have to keep a reference to the Behaviour and poll it occasionally by testing done() or by trying to retrieve the message.

In the example, 2 ReceiverBehaviours are created, the first has no timeout and accepts any message; the creation parameters are -1 (no timeout) and null (no template), the second will wait a maximum of 40 seconds for an INFORM_REF message. We give the relevant code below.

   private ReceiverBehaviour be1, be2;

       // this behaviour waits until any message arrives
       
   be1 = new ReceiverBehaviour(this, -1, null); 
   addBehaviour(be1);

      // this behaviour waits for 40 seconds because no INFORM_REF message 
      // will arrive.
      
   be2 = new ReceiverBehaviour(this, 40000, 
               MessageTemplate.MatchPerformative(ACLMessage.INFORM_REF));
   addBehaviour(be2);
Later, if the behaviour has terminated one tries to extact the message (in a try...catch statement)
   ACLMessage msg;
    
   if (be1.done()) {
      try {    
         msg = be1.getMessage();
         <... we did get a message on time ...>
      } 
      catch (ReceiverBehaviour.TimedOut e3) { ...Timed out!!!...}
   }
   else
      < ...neither message received nor timeout elapsed ...>

To our way of thinking, this Behaviour leads to awkward code. Polling can be avoided by placing the behaviour within a SequentialBehaviour with a seperate behaviour to handle the message as the next step. However, you still have to execute getMessage in a try...catch block to determine wether the behaviour timed out or received a message.

A better Receiver

Actually, it's fairly easy to design a more convivial Receiver behaviour: one that works like Jade's but with the equivalent of an action method which is called when the behaviour is done and where you do your message handling. In our design the method (named handle) receives the message as a parameter or NULL if the timeout occurred first. Here is how it can be used in the same context as above: no polling, no try-catch structure and no variables reference the behaviour.

   addBehaviour( new myReceiver(this, 40000, 
         MessageTemplate.MatchPerformative(ACLMessage.INFORM_REF)
      {
         public void handle( ACLMessage msg ) 
         {  
            if (msg == null) 
               System.out.println("Timeout");
            else 
               System.out.println("Received: "+ msg);

         }
      });

The full code for myReceiver is can be found here and there is a Test program for it.

Implementing conversations

A common pattern in agent systems is that one agent sends a message and waits for a response. We already know how to send and receive messages but the added complication here is that we have to wait, not for any message, but for a message from the agent to which we sent the request and furthermore for an answer to our original request. This is a common pattern and FIPA has defined an extensive set of interaction protocols for these stereotypical situations. Jade provides classes which implement FIPA's protocols: These are discussed in section 3.5 of the Jade Programmer's guide and David Grimshaw has created some excellent web pages describing their implementation. He also shows a simple PingPong interaction using Jade's Initiator and Responder classes.

Our problems with these classes in the context of an introduction to JADE (and multi-agent systems) is that they are overly complex to understand and difficult to use. For industrial strength open systems which must interact agents created by other organizations, these protocols are the answer; but for one's first attempts at multi-agent applications, it is easier and clearer to implement the required interaction using Behaviours.

The key to implementation of interaction protocols is to assign to each conversation a unique identifier, the conversationID (CID). This is a well kwnown mechanism: there is already a field reserved for the CID in all ACLMessages and Jade's message templates allow the selection of messages based on conversationID. Furthermore, the createReply method sets the CID in the reply to be identical to that in the original message. By using the same CID for all messages exchanged during 2 agents in the course of a given negotiation, it is relatively trivial to separate the actions for one negotiation from those of another. The important things to do are:

Sneaking a peed at the code of Jade's protocol classes, we found that jade generates CIDs this way:
	String convId = "C"+hashCode()+"_"+System.currentTimeMillis();
The use of hashCode() is a good idea. It garanties that all agents in the same container will have unique CIDs .... but experiments show that agents from identical programs run in different containers often get identical hashCodes. Furthermore, our students trying various ways to generate CIDs also reported problems with the use of the system clock. On fast machines, several agents needing CIDs ended up with identical clock readings. After a bit of experimentation, here is the code we adopted.

   protected static int cidCnt = 0;
   String cidBase ;
   
   String genCID() 
   { 
      if (cidBase==null) {
         cidBase = getLocalName() + hashCode() +
                      System.currentTimeMillis()%10000 + "_";
      }
      return  cidBase + (cidCnt++); 
   }

The first part of the ID is private to each agent. It is based on the agent's local name - which seems to be globally unique - combined with the hashCode and the clock. To get faster generation of successive CIDs, the timestamp is replaced with a local counter. It's no a bad idea to provide all messages with a conversatonID. In many of our programs, we call utility methods to create and initialize ACLMessages and these automatically insert a unique CID. An example of these methods is shown below; the complete set of methods can be found in MessageCreation.txt.

   // Example of utility methods to initialize ACLMessages

   ACLMessage newMsg( int perf, String content, AID dest)
   {
      ACLMessage msg = newMsg(perf);
      if (dest != null) msg.addReceiver( dest );
      msg.setContent( content );
      return msg;
   }

   ACLMessage newMsg( int perf)
   {
      ACLMessage msg = new ACLMessage(perf);
      msg.setConversationId( genCID() );
      return msg;
   }

We can now design a simple Buyer and a Seller. The setup for the Buyer is given below. We use our utility methods to create the QUERY_REF message that will be sent to "s1" - the seller - and we create a Receiver Behaviour to handle the answer. The template conbines the conversationId and the message type (INFORM). We also fix a timeout of 1 second. Finally, we send the message and wait for the Behaviours to do their job.

Acheteur1.java

    protected void setup() 
    {
        ACLMessage msg = newMsg( ACLMessage.QUERY_REF, "",
                             new AID( "s1", AID.ISLOCALNAME) ); 
        
        template = MessageTemplate.and( 
            MessageTemplate.MatchPerformative( ACLMessage.INFORM ),
            MessageTemplate.MatchConversationId( msg.getConversationId() ));
             
        addBehaviour( new myReceiver(this, 1000, template )
          {
             public void handle( ACLMessage msg ) 
             {  
                if (msg == null) 
                   System.out.println("Buyer: Timeout");
                else 
                   System.out.println("Buyer received: $"+ msg);
    
             }
          });
        
        send ( msg );
    }

The Seller Agent is shown below. The core of the Agent is a Cyclic behaviour which waits for QUERY_REF messages and, for each, creates aDelayBehaviour to answer after a random time. To illustrate timeout behaviour in the Buyer, we deliberately choose delays ranging from 0-2 seconds so that half the answers will be late.

Vendeur1.java

public class Vendeur1 extends Agent 
{
    ...
    MessageTemplate template = 
         MessageTemplate.MatchPerformative( ACLMessage.QUERY_REF );    
        
    ACLMessage reply;
                                                 
    protected void setup() 
    {
      addBehaviour(new CyclicBehaviour(this) 
      {
         public void action() 
         {
            ACLMessage msg = receive( template );
            if (msg!=null) {
                   
        // we create the reply 
                reply = msg.createReply();
                reply.setPerformative( ACLMessage.INFORM );
                reply.setContent("" + rnd.nextInt(100));
                               
        // but only send it after a random delay
        
                addBehaviour( 
                  new DelayBehaviour( myAgent, rnd.nextInt( 2000 ))
                  {
                     public void handleElapsedTimeout() { 
                         send(reply); }
                  });
             }
             block();
         }
      });
}}

Note that this example uses two variables which are global to the behaviours' action (and setup) methods: template and reply. With template, this is just for efficiency reasons so that we don't create a new identical template for every message; but with reply, the situation is different. reply serves the crucial role of a conversation context variable allowing information from one behaviour to be transmitted to another. Actually, this simple program can only handle one request at a time, because simultaneous requests would require multiple copies of the variable reply. We will come back to this point later when showing how to handle parallel conversations.

Getting quotes in parallel

In our market problem, we assume the existence of multiple Sellers, in this case: "s1", "s2" and "s3". To determine which gives the best price, our Buyer has to send QUERY_REF messages to all three and wait for responses (or timeouts). Using Jade's composite behaviours, we use a Sequential behaviour where the first step is a Parallel behaviour containing three of our Receiver Behaviours.

                             |
           +-------- Parallel behaviour -------+
           |                 |                 |
  myReceiver(INFORM, 10s)    |     myReceiver(INFORM, 10s)
           |                 |                 |
           +        myReceiver(INFORM, 10s)    +               
            \                |                /
             +---------------+---------------+
                             |
                       OneShot: print result
                             |
                             

The Receivers compare the quoted prices to the current bestPrice and keep track of the best offer We specify that the Parallel behaviour should terminate when all 3 are finished. Control then passes to the next step which prints the best price. The code is shown below. Note that here the variables bestPrice and bestOffer act as a context to allow communication between the various behaviours.

Acheteur2.java

   int        bestPrice = 9999;
   ACLMessage bestOffer = null;
                                                 
   protected void setup() 
   {
      ACLMessage msg = newMsg( ACLMessage.QUERY_REF ); 

      MessageTemplate template = MessageTemplate.and( 
            MessageTemplate.MatchPerformative( ACLMessage.INFORM ),
            MessageTemplate.MatchConversationId( msg.getConversationId() ));
        
      SequentialBehaviour seq = new SequentialBehaviour();
      addBehaviour( seq );
         
      ParallelBehaviour par = new ParallelBehaviour( ParallelBehaviour.WHEN_ALL );
      seq.addSubBehaviour( par );
      
      for (int i = 1; i<=3; i++) 
      {
         msg.addReceiver( new AID( "s" + i,  AID.ISLOCALNAME ));
             
         par.addSubBehaviour( new myReceiver( this, 1000, template) 
            {
               public void handle( ACLMessage msg) 
               {  
                  if (msg != null) {
                     int offer = Integer.parseInt( msg.getContent());
                     if (offer < bestPrice) {
                        bestPrice = offer;
                        bestOffer = msg;
               }  }  }
            });
      }
      seq.addSubBehaviour( new OneShotBehaviour()
          {
             public void action() 
             {  
                if (bestOffer != null) 
                     System.out.println("Best Price $" + bestPrice );
                else 
                   System.out.println("Got no quotes");    
             }
          });
        
      send ( msg );

The result from running this example is shown below.

	jean% java jade.Boot s1:Vendeur1 s2:Vendeur1 s3:Vendeur1 fred:Acheteur2
	
	 - s3 <- QUERY from fred.  Will answer in 909 ms
	 - s2 <- QUERY from fred.  Will answer in 449 ms
	 - s1 <- QUERY from fred.  Will answer in 1266 ms
	
	Got offer $39 from s2
	Got offer $76 from s3
	
	Best Price $39 from s2

... and in another window

	jean% java jade.Boot -container jack:Acheteur2

	Got offer $11 from s1
	Got offer $91 from s3
	Got offer $22 from s2
	
	Best Price $11 from s1

Handling parallel conversations

A conversation is a sequence of behaviours which work together towards a common goal by passing information through common variables. We now consider how an agent can participate in such conversations at the same time.

Take our Seller agents which receive enquiries (QUERY_REF) and answer (INFORM) with prices quotations which are different for each customer and only valid for a certain time. After returning a quote, the Seller waits for a possible order (REQUEST) from the customer. However, for various reasons, the buyer may not place an order: he may have found a better deal, he may not be able to afford our product, his program may have crashed, etc, etc... Thus, for the sake of good business, we want our seller agent to be able to respond to other price requests even while it is waiting for orders to be placed.

This is different from the parallelism of the previous section where the buyer agent launched parallel behaviours to search for the best price. In that case, they were all part of the same conversation and all used the same variables: bestPrice and bestOffer. To allow parallel conversations, we must provide each conversation with its own set of common communication variables.

Typically, we have implemented conversations as Sequential behaviours whose sequence of steps was specified via the action methods of anonymous subBehaviours. The outline of the code is shown below. In this setup there are two sets of surrounding variables which are visible to the code in the various behaviours: those - VARS_1 - in the surounding object and those - VARS_2- in the surounding method.

public class ourAgent extends Agent 
{
   <... common instance attributes: VARS_1 ... >
	
   protected void makeConversation() 
   {
      <... common method variables; VARS_2 ... >
      
      SequentialBehaviour seq = new SequentialBehaviour ();
         
      seq.addSubBehaviour( new ...Receiver ()
         {
             public void handle (...) < ...actions of STEP1 ...>
         });
          
    .... add more subBehaviours...
      
      seq.addSubBehaviour( new ...Behaviour ()
         {
             public void handleTimeout () < ...actions of STEPn ...>
         });
          
    addBehaviour( seq );
  }

Unfortunately, to simplify memory management, Java is very picky about which global variables inner classes can use. In particular, they aren't allowed to reference variables declared in the surrounding methods. They can only use variables (such as VARS_1) which are attributes of surrounding classes. This policy makes for efficient stack management; it means that Java can clear the stack of activation records as soon as the methods (like makeConversation) terminate. So method environments like VARS_2 which are unique to each conversation can't be used to provide a permanent context to the conversation and the agent environment (VARS_1) which is useable is not restricted to one conversation but accessible to ALL.

The solution we now give is not the only one possible (or even the best one). However, it works and solves our problem. The idea is to put the method which creates the conversation within a class that represents the conversation. For each conversation, we create a distinct conversation object and call its setup method. The attributes of the conversation object can now be used as context by all the member behaviours.

Seller Agent

We show below the setup for Seller agents where we receive QUERY_REF messages and, for each, we create a Transaction object that should 1) build and schedule the behaviours required to send a quote and wait for a confirmation, and 2) whose attributes will enable those behaviours to share information. In particular, an important piece of chared data is the QUERY msg whose conversationID will serve as a unique identifier for the conversation.

public class Seller extends Agent 
{
   protected void setup() 
   {
      addBehaviour( new CyclicBehaviour(this) 
      {
         public void action() 
         {
            ACLMessage msg = receive( MessageTemplate.MatchPerformative
                                          ( ACLMessage.QUERY_REF ) );
            if (msg!=null) 
               addBehaviour( new Transaction(myAgent, msg) );
            block();
         }
      });
   }
   ...

To simplify matters, we have made the Transaction a subclass of SequentialBehaviour. We don't need that many commn variables: just the original message, msg, and the quoted price; the other attributes, reply, convID and template are not strictly necessary but simplify coding. The conversation consists of 2 behaviours:

These are created in the onStart() method which is called once by Jade after the object is created. It is the equivalent of setup() for Agents or init() for Applets.

   class Transaction extends SequentialBehaviour 
   {
      ACLMessage msg,
                 reply ;
      String     ConvID ;
      
      int    price  = rnd.nextInt(100);

      public Transaction(Agent a, ACLMessage msg) 
      {
         super( a );
         this.msg = msg;
         ConvID = msg.getConversationId();
      }
      
      public void onStart() 
      {
         addSubBehaviour( new DelayBehaviour( myAgent, rnd.nextInt( 2000 ))
         {
            public void handleElapsedTimeout() { 
               reply = msg.createReply();
               reply.setPerformative( ACLMessage.INFORM );
               reply.setContent("" + price );
               send(reply); 
            }
         });

         MessageTemplate template = MessageTemplate.and( 
            MessageTemplate.MatchPerformative( ACLMessage.REQUEST ),
            MessageTemplate.MatchConversationId( ConvID ));
        
         addSubBehaviour( new myReceiver( myAgent, 2000, template) 
         {
            public void handle( ACLMessage msg1) 
            {  
               if (msg1 != null ) 
               {
                  int offer = Integer.parseInt( msg1.getContent());
                     
                  reply = msg1.createReply();
                  if ( offer >= rnd.nextInt(price) )
                     reply.setPerformative( ACLMessage.AGREE );
                  else
                     reply.setPerformative( ACLMessage.REFUSE );
                  send(reply);
               } 
               else {
                  System.out.println("Timeout ! quote $" + price +
                      " from " + getLocalName() +
                      " is no longer valid");
               }
            }  
         });
      }
            
   }  // --- Transaction class ---

Buyer Agent

IN the Buyer, we use the Agent itself as repository of the common state and we build the Sequence of behaviours in setup() where we also send off the message which sets off the whole process: a QUERY_REF boradcast to the 3 Seller asking for price quotations. The Behaviours include:

To add a bit of spice to the whole thing, we use random numbers to guide our decisions and determine delays. As a result, there is a good chance that no suitable Supplier will be found or that messages arrive too late to be accepted. In case of failure, our Buyer sets off another round of negotiation by simply calling setup again.

public class Buyer extends Agent 
{
   Random     rnd  = newRandom();
   int        bestPrice = 9999;
   
   ACLMessage msg,
              bestOffer;

   protected void setup() 
   {
      bestPrice = 9999;
      bestOffer = null;
      
      msg = newMsg( ACLMessage.QUERY_REF ); 

      MessageTemplate template = MessageTemplate(  ));
            
      SequentialBehaviour seq = new SequentialBehaviour();
      addBehaviour( seq );
         
      ParallelBehaviour par = 
           new ParallelBehaviour( ParallelBehaviour.WHEN_ALL );
      
      for (int i = 1; i<=3; i++) {
         msg.addReceiver( new AID( "s" + i,  AID.ISLOCALNAME ));
             
         par.addSubBehaviour( new myReceiver( this, 1000, template) 
            {
               public void handle( ACLMessage msg) 
               {  
                  if (msg != null) {
                     int offer = Integer.parseInt( msg.getContent());
                     if (offer < bestPrice) {
                        bestPrice = offer;
                        bestOffer = msg;
                     }
                   }
               }
            });
      }
      seq.addSubBehaviour( par );
             
      seq.addSubBehaviour( new DelayBehaviour( this, rnd.nextInt( 2000 ))
         {
            public void handleElapsedTimeout() 
            {  
               if (bestOffer != null) {
                  ACLMessage reply = bestOffer.createReply();
                  if ( bestPrice <= 80 ) {
                     reply.setPerformative( ACLMessage.REQUEST );
                     reply.setContent( "" + rnd.nextInt( 80 ) );
                     send ( reply );
                  }
               }
             }
          });
      
      seq.addSubBehaviour( new myReceiver( this, 1000,
                 MessageTemplate( <...CID &  AGREE or REFUSE ...>) ) 
            {
               public void handle( ACLMessage msg) 
               {  
                  if (msg != null ) {                    
                     if( msg.getPerformative() == ACLMessage.AGREE)
                        System.out.println("  --------- Finished ---------\n");
                     else
                        setup();
                  } 
                  else {
                     System.out.println("==" + getLocalName() 
                        +" timed out");
                     setup();
                  }
               }  
            });
 
               
      send ( msg );
   }

Results

Here is the result of running the Sellers in the main container. We started off 2 Buyers, James and Helen, in another window....Click here to see that output. After a couple of rounds of negotiation, our agents come get to AGREE.

vor% java jade.Boot s1:Seller s2:Seller s3:Seller

 - s1 <- QUERY from james.  Will answer $14 in 485 ms
 - s2 <- QUERY from james.  Will answer $80 in 439 ms
 - s3 <- QUERY from james.  Will answer $24 in 402 ms
 - s2 <- QUERY from helen.  Will answer $76 in 268 ms
 - s1 <- QUERY from helen.  Will answer $20 in 1822 ms
 - s3 <- QUERY from helen.  Will answer $92 in 1128 ms
Got proposal $71 from helen & my price is $76
  == REFUSE
 - s1 <- QUERY from helen.  Will answer $91 in 565 ms
 - s2 <- QUERY from helen.  Will answer $9 in 602 ms
 - s3 <- QUERY from helen.  Will answer $80 in 1564 ms
Got proposal $68 from james & my price is $14
  == AGREE
Timeout ! quote $24 from s3 is no longer valid
Timeout ! quote $80 from s2 is no longer valid
Timeout ! quote $92 from s3 is no longer valid
Timeout ! quote $20 from s1 is no longer valid
Timeout ! quote $91 from s1 is no longer valid
Timeout ! quote $9 from s2 is no longer valid
Timeout ! quote $80 from s3 is no longer valid
 - s2 <- QUERY from helen.  Will answer $78 in 556 ms
 - s1 <- QUERY from helen.  Will answer $41 in 1223 ms
 - s3 <- QUERY from helen.  Will answer $22 in 564 ms
Got proposal $47 from helen & my price is $22
  == AGREE
Timeout ! quote $78 from s2 is no longer valid
Timeout ! quote $41 from s1 is no longer valid

The complete code is available for the Buyer agent and the Seller Agent.

Flushing orphan messages

To prevent infinite waiting, it is necessary to use timeouts; but what happens to the messages that arrive late? Well, they become orphans, unwanted by existing behaviours and they accumulate in the input queues of agents until they gobble up all available space. They also slows down operations - since every receive with a template will have to examine the orphan messages before getting to the ones they are looking for.

Short timeouts are not the only cause of orphans. In our agent competition, we found that agents often received messages that they hadn't programmed for or SPAM that they didn't want. These also waste space and time. So it would be useful to identify these orphan messages and remove them from the queue. When debugging, it is also useful to print out orphan messages to see if we should lengthen timeouts or provide special behaviours to handle them. But the question remains: how do we distinguish messages that haven't been treated yet from the real orphans - that will never be treated.

Our answer is to consider as orphans, any message that has been in the queue longer than a specified time, say 5 seconds. To do this we use a Ticker behaviour which keeps track (with a HashSet) of all the messages that were in the queue the last time it looked at it. The behaviour reads (and removes) all messages from the queue. If a message was seen in the previous, it is considered an orphan to be discarded (or dumped); otherwise it is placed in the set of new messages. Once the input queue is empty, we can return these new messages to the input queue with the very useful method Agent.putBack( msg ). The new set (called seen in the code below) then replaces the old set.

Clean-up agent
which removes orphan messages

class GCAgent extends TickerBehaviour { Set seen = new HashSet(), old = new HashSet(); GCAgent( Agent a, long dt) { super(a,dt); } protected void onTick() { ACLMessage msg = myAgent.receive(); while (msg != null) { if (! old.contains(msg)) seen.add( msg); else { System.out.print("+++ Flushing message: "); dumpMessage( msg ); } msg = myAgent.receive(); } for( Iterator it = seen.iterator(); it.hasNext(); ) myAgent.putBack( (ACLMessage) it.next() ); old.clear(); Set tmp = old; old = seen; seen = tmp; } }

The Garbage Collector or GCAgent above was developped to handle the late messages for the Buyer/Seller example in this chapter but it proved invaluable in the debugging. The reader will see that the class is present in the source of both examples though the lines to activate it are commented out.

Independent Random Generators

Random number generators are deterministic algorithms where sequence of numbers generated depends totally on the number given as initial value. Two generators which are initialized with the same value will give exatly the same set of numbers. In our market situation, the prices proposed come from random generators and it is important that the generator in each agent is initialized to a different value. By default, Java's generators are initialized from the system clock, System.currentTimeMillis. In general, this is good but in our case, all agents are generated at the beginning of execution and we discovered that many were created during the same clock cycle and had identical generators and proposed identical prices. The problem of getting independent generators is similar to that of getting unique conversationIDs and our solution is similar: initialize generators with a combination of System_time and HashCode. Here is the method we use to create independent generators.

    Random newRandom() 
    { return  new Random( hashCode() + System.currentTimeMillis()); }

Introduction
  1. Installing the software
  2. Your first programs
  3. Parallelism and Behaviours
  4. Agent communication
  5. Using the DF
  6. --> Complex behaviours
  7. Using ontologies
  8. Graphical Interfaces
  9. Mobility