Jade Primer

4. Agent communication
(version 1.1, april 2004)

    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
A fundamental charactistic of multi-agent systems is that individual agents communicate and interact. This is accomplished through the exchange of messages and, to understand each other, it is crucial that agents agree on the format and semantics of these messages. Jade follows FIPA standards so that ideally Jade agents could interact with agents written in other languages and running on other platforms.

There are many auxiliary parts to a message in addition to the content, for example: the intended recipients, the sender and the message type. It is essential for the message as a whole to respect a common format. In JADE, messages adhere strictly to the ACL (Agent Communication Language) standard which allows several possibilities for the encoding of the actual content. In particular, Jade supports FIPA's SL (Semantic Language), a LISP-like encoding of concepts, actions and predicates. It also allows the content to be serialized Java objects. For simple application (like our tutorial examples), it is often easiest to treat the content as simply a String whose meaning is application dependent.

Structure of a JADE Message

Here is a list of all attributes of a Jade ACL message. As described in the API documentation, Jade provides get and set methods to access all the attributes. We put in bold the ones we use most.

When you create a message, you have to indicate its type - its performative in ACL lingo - and set the content. This is shown below:
    ACLMessage msg = new ACLMessage( ACLMessage.INFORM );
    msg.setContent("I sell seashells at $10/kg" );
Our message uses the most common performative: INFORM whereby one agent gives another some useful information. Other types are: QUERY to ask a question, REQUEST to ask the other to do something and PROPOSE to start bargaining. Performatives for answers include AGREE or REFUSE.

Sending and receiving messages

Then, assuming you have an Agent ID (AID) for the recipient, sending a message is fairly easy (we will show how to get AIDs later in this section):

    AID dest = ...;
    msg.addReceiver( dest );
    send(msg);
Note: We use addReceiver because there is no setReceiver method.... One reason is that we can add several receivers to the message and the one send broadcasts it to all of them.

Messages are routed to wherever the destination agent resides and are placed in its message queue. There are 2 basic ways for the receiver to get its messages. By using blockingReceive(), the receiving agent suspends all its activities until a message arrives:

	ACLMessage msg = blockingReceive();
The second method, with receive(), examines the message queue, returning a message if there is one or null otherwise. This is the normal technique used when an agent is involved in parallel activities and has multiple active Behaviours.
	ACLMessage msg = receive();
	if (msg != null) 
		<.... handle message...>
	else
		<... do something else like block() ...>
These methods have variants with additional arguments to alter their behaviour. With blockingReceive(), one can add a pattern object to select the kinds of message that we want to receive. We can also specify a timeout. If the timeout expires before a desired message arrives, the method returns null. More details can be found in the Jade API.

With receive(), since the method never blocks, only the selector pattern argument makes sense. Below we show the code for Receiver.java which prints out copies of all messages received.

        public class Receiver extends Agent 
        {
            protected void setup() 
            {
                addBehaviour(new CyclicBehaviour(this) 
                {
                     public void action() 
                     {
                        ACLMessage msg= receive();
                        if (msg!=null)
                            System.out.println( " - " +
                               myAgent.getLocalName() + " <- " +
                               msg.getContent() );
                        block();
                     }
                });
            }
        }
Note the use of block() without a timeout. This puts the behaviour on hold until the next message is received, at which time all behaviours wether waiting for messages OR the passage of time ( with block(dt) ) will be activated and their action methods called one after the other. As we said before,

block doesn't stop execution;
it just schedules the next execution

Note that, if you don't call block(), your behaviour will stay active and cause a LOOP. Generally all action methods should end with a call to block() or invoke it before doing return.

Note: here we sneaked in a new type of behaviour, the CyclicBehaviour, which stays active as long as its agent is alive. This is exactly what we need to handle message reception and many of the examples in this section will use this behaviour. Actually, we could easily achieve the same effect by providing a done method which always returns false which is exactly how CyclicBehaviour operates but by using CyclicBehaviour, we don't need to specify done.

Answering messages

