PHAT-CLIENT TUTORIAL

The following examines the messaging and agent layers of the Multi-Agent Management (MAM) system, in addition to providing some step by step coding instruction. PLEASE REALIZE that it is intended primarily for programmers who want to start creating custom messages and agents of their own. Hopefully you have already taken a moment to look at the packages and agent types to get an overall sense of the project architecture.

As with most complex systems, the MAM System was designed and conceived in several layers. Conceptually speaking, the messaging and agent layers are the most important in understanding why the MAM System is unique and what is it trying to achieve.

It may be best to view the MAM System as a complex interpreter for an agent system. The main communications channel is the messaging layer, which consists of asynchronous Java-object based messages. The primary execution layer is the agent layer, composed of numerous independently running Java threads whose main form of I/O is the messaging layer.

NOTE: The MAM system requires you to use Apache Ant for compiling and jarring. Once installed you can use the build script we have included which automates everything for you. All you need to do is type 'ant' at the command prompt, from inside the 'mam/' directory. Once compiled, change directories to 'mam/deploy' to run the tutorial for testing ('java -jar mam-tutorial.jar' from the command prompt, or just double-click the mam-tutorial.jar file directly).

QUICK LINKS: Messaging and Agent Layers

Identity
Internetworking
Messaging

Creating A Simple Message
Creating A Simple Agent
Creating A Complex Message
Creating Pig Latin Agent
Creating An Agent GUI

Phat-Client Tutorial: Menuing and Functionality
Phat-Client Tutorial: Agent Attributes
Phat-Client Tutorial: Custom Look & Feel

Phat-Client Tutorial: [for programmers] Packages and Agent Types
Phat-Client Tutorial: [for programmers] Messaging and Agent Layers
Phat-Client Tutorial: [for programmers] MUE/MOO and MAM interfaces
Phat-Client Tutorial: [for programmers] MUE/MOO Game and Utility Code
Phat-Client Tutorial: [for programmers] Server-Side Scripts
Phat-Client Tutorial: [for programmers] MAM UML Documentation
Phat-Client Tutorial: [for programmers] MAM Javadocs

PROXY portal

IDENTITY

The notion of identity is very important for an agent system. There are really two sorts of identities within the MAM System: 1) a globally unique identity; and 2) a capabilities-based notion of identity.

The MamId class is our implementation of a unique identifier. To generate a unique MamId, just call mam.id.MamIdGenerator's generateUniqueMamId() method. This MamId is essentially a class that wraps a unique String with comparison and other utility methods. MamId's getUniqueIdString() method can be used to retrieve this String for use in other areas, such as the construction of a RoutingTag.

The RoutingTag serves as the foundation of our capabilities-based identity. A RoutingTag is used by the MAM System to route messages to a proper handler. How the routes are found and resolved is up to the implementation of the MAM System. For the time being, it is sufficient to view the routing as a black box. RoutingTags are used to indicate the source and destination of a message. Agents should register themselves on RoutingTags they are interested in.

Any messages sent to that RoutingTag can end up in that agent's incoming message queue, but it is not guaranteed. To send a message, a destination RoutingTag is set to indicate what demarcates the intended recipients of the message. For example, if you are interested in finding a Search Agent, you would send a general properties query message to any agents registered on the Search Agent routing tag. The state of a RoutingTag, like the MamId, is based upon a String. Construction of a RoutingTag follows standard Java syntax:

RoutingTag searchAgentRoutingTag = new RoutingTag( "SearchAgent" );

An agent registers its routing tags in the protected void registerRoutingTags() method. Normally an agent will register under its unique MamId and also under its type (for example: SearchAgent or InformationPersonaeAgent).

A quick check of the agent's globally accessible constants will let you know what it registers under. Note that an agent will also register tags in its parent classes.

Top

INTERNETWORKING

The MAM system is peer-to-peer. As such, each computer is connected directly to other computers via IP. At run-time a web of interconnections is created to facilitate collaboration through our message routing and agent discovery methods. Among the key terminology in this process are facility and node.

