4 Mobile Agents

This chapter shows how to program mobile agents in Mozart. We call agent any distributed computation that is organized as a set of tasks. A task is a computation that uses the resources of a single site. By resource we mean the technical definition given in Section 2.1.5, for example, file system, peripherals, networking abilities, operating system access, etc. A task can initiate tasks on other sites, with well-defined specifications of what resources they should use. The distributed behavior of the agent is therefore in first instance decided by the agent itself.

Agents are therefore just resource-aware distributed computations, which can exist on one site or be spread out over more than one site. This definition of agent might seem unnecessarily general, but it is natural in Mozart, where it is as easy for an application to run on one site or a set of sites. For example, here's an agent A that concurrently delegates 10 tasks to remote sites and waits until all are done before continuing. The agent servers are represented by AS0 and AS9 and the work is represented by functors containing the one-argument procedures P0 to P9. Each procedure binds its argument to the result of its calculation.

declare 
A=functor 
  define 
     X0 ... X9
     {AS0 functor define {P0 X0} end}
     ... 
     {AS9 functor define {P9 X9} end}
     {Wait X0} ... {Wait X9}
     ... 
  end

This is efficient. As the distribution model makes clear (see Section 2.1.3), the termination of each remote task is signaled to the original task by exactly one network message, which contains the result of the task's calculation.

The Mozart vision of a universe of agents is a set of fixed places, the agent servers, and a set of evolving computational ``webs'', each potentially covering many places at once. A web is what we call an ``agent''.

4.1 An example agent

Let's start with a very simple example of an agent that goes somewhere, interrogates the operating system, and then comes back. We show the example in two parts. First, we show how to install agent servers so that the agent has somewhere to go. Then, we will program the agents.

4.1.1 Installing the agent servers

To go to a site, there has to be something at the site that accepts the agent. We call it an agent server. An agent server accepts functors (which represent agents or parts of agents) and applies them on its site. To install an agent server on a site, we use the functor AgentServer (see Section 4.1.6, below). When AgentServer is installed on a site, then it creates an agent server module AS on that site along with the following operations:

Let's say that we know an URL "http://www.info.ucl.ac.be/~pvr/agents.ozf" that references the functor AgentServer. Then the following code creates a local agent server and makes it accessible through the URL "http://www.info.ucl.ac.be/~pvr/as1":

declare GetServer in 
local 
   % Get the AgentServer:
   AgentServer={Pickle.load "http://www.info.ucl.ac.be/~pvr/agents.ozf"}
   % Install AgentServer locally: (this creates an agent server)
   [AS1]={Module.apply [AgentServer]}
   % Publish the agent server:
   {AS1.publishserver "/usr/staff/pvr/public_html/as1"}
in 
   GetServer=AS1.getserver
end

This code also creates the procedure GetServer as defined above.

Let's create a second agent server, a remote one, and make it accessible through the URL "http://www.info.ucl.ac.be/~pvr/as2":

local 
   RF=functor import Pickle Module export done:D
      define 
         AgentServer={Pickle.load "http://www.info.ucl.ac.be/~pvr/agents.ozf"}
         [AS2]={Module.apply [AgentServer]}
         {AS2.publishserver "/usr/staff/pvr/public_html/as2"}
      end 
   RM={New Remote.manager init}
   M={RM apply(RF $)}
in 
   skip 
end

In this case, the three lines of code that do the work of installing the agent server and publishing it are put inside the functor RF, and RF is installed remotely.

Now let's get access to both of these agent servers. We use the procedure GetServer that is defined in the functor AgentServer. This procedure can get access to any agent server, not just the one on its site:

declare 
Server1={GetServer "http://www.info.ucl.ac.be/~pvr/as1"}
Server2={GetServer "http://www.info.ucl.ac.be/~pvr/as2"}

4.1.2 Programming agents

Now that the agent servers are installed, we're ready to let the agents work.