All messages have an attribute which contains the ID of the sender. Thus, we can answer a message as follows:
	ACLMessage msg = receive();
	
    ACLMessage reply = new ACLMessage( ACLMessage.INFORM );
    reply.setContent( "Pong" );
    reply.addReceiver( msg.getSender() );
    send(reply);
To simplify answering, Jade provides a method createReply() which creates a new message with the sender and receiver attributes switched and all other attributes set correctly. Generally, only the content and performative have to be modified before sending it back. Below, we show the action method from Pong.java, a modified Receiver agent which answers all messages with a "Pong":
             public void action() 
             {
                ACLMessage msg = receive();
                if (msg!=null) {
                    System.out.println( " - " +
                       myAgent.getLocalName() + " <- " +
                       msg.getContent() );
                       
                    ACLMessage reply = msg.createReply();
                    reply.setPerformative( ACLMessage.INFORM );
                    reply.setContent(" Pong" );
                    reply.send();
                 }
                block();
             }
In this example, there isn't much advantage to using createReply, the real benefits apply in applications when other attributes like conversationID or ontology have to correspond to the original message.

Finding agents to talk to

Now, we return to the problem of how to locate other agents and obtain their AIDs so that we can send them messages. We've already seen that when we receive messages, we can obtain the AID of sender - actually, with createReply, we don't even need the sender's ID.

We can also create agent IDs from the agent's local name, for example the one specified on the command line when starting a container. This is especially useful when fixed names for certain agents have been decided beforehand. Assume that we have an application with buyers which needs to interact with two suppliers called "store1" and "store2" and that the program will be started as follows:

	% java jade.Boot fred:BuyerAgent  store1:SellerAgent  store2:SellerAgent
Then within the buyer agent we could broadcast a message to the stores as follows. Note the AID contructor which takes both a local name and the ISLOCALNAME constant.
     ACLMessage msg = new ACLMessage(ACLMessage.INFORM);
     msg.setContent( "bla...bla...bla" );
     for (int i = 1; i<=2; i++)
        msg.addReceiver( new AID( "store" + i, AID.ISLOCALNAME) );
     send(msg);
The program Sender.java uses this technique to send "Ping" messages to agents "a1" and "a2". To test this try first:
      % java jade.Boot main:Sender a1:Receiver a2:Pong
where Receiver and Pong are the classes previously discussed. Then try this more elaborate version setting up "a1" and "a2" in one container:
      % java jade.Boot main:Sender a1:Receiver a2:Pong
and querying them from another container:
      % java jade.Boot -container main:Sender 

There are other ways to get agent IDs:

  1. using JADE's GUI interface
  2. from a directory entry in the DF (the normal method)
  3. from the AMS (Agent Management Service) which keep the IDs of all active agents

The first possibility, using the GUI interface to create agents and send them messages is the technique used by David Grimshaw in his JADE Tutorials.

The second technique involves searching the DF (yellow page service) to find agents which provide needed services. This is the usual way of discovering new agents IDs and we'll look at it in the next chapter.

We will use the third technique based on the other agent registry, the AMS.

Searching the AMS

The following shows how to search the AMS to get an array of all presently known agents. The full example is in AMSDump.java:
        // Required imports
    
        import jade.domain.AMSService;
        import jade.domain.FIPAAgentManagement.*;
    
        ...
        AMSAgentDescription [] agents = null;
        
        try {
            SearchConstraints c = new SearchConstraints();
            c.setMaxResults ( new Long(-1) );
            agents = AMSService.search( this, new AMSAgentDescription (), c );
        }
        catch (Exception e) { ... }
The parameters to search include: 1) the searching agent (this), 2) an AgentDescription which could be used filter agents and 3) contraints on the number of agents returned (-1 means ALL). The descriptors returned are not AIDs; they're AMSAgentDescriptions. To extract the AID from these descriptors we have to use something like agents[i].getName(). Here is the loop which uses another getName() - confusing isn't it !!! - to extract printable names from the AID. We also compare the AIDs to our own so that we can detect other agents. Note the use of equals not "==".
        AID myID = getAID();
        for (int i=0; i<agents.length;i++)
        {
            AID agentID = agents[i].getName();
            System.out.println(
                ( agentID.equals( myID ) ? "*** " : "    ")
                + i + ": " + agentID.getName() 
            );
        }