A facility is usually the internetworking component on an application-level. By running multiple facilities, the message routing is efficiently handled by exploiting locality. Most messages will be between agents in the same application, so we can reduce cross-node traffic in this manner. Conceptually, all agents interact with a facility.

A node is the layer below the facility. It is responsible for routing any traffic that needs to cross over the physical network. In the current design, there exists one facility and one node, rather than 'n' facilities and one node. This implementation was used for purposes of simplicity. Nevertheless, an agent designer doesn't need to worry about these concepts much. Focus on the routing tags and the locality of a message, and let the system take care of the actual routing.

Top

MESSAGING

The messaging layer is actually quite simple. Messages are represented in the system by Java classes, under the mam.messaging package, that are derived from the AgentMessage class. There is a bit of metadata used for the message envelope, but the most important to be aware of are: id, source, destination, reply-to, and locality.

The id is a MamId that uniquely identifies that instance of the message. The source is a routing tag to the originator of the message, usually a routing tag to an agent. The destination is a routing tag designating who should receive the message. The reply-to information is used to manage transactional context and is the basis for a synchronous chain of processing within the MAM System. The locality of a message is important for purposes of scalability. A message is propagated as far as possible by default. To restrict the message to the local system, use the setLocal method of a message.

Messaging normally follows a request/response pattern, a notification pattern, or a command pattern. In the request/response pattern, an agent will receive a request for some operation or information. The agent will then reply to that message with a response message, including either the information requested or some sort of processing conditional.

Usually the response message will be of the type EventDeclare*Message, where * is the information being provided. An example would be the GetAgentPropertiesMessage/EventDeclareAgentPropertiesMessage pair. In the case of a notification pattern, the agent will receive a message containing some sort of update or declaration which it is interested in, and will use to update either state or trigger a series of processing. Another example would be the EventDeclareAgentCreationMessage.

In the command pattern a request will contain some directive for the agent, which it can choose to execute. DoRemoveKnowledgeAgentMessage is a command-type message. Yet another example of a message is the GetQuoteMessage. This is understood by all agents in the system and is used to retrieve a quote from the agent. The code below demonstrates how to create an instance of a message:

GetQuoteMessage getQuoteMessage = new GetQuoteMessage( new RoutingTag( "me" ), new RoutingTag( "bob" ) );

To send that message, the following is used:

getFacility().sendAgentMessage( getQuoteMessage );

Top

CREATING A SIMPLE MESSAGE

We will now create a new message type. Each message is represented by a Java class under the mam.messaging package.

The following code skeleton can be used as the basis for the new message:

package mam.messaging;

import mam.internetworking.RoutingTag;
import mam.id.MamId;

public class SimpleMessage extends AgentMessage
{
  public SimpleMessage( RoutingTag source, RoutingTag destination ) 
  {
    this( source, destination, null );
  }
  
  public SimpleMessage( RoutingTag source, RoutingTag destination, MamId responseTo )
  {
    super( source, destination, responseTo );
  }
}

Save that file as "SimpleMessage.java" inside the 'mam/messaging/' directory and compile. Congratulations! You've created your first MAM message. Now, let's extend what we've done and actually use the message. In order to do that, we'll have to create a new agent.

Top

CREATING A SIMPLE AGENT

Okay, let's create our first agent. We will keep it quite simple. All the agent will do is repeatedly output a text string to the command line when executed. MAM agents reside in the mam.agent package and are derived from the Agent base class (though this isn't really a requirement, and when adding extensions to the system it may be easier to keep stuff outside of the main mam.* source tree). Depending on the needs of your agent, you can also derive from any of the other existing agents.

There are three main phases to the agent's life cycle: 1) initialization (routing tag registration, internal initialization, and restoration of state); 2) alive (normal message processing, idle time activity); and 3) shutdown (relinquishment of resources, notification, and persistence of state). We'll cover these topics as we construct and analyze the tutorial agent.

The following code skeleton can be used as the basis for the tutorial agent:

package mam.agent;

