Agent Communication: Sending and Receiving Messages

As you know, messages are the basis of every MAS. They are the centre of the whole "computing as interaction" paradigm in which MAS are based. So it is very important to understand which facilities are present in the SPADE Agent Library to work with agent messages.

First and foremost, there is the Agent Identifier or AID. It is the information that identifies an agent and provides some information on how to communicate with it, i.e., its addresses. Every agent has a unique AID. In SPADE, that AID is composed of a unique name and a set of addresses which can be used to communicate with the agent. In most cases, an agent will use its JID as its name, and will have only one single (Jabber) address. For example:

Name = "agent@myhost.myprovider.com" . Addresses = ["xmpp://agent@myhost.myprovider.com"] .

The SPADE Agent Library uses the class spade.AID.aid to represent AID information. Translating the previous example into actual code would produce something like this:

import spade

identification = spade.AID.aid(name="agent@myhost.myprovider.com", addresses=["xmpp://agent@myhost.myprovider.com"])
		

Second, there is the class that represents a FIPA-ACL message. This class is spade.ACLMessage.ACLMessage , and you can instantiate it to create new messages to work with. The class provides several methods to construct and de-construct messages, based on the fields present in standard FIPA-ACL Messages. When a message is ready to be sent, it can be passed on to the send() method of the agent, which will trigger the internal communication process to actually send it to its destination. Here is a self-explaining example:

import spade

class MyAgent(spade.Agent.Agent):
	class InformBehav(spade.Behaviour.OneShotBehaviour):

		def _process(self):
			# First, form the receiver AID
			receiver = spade.AID.aid(name="receiver@myhost.myprovider.com", 
                                     addresses=["xmpp://receiver@myhost.myprovider.com"])
			
			# Second, build the message
			self.msg = spade.ACLMessage.ACLMessage()  # Instantiate the message
			self.msg.setPerformative("inform")        # Set the "inform" FIPA performative
			self.msg.setOntology("myOntology")        # Set the ontology of the message content
			self.msg.setLanguage("English")           # Set the language of the message content
			self.msg.addReceiver(receiver)            # Add the message receiver
			self.msg.setContent("Hello World")        # Set the message content
			
			# Third, send the message with the "send" method of the agent
			self.myAgent.send(self.msg)

	def _setup(self):
		print "MyAgent starting . . ."
		b = self.InformBehav()
		self.addBehaviour(b, None)

if __name__ == "__main__":
	a = MyAgent("agent@myhost.myprovider.com", "secret")
	a.start()
		

The previous example is also an opportunity to introduce the myAgent attribute that can be used in any behaviour. It is a reference to the agent that holds the behaviour and thus it can be used as a shortcut to access any method or attribute the agent object has. In this particular case, the command self.myAgent.send(self.msg) is accessing the send method of the agent through the myAgent shortcut.

There are two basic methods in message communication: send , which you already know, and _receive , which, as its name says, serves the purpose of receiving a message. At any given moment, a behaviour can make use of the _receive method in order to receive a message.

[Note] Regarding send and _receive

WARNING: Please take note that whereas send is a method of the agent, _receive is a method of the behaviour. This difference is very important. All the behaviours send messages the same way, but, as stated earlier, every behaviour has its unique message template that specifies which kind of messages it may receive, and thus, each behaviour uses its own _receive method to receive its own messages.

[Note] Regarding messages at initialization time

WARNING: A SPADE agent cannot send nor receive messages until its behaviours are active. That is, do NOT place calls to the send or _receive methods inside the _setup and takeDown methods.

The _receive method accepts two parameters: a boolean representing wether the behaviour must block waiting for a message, and the number of seconds it must wait before resuming execution. Of course the latter is only needed if the former is set to "True". If only the first parameter is present and set to "True", the behaviour will block indefinitely.

If there is a message for the behaviour calling the _receive method, the call will return an object of the spade.ACLMessage.ACLMessage class representing such message. If there is no message and the call is non-blocking or it times out, the return value will be None.

In order to set a message template for a behaviour, you must first define such template. Or you can set a behaviour to be the default behaviour, which means it will receive all kind of messages (no need to specify a template). To define a template you can use the classes spade.Behaviour.ACLTemplate and spade.Behaviour.MessageTemplate . First, you need to instantiate a spade.Behaviour.ACLTemplate object (which quite the same methods as a spade.ACLMessage.ACLMessage object) and edit it to match your requirements (i.e. set the ontology, the language, the protocol, etc...). Then, you must wrap it around a spade.Behaviour.MessageTemplate object. That is the object that you will pass on to the addBehaviour method, along with the behaviour itself. Let's see an example:

			
import spade

class MyAgent(spade.Agent.Agent):
	class ReceiveBehav(spade.Behaviour.Behaviour):
		"""This behaviour will receive all kind of messages"""

		def _process(self):
			self.msg = None
			
			# Blocking receive for 10 seconds
			self.msg = self._receive(True, 10)
			
			# Check wether the message arrived
			if self.msg:
				print "I got a message!"
			else:
				print "I waited but got no message"

	class AnotherBehav(spade.Behaviour.Behaviour):
		"""This behaviour will receive only messages of the 'cooking' ontology"""
	
		def _process(self):
			self.msg = None
			
			# Blocking receive indefinitely
			self.msg = self._receive(True)
			
			# Check wether the message arrived
			if self.msg:
				print "I got a cooking message!"
			else:
				print "I waited but got no cooking message"				

	def _setup(self):
		# Add the "ReceiveBehav" as the default behaviour
		rb = self.ReceiveBehav()
		self.setDefaultBehaviour(rb)
		
		# Prepare template for "AnotherBehav"
		cooking_template = spade.Behaviour.ACLTemplate()
		cooking_template.setOntology("cooking")
		mt = spade.Behaviour.MessageTemplate(cooking_template)

		# Add the behaviour WITH the template
		self.addBehaviour(ab, mt)				

if __name__ == "__main__":
	a = MyAgent("agent@myhost.myprovider.com", "secret")
	a.start()
		

In the previous example, there are a couple of behaviours. The ReceiveBehav behaviour will wait for a new message for 10 seconds and then print wether a message arrived or not (and then start over). The AnotherBehav will wait indefinitely for a "cooking" message. How do we specify a "cooking" message? In this case, by means of the cooking ontology. So, when the behaviours are instantiated in the _setup method of the agent, we create the cooking_template template (an object of the spade.Behaviour.ACLTemplate class) and set its ontology to cooking. This means that whenever a message arrives with its ontology set to cooking, it will match with the template. Then, we create an object of the spade.Behaviour.MessageTemplate class to wrap the cooking_template and add the AnotherBehav behaviour to the agent with the new message template associated (the second parameter of the addBehaviour method).