A first example creates an agent on the remote site. The agent queries the operating system and returns the value of the OS.time operation. Let's first execute the agent interactively:

declare D1 in 
{Server2 functor import OS define D1={OS.time} end}
{Browse D1}

This first creates variable D1 and then executes an agent on the remote site. The agent needs only one resource, OS, a module that gives access to some operating system functions. The agent is created asynchronously, which means that D1 will usually still be unbound when the Browse is called. In most cases this is not a problem. The dataflow semantics of Oz mean that most operations that need D1 will wait before continuing. If the caller wants to be sure that D1 is bound before continuing, it just needs to do a {Wait D1}.

Now let's slightly modify the first example to get a standalone agent, i.e., the agent is itself a functor that can be compiled and executed standalone or by another application. We assume that the functor AgentServer is available at a standard place.

functor 
import System AgentServer
define 
   GetServer=AgentServer.getserver
   Server2={GetServer "http://www.info.ucl.ac.be/~pvr/as2"}
 
   D1
   {Server2 functor import OS define D1={OS.time} end}
   {System.show D1}
end

This is almost the same as the previous example; it just extends it with a call to GetServer to access the agent server. The agent first gets access to the remote agent server Server2 and then starts a calculation there.

If the functor AgentServer is not available in a standard place, then the standalone agent has to get it explicitly:

functor 
import System Pickle Module
define 
   AgentServer={Pickle.load "http://www.info.ucl.ac.be/~pvr/agents.ozf"}
   [AS]={Module.apply [AgentServer]}
   GetServer=AS.getserver
   Server2={GetServer "http://www.info.ucl.ac.be/~pvr/as2"}
 
   D1
   {Server2 functor import OS define D1={OS.time} end}
   {System.show D1}
end

4.1.3 There and back again

We give an example of an agent that goes to a remote site, does a calculation there, and comes back with the result. Surprise, surprise, the calculation takes almost no local CPU time.

4.1.4 Round and round it goes, where it stops nobody knows

We give an example of an agent that visits a set of sites repeatedly. It chooses dynamically the next site to visit. In fact, during its execution it can even get access to new sites that it has never heard of before and visit them.

4.1.5 Barrier synchronization

An agent can delegate work by creating tasks dynamically, and then wait until all tasks are done. This is easy by using logic variables to synchronize on the termination.

4.1.6 Definition of AgentServer

The basic tool giving agent functionality is the functor AgentServer, which is defined as follows. When installed, this functor creates an agent server, a procedure to publish the agent server, and a procedure to get access to any published agent server.

functor 
import Module Connection Pickle
export  
   server:AS
   publishserver:PublishServer
   getserver:GetServer
define 
   S P={NewPort S}
   proc {InstallFunctors S}
      case S
      of F|S2 then 
         try 
            [_] = {Module.apply [F]}
         catch _ then skip end 
         {InstallFunctors S2}
      else skip end 
   end 
   thread {InstallFunctors S} end 
   proc {AS F} {Send P F} end 
   T={Connection.offerUnlimited AS}
 
      % Make agent server available through file FN:
      % Note that the agent server is asynchronous.
   proc {PublishServer FN}
      {Pickle.save T FN}
   end 
 
      % Get access to a server that is at file/URL UFN:
   proc {GetServer UFN AS}
      try 
         T={Pickle.load UFN}
      in 
         AS={Connection.take T}
      catch _ then 
         raise serverUnavailable end 
      end 
   end 
end

One way to use AgentServer is to compile it with the standalone compiler and make the resulting ozf file globally-accessible by putting it in a public_html directory. This can also be done in the interactive user interface:

declare 
AgentServer=
   functor 
      ... (body of functor definition)
   end 
 
{Pickle.save AgentServer "/usr/staff/pvr/public_html/agents.ozf"}


Peter Van Roy, Seif Haridi and Per Brand
Version 1.2.5 (20030703)