import mam.id.MamId;
import mam.internetworking.Facility;
import mam.internetworking.RoutingTag;
import mam.messaging.AgentMessage;
import mam.messaging.SimpleMessage;

public class TutorialAgent extends Agent
{
  public static final RoutingTag TUTORIAL_AGENT_ROUTING_TAG = new RoutingTag( "tutorial-agent" );
  
  //Constructor for the TutorialAgent object
  public TutorialAgent()
  {
    super();
  }
  
  //Gets the Quote attribute of the TutorialAgent object
  public String getQuote()
  {
    return "I am a mere tutorial";
  }
  
  protected void registerRoutingTags()
  {
    super.registerRoutingTags();
    try 
    {
      getFacility().register( TUTORIAL_AGENT_ROUTING_TAG, this );
    }
    catch( Exception e )
    {
      e.printStackTrace();
    }
  }
  
  public void handleSimpleMessage( AgentMessage m )
  {
    System.out.println( "Simply irresistable." );
  }
}

Save the file as "TutorialAgent.java" inside the 'mam/agent/' directory and compile. Congratulations! You've created your first MAM agent. Now, let's make things a bit more complex and have the agent do something that can be displayed in the phat-client during run-time.

Top

CREATING A COMPLEX MESSAGE

We will now create a more complex message type. Again, each message is represented by a Java class under the mam.messaging package. We'll be creating a pair of messages that conform with the request/response pattern that is used throughout the system. Our first message will be a request type message that we'll call "TutorialTranslationRequestMessage."

The following code skeleton can be used as the basis for the new message:

package mam.messaging;

import mam.internetworking.RoutingTag;
import mam.id.MamId;

public class TutorialTranslationRequestMessage extends AgentMessage
{
  private String m_englishSentence = "";
  
  public TutorialTranslationRequestMessage( RoutingTag source, RoutingTag destination, String englishSentence ) 
  {
    this( source, destination, null, englishSentence );
  }
  
  public TutorialTranslationRequestMessage( RoutingTag source, RoutingTag destination, MamId responseTo, String englishSentence )
  {
    super( source, destination, responseTo );
    setEnglishSentence( englishSentence );
  }
  
  public String getEnglishSentence()
  {
    return m_englishSentence;
  }
  
  public void setEnglishSentence( String englishSentence )
  {
    m_englishSentence = englishSentence;
  }
}

Save that file as "TutorialTranslationRequestMessage.java" inside the 'mam/messaging/' directory and compile. Congratulations! You've just created a more complex MAM message. Notice how the MAM messages leverage the standard JavaBean design pattern. You'll often encounter such industry standard patterns in the code and you should leverage their use in your own code whenever possible. Now, let's create the response message to complete the pair and we'll work on integrating their use into the tutorial agent.

The response message is pretty similar to the request, except for the class name and the payload, which is the Pig Latin string now.

The following code skeleton can be used as the basis for the response message:

package mam.messaging;

import mam.internetworking.RoutingTag;
import mam.id.MamId;

public class TutorialTranslationResponseMessage extends AgentMessage
{
  private String m_pigLatinSentence = "";
  
  public TutorialTranslationResponseMessage( RoutingTag source, RoutingTag destination, String pigLatinSentence ) 
  {
    this( source, destination, null, pigLatinSentence );
  }
  
  public TutorialTranslationResponseMessage( RoutingTag source, RoutingTag destination, MamId responseTo, String pigLatinSentence )
  {
    super( source, destination, responseTo );
    setPigLatinSentence( pigLatinSentence );
  }
  
  public String getPigLatinSentence()
  {
    return m_pigLatinSentence;
  }
  
  public void setPigLatinSentence( String pigLatinSentence )
  {
    m_pigLatinSentence = pigLatinSentence;
  }
}

Save that file as "TutorialTranslationResponseMessage.java" inside the 'mam/messaging/' directory and compile. Congratulations! The messages are ready to use, so now we just need to write a Pig Latin translation agent so that the message flow can be demonstrated.

Top

CREATING PIG LATIN AGENT

package mam.agent;