To test AMSDump, we created a main container with 3 user agents: a, b and xxx.
	jean% java jade.Boot a:Pong b:Pong xxx:Receiver
and then ran AMSDump.
	jean% java jade.Boot -container dump:AMSDump

	    0: xxx@Jeans-Computer.local.:1099/JADE
	    1: ams@Jeans-Computer.local.:1099/JADE
	    2: df@Jeans-Computer.local.:1099/JADE
	*** 3: dump@Jeans-Computer.local.:1099/JADE
	    4: b@Jeans-Computer.local.:1099/JADE
	    5: a@Jeans-Computer.local.:1099/JADE
The list includes 2 system agents (DF and ams) as well as the dump agent and the 3 agents in the other container. Finally, the dump agent was able to recognize itself.

PingPong with agents from the AMS

In the next example, Comm1.java, the main agent sends Ping messages to all agents found from the AMS, including itself and the 2 system agents.
        ACLMessage msg = new ACLMessage(ACLMessage.INFORM);
        msg.setContent( "Ping" );

        for (int i=0; i<agents.length;i++)
            msg.addReceiver( agents[i].getName() ); 

        send(msg);
Before executing Comm1, we started a main container with the same 3 agents as in our previous example [ We anticipate and already show what was printed when Comm1 executed ]:
	jean% java jade.Boot a:Pong b:Pong xxx:Receiver

	 - b <- Ping
	 - a <- Ping
	 - xxx <- Ping
Comm1 also started a cyclic behaviour to print out answers to its Pings. This is the result:
	jean% java jade.Boot -container main:Comm1
	
	== Answer <- Ping from main@Jeans-Computer.local.:1099/JADE
	== Answer <-  Pong from b@Jeans-Computer.local.:1099/JADE
	== Answer <-  Pong from a@Jeans-Computer.local.:1099/JADE
Note that the first message is the one main sent to itself. Only a and b (Pongs) answered and we got no reply from the 2 system agents who probably assumed out messages to be spam and discarded them.

Templates for Selective reception

In our examples so far, we've used one behaviour to handle all the messages sent to our agent. Often we would like to filter message and set up distinct behaviours to handle messages from various agents or various kinds of messages. To do this, Jade provides Message Templates and a receive method which takes a template as a parameter and only returns messages matching that template. This is described in detail in section 3.3.4 of the Programmer's Guide.

More exactly, the MessageTemplate class provides static methods to create filters for each attribute of the ACLMessage. Further methods allow elementary patterns to be combined with AND, OR and NOT operators. The code required to build these matching expression seems complicated at first but with use you will realize that, even though the expressions are quite long-winded, the code is quite simple. Here the methods we have found most useful:

Here is how to set up a template for INFORM messages from agent "a1":
	MessageTemplate mt = 
	       MessageTemplate.and(  
	           MessageTemplate.MatchPerformative( ACLMessage.INFORM ),
	           MessageTemplate.MatchSender( new AID( "a1", 
                                                         AID.ISLOCALNAME))) ;
																	  
	. . .
	
	ACLMessage msg = receive( mt );
	if (msg != null) { ... handle message }
	block();
A bit wordy, as we said.....

To test this, we have created a Responder Agent which is a bit like Pong but it answers all messages with 2 messages: an INFORM and a PROPOSE. We will start one container with two of these agents "a1" and "a2".

    % java jade.Boot a1:Responder  a2:Responder
The main program, called Template will set up 2 behaviours, each coded to receive messages but the first will use with a template for INFORM messages from a1. The second will accept any kind of message. Actually this is BAD programming because messages for Behaviour 1 could be captured by Behaviour 2... a race condition ! Here are the two commands - to be entered in 2 windows.
    % java jade.Boot a1:Responder a2:Responder
    % java jade.Boot -container main:Template
