Reactive Message Agent

From Agent Factory

Jump to: navigation, search

Contents

Editorial History

08/07/2008: Version 1 - Initial version of page

Introduction

In addition to the deliberative agent architectures that underpin the AFAPL and AFAPL2 agent oriented programming languages, Agent Factory also supports the creation of reactive agents via the Reactive Message Agent (RMA) architecture. This architecture is based on the simple example architecture that is presented as part of the Architecture / Interpreter Development Guide, and is used to implement the Robust FIPA Architecture infrastructure.

This page presents an overview of the overall structure of the RMA Agent Architecture. While it gives some sample code to illustrate the main features of this architecture, it does not provide any concrete examples that illustrate how to build agents. Instead, these examples are provided as part of the RMA Programmers Guide.

While Agent Factory can be run from the command line, by far the easiest way to get started is to make use of the Netbeans Plugin, which provides code templates, default projects, and support for deploying agent-based applications via the Agent Factory Run-Time Environment.

Getting Help

The RMA Architecture was developed to demonstrate how to integrate alternate agent architectures into the Agent Factory Run-Time Environment. Details of how to perform this integration can be found in the Architecture / Interpreter Development Guide. Should you have any additional questions, the best place to get answers and help is through the Source Forge Help Forum.

Overview of Architecture

The architecture is intended to support the fabrication of reactive agents. Specifically, it supports handling of:

  • Messages received from other agents that are handled by a MessageHandler; and
  • Events received from internal components that are handled by an EventHandler.

As is shown in the schematic below, these are augmented with a simple map-based memory is provided to allow information to persist and be shared across different message and event handlers.

In this architecture, the RMA Agent monitors an internal inbox (implemented by a core component of ALL agents known as the FIPAMessageQueue) for new messages. On receipt of a new message, the currently loaded message handlers are checked until either an appropriate message handler is located or there is no suitable message handler (in this latter case, the agent sends back a not-understood message). If an appropriate message handler is located, then it is invoked.

The message handler is able to do some combination of the following:

  • generate an appropriate response message
  • generate an internal event, which is passed to the agents event queue
  • perform operations on platform services
  • interact with some other component that is external to the core agent architecture
  • access or update the agents shared memory

In addition to handling incoming messages, the agent also handles events that are added to the event queue. These events may be generated by either a message handler or some other external (to the architecture) component. These events are processed in the same way as messages: a filter is used to check the relevant of each loaded event handler to the event, and each event handler is checked until either no suitable event handler is found (in which case, the event is ignored), or an event handler is found and executed. The event handler may do any combination of the operations identified for message handlers.

In contrast with languages such as AFAPL, RMA-style agents are purely Java entities and are created by extending the com.agentfactory.rma.ReactiveMessageAgent class which is provided as part of the RMA Development Kit, instances of the resultant agent implementations are then created by specifying relevant Java class as is discussed in both the Architecture / Interpreter Development Guide and the Application Deployment Guide.

Message Handlers

Message handlers are used to react to messages that are received from other agents. Logically, a message handler consists of two parts:

  • A filter part that checks whether the handler should be used to handle a given message, and
  • A handler part that specifies what the agent should do if the filter determines that a given message is relevant to the handler.

Message handlers are implemented by subclassing the com.agentfactory.rma.MessageHandler class.

For example, if you wish to develop an agent that implements a "ping-pong" style protocol (often used as part of a health monitoring system), where some agent sends a "ping" request and your agent must respond by informing the sender of "pong". The response part of this "ping-pong" protocol would be implemented using a message handler of the form:

   public class PingRequestHandler extends MessageHandler {
           super(agent, "request");
       }
           
       @Override
       public boolean filter(Message message) {
           return message.getProtocol().equals("ping-pong") &&
                   message.getContent().equals("ping");
       }
   
       @Override
       public boolean act(Message message) {
           StringMessage response = StringMessage.newInstance();
           response.setSender(agent.getAgentID());
           response.getReceivers().add(message.getSender());
           response.setPerformative("inform");
           response.setProtocol(message.getProtocol());
           response.setContent("pong");
           response.setConversationId(message.getConversationId());
           return send(response);
       }
   }