import java.util.StringTokenizer;
import mam.id.MamId;
import mam.internetworking.Facility;
import mam.internetworking.RoutingTag;
import mam.messaging.AgentMessage;
import mam.messaging.TutorialTranslationRequestMessage;
import mam.messaging.TutorialTranslationResponseMessage;

public class PigLatinAgent extends Agent
{
  public static final RoutingTag PIG_LATIN_AGENT_ROUTING_TAG = new RoutingTag( "pig-latin-agent" );
  
  //Constructor for the PigLatinAgent object
  public PigLatinAgent()
  {
    super();
  }
  
  //Gets the Quote attribute of the PigLatinAgent object
  public String getQuote()
  {
    return "I amhay a eremay utorialtay";
  }
  
  protected void registerRoutingTags()
  {
    super.registerRoutingTags();
    try 
    {
      getFacility().register( PIG_LATIN_AGENT_ROUTING_TAG, this );
    }
    catch( Exception e )
    {
      e.printStackTrace();
    }
  }

  public void handleTutorialTranslationRequestMessage( AgentMessage m )
  {
    // Note: Often message handlers are wrappers to internal utility functions that can be exposed for
    //   direct calls. In such cases, care much be taken to ensure that the code is thread-safe. All
    //   message handlers are executed in a thread-safe manner by default, but that assumes the agent
    //   only interacts via messages, not the combination of exposed methods and messages. 

    // Cast the agent message so that we can extract information specific to this message request.
    TutorialTranslationRequestMessage translationRequestMessage = (TutorialTranslationRequestMessage) m;
    String englishSentence = translationRequestMessage.getEnglishSentence();
    String pigLatinSentence = getEnglishToPigLatinTranslation( englishSentence );
    
    // Construct response message. Note the use of the response-to field.
    TutorialTranslationResponseMessage translationResponseMessage = new TutorialTranslationResponseMessage(
        getRoutingTagToSelf(), // source is the pig latin agent
        translationRequestMessage.getSource(),  // destination is the agent who sent us the request
        translationRequestMessage.getId(),  // response-to is the request message
        pigLatinSentence );  // the response's payload -- a translated string
        
    // Send the response message
    getFacility().sendAgentMessage( translationResponseMessage );
  }
  
  
  protected String getEnglishToPigLatinTranslation( String englishSentence )
  {
    StringBuffer sbPigLatinSentence = new StringBuffer();
    StringTokenizer st = new StringTokenizer( englishSentence, ".,!?:;()[] \t\n\r\f", true );
    while( st.hasMoreTokens() )
    {
      String englishWord = st.nextToken();
      if( englishWord.length() < 3 )
      {
        sbPigLatinSentence.append( englishWord );
      }
      else if( startsWithVowel( englishWord ) )
      {
        sbPigLatinSentence.append( englishWord ).append( "hay" );
      }
      else
      {
        String firstLetter = englishWord.substring( 0, 1 );
        if( firstLetter.equals( firstLetter.toLowerCase() ) )
        {
          sbPigLatinSentence.append( englishWord.substring( 1 ) ).append( firstLetter ).append( "ay" );
        }
        else
        {
          sbPigLatinSentence.append( englishWord.substring( 1,2 ).toUpperCase() ).append( englishWord.substring( 2 ) ).append( firstLetter.toLowerCase() ).append( "ay" );
        }
      }
    }
    
    return sbPigLatinSentence.toString();
  }
  
  
  protected boolean startsWithVowel( String word )
  {
    String[] asVowels = { "a", "e", "i", "o", "u" }; 
    
    word = word.toLowerCase();
    for( int idxVowel = 0; idxVowel < asVowels.length; ++idxVowel )
    {
      if( word.startsWith( asVowels[ idxVowel ] ) )
      {
          return true;
      }
    }
    return false;
  }
}

Save that file as "PigLatinAgent.java" inside the 'mam/agent/' directory and compile. Now, let's modify TutorialMain.java so that it starts the pig latin agent. In the createAgents() method, add the following code:

PigLatinAgent pigLatinAgent = new PigLatinAgent();
getFacility().migrateAgent( pigLatinAgent );

Now, let's modify TutorialAgent.java so that the translation sequence is kicked off when a SimpleMessage is received. Thus, in this context, the SimpleMessage becomes a command that initiates our complex messaging chain. Almost all interesting behavior is a result of complex multiple agent/messaging interactions.

We will need to modify the handleSimpleMessage( AgentMessage m ) method in TutorialAgent.java as follows:

public void handleSimpleMessage( AgentMessage m )
{
  TutorialTranslationRequestMessage requestMessage = new TutorialTranslationRequestMessage( getRoutingTagToSelf(), PigLatinAgent.PIG_LATIN_AGENT_ROUTING_TAG, "Hello world!" ); 
  getFacility().sendAgentMessage( requestMessage );
}

Next, we need to add the handler for the response message:

public void handleTutorialTranslationResponseMessage( AgentMessage m )
{
  TutorialTranslationResponseMessage responseMessage = (TutorialTranslationResponseMessage) m;
  System.out.println( "Translated: " + responseMessage.getPigLatinSentence() );
}

Voila! Now when you run the tutorial, the tutorial agent and Pig Latin agent will continually interact on a five second interval, driven by TutorialMain.java. In the next couple sections, we will decouple the activity driver from the main program and into a graphical user interface under the control of an agent.

Top

CREATING AN AGENT GUI

In many cases, the agents in a MAM system will run without a graphical user interface (GUI). This is simply because a lot of activity happens behind the scenes, as agents interact with one another, and with external resources. However, for the purposes of interacting with a human user it is sometimes convenient to associate a graphical user interface with an agent. Like a JavaBean, the MAM agents are constrained by patterns to provide a sensible and simple interface for creating agent behavior. None of this excludes the inclusion of a GUI, but conceptually it can be argued that it is not a clean design decision. Nevertheless, we shall explore the marriage of a graphical Swing interface to the tutorial agent.

Graphical UI resources can be initialized and shown in an agent's constructor, within the idleActivity() method, or in response to a message. Where this happens will affect the how the human user interacts with the agent, as well as how the agent makes its presence known to other agents. To start off we'll add the following imports at the top of TutorialAgent.java.

import java.awt.event.*;
import java.awt.*;
import javax.swing.*;

Now we'll need to add some member variable declarations into tutorial agent. Add the following code into the declaration section of TutorialAgent.java.

private JFrame m_guiFrame = null;
private JTextField m_englishText = null;
private JLabel m_pigLatinText = null;

Now, let's add some GUI code into the constructor.

public TutorialAgent()
{
  super();
  m_guiFrame = new JFrame();
  m_guiFrame.setBounds( 50, 50, 200, 80 );
  m_guiFrame.setSize( 350, 100 );
  m_englishText = new JTextField();
  m_pigLatinText = new JLabel();
  JPanel oPanel = new JPanel( new GridLayout( 2, 2 ) );
  oPanel.add( new JLabel( "English:" ) );
  oPanel.add( m_englishText );
  oPanel.add( new JLabel( "Translated:" ) );
  oPanel.add( m_pigLatinText );
  m_guiFrame.getContentPane().add( oPanel );
  JButton oButton = new JButton( "Translate" );
  oButton.addActionListener( new ActionListener()
  {
    public void actionPerformed( ActionEvent e )
    {
      TutorialTranslationRequestMessage requestMessage = new TutorialTranslationRequestMessage( getRoutingTagToSelf(), PigLatinAgent.PIG_LATIN_AGENT_ROUTING_TAG, m_englishText.getText() ); 
      getFacility().sendAgentMessage( requestMessage );
    }
  } );
  m_guiFrame.getContentPane().add( oButton, BorderLayout.SOUTH );
  m_guiFrame.show();
}

Note that the messaging sequence is now triggered via the GUI. You will probably want to remove the automated messaging loop in TutorialMain.java.

Ta-da! You've completed an agent with a GUI that collaborates with its fellow agents!

Please send comments to nideffer@uci.edu.