We've arranged for each Template behaviour to print out trace messages whenever it is called, returning NULL if there was no matching message in the queue. Here is the trace of one execution .
	Behaviour ONE: gets NULL
	Behaviour TWO: gets NULL
	Behaviour ONE: gets 7 from a1= Gossip.....
	Behaviour TWO: gets NULL
	Behaviour ONE: gets NULL
	Behaviour TWO: gets 11 from a1= Really sexy stuff... cheap! 
	Behaviour ONE: gets NULL
	Behaviour TWO: gets NULL
	Behaviour ONE: gets NULL
	Behaviour TWO: gets 7 from a2= Gossip.....
	Behaviour ONE: gets NULL
	Behaviour TWO: gets 11 from a2= Really sexy stuff... cheap! 
	Behaviour ONE: gets NULL
	Behaviour TWO: gets NULL
Note:
  1. Lots of NULL messages! Although 4 messages were received, the behaviours were called 14 times; possibly as a result of system events such as messages from AMS. It is clear that your action methods will be called quite often, generally for events that don't concern them!
  2. The trace message prints out the performative which is supposed to be INFORM or PROPOSE... However these are coded as integer values and we gather from the trace that 7 stands for INFORM and 11 for PROPOSE.
  3. The template worked and Behaviour1 only received ONE message: INFORM (7) from a1, Behaviour2 received the other 3 messages.

Finally, as with all parallel systems, the order of events can change. Here is the trace of another execution. Close examination shows some variations.

	% java jade.Boot -container main:Template

	Behaviour ONE: gets NULL
	Behaviour TWO: gets NULL
	Behaviour ONE: gets 7 from a1= Gossip.....
	Behaviour TWO: gets NULL
	Behaviour ONE: gets NULL
	Behaviour TWO: gets 11 from a1= Really sexy stuff... cheap! 
	Behaviour ONE: gets NULL
	Behaviour TWO: gets 7 from a2= Gossip.....
	Behaviour ONE: gets NULL
	Behaviour TWO: gets NULL
	Behaviour ONE: gets NULL
	Behaviour TWO: gets 11 from a2= Really sexy stuff... cheap! 
	Behaviour ONE: gets NULL

Creating agents from other agents

In the final example, Comm2.java, our main agent creates a Pong agent in its own container, gives it a local name (Alice) and uses that name to create an agent ID which it uses to send Alice a series of messages. The output is shown below:
	jean% java jade.Boot Fred:Comm2
		
	+++ Sending: 0
	 - Alice <- Message #0
	+++ Sending: 1
	 - Alice <- Message #1
	+++ Sending: 2
	 - Alice <- Message #2
	+++ Sending: 3
	 - Alice <- Message #3
The code to create an agent whose name is "Alice" is:
        String name = "Alice" ;
        AgentContainer c = getContainerController();
        try {
            AgentController a = c.createNewAgent( name, "Pong", null );
            a.start();
        }
        catch (Exception e){}

NOTE:
Only agents of public classes can be created in this way

and for sending a message:

        ACLMessage msg = new ACLMessage(ACLMessage.INFORM);
        msg.setContent("Message #" + n );
        msg.addReceiver( new AID( name, AID.ISLOCALNAME ) );
        send(msg);
The extract from the Jade API shows the classes and methods used:

Defined in
Useful methods
Agent  AgentContainer getContainerController()
          Return a controller for this agents container.
AgentContainer  AgentController createNewAgent(String nickname, String className, Object[] args)
          Creates a new JADE agent, running within this container,

Passing arguments to newly created Agents

In the second section (First programs) of this primer, we showed how arguments could be given to an agent (ParamAgent) created from the command line. Similarly, an array of arguments can be provided for new agents in the third parameter of createNewAgent. Here is how we could create a ParamAgent with the same name and arguments as in our earlier example. Note that the arguments are passed as Objects; thus, simple types must be converted to Strings or Wrapper classes.
        Object [] args = new Object[2];
        args[0] = "3";
        args[1] = "Allo there";
	 
        String name = "Fred" ;
        AgentContainer c = getContainerController();
        try {
            AgentController a = c.createNewAgent( name, "ParamAgent", args );
            a.start();
        }
        catch (Exception e){}

Top
Previous
Next