As can be seen, the filter(...) method checks that the protocol name is correct and that the content has the correct format. Here, the filter performs an exact match, however, where the content takes a more complex format, it is more usual for only a partial match may be used here. Should this filter return true, then the act(...) method is invoked. This method is responsible for actually handling the incoming message. While the example above simply constructs and sends a response, other more complex message handlers may perform some form of analysis and construct different response messages, or decide do to respond. In the case where the handler cannot handle the received message, the act(...) method should return false. This will cause the RMA agent to check if another Message Handler can handle the message, and if none is found, a not-understood message will be returned to the sender.

This Hessage Handler would typically be associated with the agent when it is created, as follows

   public class TestAgent extends ReactiveMessageAgent {
       public TestAgent(String name) {
           super(name);
       
           addMessageHandler(new PingRequestHandler(this));
       }
   }

Event Handlers

Similarly, event handlers are used to react to events that are generated by non-agent components that are linked to an agent. Logically, an event handler consists of two parts:

  • A filter part that checks whether the handler should be used to handle a given event, and
  • A handler part that specifies what the agent should do if the filter determines that a given event is relevant to the handler.

Event handlers are implemented by sub-classing the com.agentfactory.rma.EventHandler class.

By way of illustration the trivial example below uses an event handler to update a counter until it reaches 10.

   public class TestEventHandler extends EventHandler {
       public TestEventHandler(ReactiveMessageAgent agent) {
           this.agent = agent;
       }
   
       @Override
       public boolean filter(Event event) {
           return event.getType().equals("test");
       }
   
       @Override
       public void handle(Event event) {
           Integer value = (Integer) event.getData();
           System.out.println("Iteration: " + value.intValue());
           if (value.intValue() < 10) {
               value = new Integer(value.intValue() + 1);
               agent.addEvent(new Event("test", value));
           }
       }
   }

This event handler handles an event of type "test" and upon detection of such an event, extracts the integer value stored in the data part of the event, prints out the value, and if the value is less than 10, generates a new event of type "test", where the data is the integer value incremented by 1. This behaviour requires an initial event which can be declared when the associated agent is created.

   public class TestAgent extends ReactiveMessageAgent {
       public TestAgent(String name) {
           super(name);
       
           addEventHandler(new TestEventHandler(this));
           addEvent(new Event("test", new Integer(0)));
       }
   }

As can be seen, events are passed to the agent via the addEvent(...) method which is provided as part of the core RMA agent architecture implementation.

Shared Memory

The RMA Agent provides a shared memory that can be used to persist and share information between handlers. This memory takes the form of a map that associates String keys with Object values, allowing data to be stored in any appropriate format. The shared memory can be directly accessed within the agent implementation class via the "memory" field. For handlers, the memory can be accessed via the getMemory() method that is provided by the RMA Agent Architecture class.

For example, our example event handler could store the current value of the counter in memory as follows:

  public class TestEventHandler extends EventHandler {
      public TestEventHandler(ReactiveMessageAgent agent) {
          this.agent = agent;
      }
  
      @Override
      public boolean filter(Event event) {
          return event.getType().equals("test");
      }
  
      @Override
      public void handle(Event event) {
          Integer value = (Integer) agent.getMemory().get("counter");
          System.out.println("Iteration: " + value.intValue());
          if (value.intValue() < 10) {
              agent.getMemory().put("counter", new Integer(value.intValue() + 1));
              agent.addEvent(new Event("test", null));
          }
      }
  }

In our revised implementation, we could initialise the counter as follows:

  public class TestAgent extends ReactiveMessageAgent {
      public TestAgent(String name) {
          super(name);
      
          addEventHandler(new TestEventHandler(this));
          memory.put("counter", new Integer(0));
          addEvent(new Event("test", null));
      }
  }

Platform Services

The RMA Agent supports binding to and unbinding from platform services via associated methods bindToService(...) and unbindFromService(...) that are inherited from the generic agent class com.agentfactory.platform.core.Agent.