Interaction Protocols

When two agents interact, a single message is rarely enough to ensure that the interaction will result in a desired behaviour. Instead, agent interaction typically involves the exchange a sequence of messages whose ordering and types are based on some form of interaction protocol.

An interaction protocol is the common sense equivalent of a protocol in networking. It defines a sequence of message exchanges between two (or more) agents. The sequence of message exchanges is based on the types of messages that are exchanged, which participant should send each message, and the order in which they should be exchanged. FIPA has defined a number of standard protocols, which are listed here.

In this lesson, we will explore how to implement one of the simpler of the FIPA protocols, known as the FIPA Request Protocol.  An Agent UML Sequence diagram representing this protocol is shown below.

The FIPA Request Protocol is designed to allow an agent to ask another agent to perform a task. The Initiator is the agent that needs the task to be performed and the Participant is the agent that it is asking to perform the task. The flow of the protocol is fairly straight forwards: the Initiator requests that the Participant perform a task. The participant decides whether or not to perform the task and either agrees or refuses to perform the task.  If the Participant agrees to perform the task, then, once the agreed task is performed, it should inform the Initiator that either the task is completed or (if necessary) the outcome (result) of the task.  In the event that the Initiator fails to complete the agreed task, it is required to inform the Initiator of the failure of that task.

To illustrate this, lets consider an example of an Authentication agent that is able to authenticate username / password tuples when requested to by trusted agents.

An Authentication Agent

To implement our example, lets us first look at the Authentication agent as this agent implements the task that is to be performed.  To implement this agent, we need to make use of two types of belief:

  • trusted(string X): is a belief that indicates that agent X is trusted.
  • credentials(string U, string P): is a belief that holds user credentials – username (U) and password (P).

Next, lets consider the core behaviour – the Authentication agent receives a request message from another agent asking that it authenticate a username/password tuple.  this can be modelled using the following rule:

agent Authentication {
    rule @message(request, string X, validate(string U, string P) {
    }
}

The first step of the protocol involves deciding whether or not to accept the request.  In the description of our proposed solution, the Authentication agent will agree to authentication requests from agents that it trusts and will refuse requests from agents that it does not trust.  To implement this, we can use the trusted(…) belief as follows:

agent Authentication {
    rule @message(request, string X, validate(string U, string P) {
        if (trusted(X)) {
            send(agree, X, validate(U));
        } else {
            send(refuse, X, validate(U));
        }
    }
}

Now, the final step involved in implementing our authentication protocol is to add additional code to actually validate the user. For this, we can use a query statement:

agent Authentication {
    rule @message(request, string X, validate(string U, string P) {
        if (trusted(X)) {
            send(agree, X, validate(U));
            query(credentials(U, P));
            send(inform, X, validated(U));
        } else {
            send(refuse, X, validate(U));
        }
    }
}

Note, that the above code makes an assumption that the user credentials will be validated. Also, the behaviour of the query statement is that failure of the query = failure of the plan.  This means that we need to cater for the possibility that the credentials will be incorrect.  To do this, we change from using a query statement to using an if statement:

agent Authentication {
    rule @message(request, string X, validate(string U, string P) {
        if (trusted(X)) {
            send(agree, X, validate(U));
            if (credentials(U, P)) {
                send(inform, X, validated(U));
            } else {
                send(inform, X, invalid(X, P));
            }
        } else {
            send(refuse, X, validate(U));
        }
    }
}

This completes the core implementation of the authentication agent. Now, we need to implement an agent to test this behaviour:

agent AuthenticationTest {
    module Console C;

    initial !init();

    rule +!init() {
        send(request, "authenticator", validate("rem", "password"));
    }

    rule @message(refuse, string X, validate(string U)) {
        C.println(X + " has refused to validate: " + U);
    }

    rule @message(agree, string X, validate(string U)) {
        C.println(X + " has agreed to validate: " + U);
    }
}

ff

Integrating Timeouts into Conversations

Sometimes you need to develop a solution that allows an agent to send out a number of messages and then wait a fixed amount of time for responses. This scenario requires the implementation of a timeout mechanism. The code below reflects a further updated Master agent that includes a timeout:

  package examples;
  
  agent Master {
      module Console C;
      module System S;
    
      initial !init();
  
      rule +!init() {
          S.createAgent("rem", "examples.Slave");
          S.sleep(5000);
          !add(3, 5);
      }
  
      rule @message(inform, string sender, state(string X)) {
          C.println(sender + " is alive!");
          +slave(sender);
      }
  
      rule +!add(int X, int Y) : slave(string slave) {
          send(request, slave, add(X, Y));
          !!timeout("add", 1000); // timeout set for 1 second...

          when(added(X, Y, int Z) | timeout("add")) {
              -timeout("add"); // remove timeout flag just in case...
              if (added(X, Y, int Z2)) {
                  C.println("3+5=" + Z2);
              }
          }
      }
	    
      rule @message(inform, string sender, added(int X, int Y, int Z)) {
          +added(X, Y, Z);
      }
      
      rule +!timeout(string id, int length) {
          system.sleep(length);
          +timeout(id);
      }
  }

Here, the timeout is implemented through a new !timeout(…) goal. That takes an id and a timeout duration as parameters. This goal is added whenever the agent wishes to have a timeout and the when(…) statement is modified to be satisfied by either the original condition or the timeout belief (which is generated by the new rule). Notice that the timeout goal is added as a spawned goal instead of a subgoal – this means that the timeout runs as a separate intention that is executed in parallel to the “conversation” intention.