このページは大阪弁化フィルタによって翻訳生成されたんですわ。

翻訳前ページへ


control over the agent痴 general behavior.

Other methods are provided to govern and refine your agent. For instance, every agent is equipped with lookup modules, which give your agent the capability to investigate its network surroundings. There are also modules designed to work specifically with specific infrastructure components such as matchmakers and logging agents. We will explain how to work with these events Arial'>
Every agent is configured with one or more file-logging modules. These modules provide detailed information to external entities as to the functioning of the agent. The file-logging module allows an agent to stream internal events to a Installation Instructions, below, for how to manage the behavior of logging modules). All log-files are created by the agent in a directory with the name of the day and month on which the agent was started.

Agent Process ID (PID)

 

All agents built with the AFC maintain PID files in the RETSINA system directory. The PID provides for the following functions:

 

1) It assists agents in identifying other agents running on the same platform. If it is programmed to communicate with a user via a voice, for example, an Interface agent should be able to find a SpeechAgent running on the same system.

 

2) It allows agent management tools to rapidly see what agents are running and what agents have crashed, by providing a comparison of the file entries with the list of agents actually running on an ANS server.

 

 

 







System and Software Requirements

 

To use the RETSINA Agent Foundation Classes you will need

 

  1. Pentium 90Mhz
  2. 16 Mb RAM
  3. A minimum of 50 Mb free disk space for full installation
  4. 2x speed CDROM
  5. Windows 95/98/NT/2000/Xp

The version of the RETSINA Agent Foundation Classes as described in
this manual requires that the following applications are present prior to
installation:

1. Visual Studio 6.0 (this has to have been run at least once prior to AFC installation)

2. Java 1.2 or higher (runtime environment)

 

Networking

 

To run agents on your own computer only, you do not need to be connected to a networked domain. To section below), you will need a live Ethernet connection.

 

When we refer to Agent Name Servers below, we mean an agent infrastructure component that can reside locally on your machine. You can register with an ANS server on your own machine; you do not need to be connected to a specific network to connect to an ANS Server, but in order to find and communicate with other agents, you will need to find and register with non-local ANS servers using Discovery.

 





 

 

 

 

 

Installation Instructions

1.

2.

3.

4.p;             Please read and accept the CMU licensing agreement.

5.p;             The Read-me file will appear. It contains information on the latest updates, which may not be reflected in this manual. It will be stored in the directory for the software. margin-left:63.0pt;margin-bottom:.0001pt;text-indent:-63.0pt;mso-list:l20 level1 lfo1; tab-stops:list .5in'>6.p;             The next GUI is for setting the installation path. This path designates the root location where all the libraries, files and examples will be stored. The default path is C:\program files\RETSINA. You can change this path, but we recommended that you do not.

7.p;             The next GUI is margin-left:63.0pt;margin-bottom:.0001pt;text-indent:-63.0pt;mso-list:l20 level2 lfo1; tab-stops:list 1.0in'>a.p;             Administrator (for WinNT 2000 and XP, for installation of multiple users).

b.p;             Compact: installs the smallest configuration necessary to build agents: for users with limited drive space or who do not need agent examples. Not recommended for first-time users.

c.p;             Custom: To choose components. For experienced users.

d.p;             Typical: To install complete set of files. This is the default and recommended installation setting.

8.


When you don't specify anything at this point, but instead just click 'OK', the
visual logging module will be initialized with a default name. This is normally
'DemoDisplay'. After you've selected a different name for the target agent, click
on 'Commit'. This will ensure that information is propagated to the agent code.

Step 3, Registering with an ANS


If no ANS server was specified using either one of the configuration files or command line parameters, the agent will pop-up a dialog box. You can use this window to register with an Agent Name Server.

Choose a server from the list, or enter a new one. Then press 'register' and the agent should inform you whether or not the registration process was successful. Use the 'unregister' button if you accidentally register with the wrong server. This process will not affect the already-running agent. When all goes well, the dialog should look like the dialog box in the second figure, below. The list of agents you will see in the drop-down box is obtained from the RETSINA system directory. We provide more information on this in the following sections.

 

 

 

 

 

 

Text Box: Before Registration


 

After Successful Registration

 
 

 



PART II: EXAMPLES


Example One: Agent Communications

 

Now that the AFC software is installed, you should now be able to test your agent system by running the most basic agent examples. The test will verify that the system is properly installed, while also demonstrating a basic communication between agents.

 

  1. From the Windows start menu, scroll to Java ANS (in Programs\RETSINA\tools).
  2. An ANS[5] console window will appear.

 


Note
: the RETSINA agents will function without the DemoDisplay, but you will not be able to easily verify the functioning of the agents.

  • Go to the AFC files on your C drive (the default is Programs\RETSINA\Examples\Step 1\).
  • Start AgentA by double clicking. AgentA will open:


  • Start AgentB.



  • AgentB will appear with AgentA on the DemoDisplay:



    The agents will automatically register with the ANS server, and will begin to pass a series of messages to each other based on a simple pattern: AgentB will AgentA will wait two seconds and reply with B2 +1. AgentB will wait three seconds and reply with A2+1, etc, until you quit one of the agents.


    7. Double click on the ANS server icon in the system tray (in the Windows-only version of the ANS server only) to check the registration of the agents. A window like the one below should appear, which shows the Hostname and port, the type="#_x0000_t75" style='width:354pt;height:306pt'>

     

    1. Unregister the agents (from the ANS server GUI) and shut them down (from the agent interfaces).

     


    Building the First Example Agents

     

    Now that you have run the first example of a RETSINA agent system, we will

    show you how to build that example using the Agent Foundation Classes and

    Visual Studio. In this example, difficulty, you can always refer to the agent code in the actual examples provided.

     

    Both AgentA and AgentB are identical in that they take in a number wait for the number of seconds indicated, add one to the number and send it back to the receiver. The only difference between A and B is that A starts the sequence. This means that AgentA needs some additional code to begin the dialog with AgentB.

     

    We will go through the example by showing what parts were added to the files generated by the workspace. Once you have the full set of agents as used in step 1, we will explain how the added code works together with the AFC to create the small agent system. Let's mso-position-vertical-relative:text'>


     

     


    When clicking ok you should see a dialog where you can choose what sort of graphical user interface style you would like to use.


     


    We strongly suggest that you construct these agents with the use of a Visual C++ guide. We chose to create a Dialog based agent for this example. The agent is labeled AgentA.

    AgentA Workspace

     

     

    Once you致e navigated through the configuration dialogs you will end up with a screen similar to the figure above (AgentA Workspace). It shows the newly created agent project and an empty dialog window that can be used by the agent. A status bar has already been included, which will show all the messages generated by the AFC components and modules. These files mirror the messages logged to disk.

    With the AgentA project a number of files were created. Most of these files are particular to Microsoft Visual C++ and can be used to connect any visual code to the agent code. The files you should be seeing in the file pane are: c_AgentA.cpp, AgentA.cpp, AgentA.rc, AgentADlg.cpp and StdAfx.cpp. These are the basic source files. The actual agent code is contained in c_AgentA.cpp. It contains the implementation of an agent derived from CBasicAgent. Comments are included to explain the behavior of the example code.

    In a previous section we called AgentA, which is directly derived from our basic agent. As we progress, you will become more familiar with the different kinds of agent derivations and their functionality. We will introduce information agents and middle agents. All of these classes are based on the basic agent and you will therefore need to understand how to develop with this class.

     

    If you do not already have style='mso-special-character:line-break'>

    Note: All agent-related files start with 'c_'. This is graphical-interface-specific functionality.

     

    The Agent Application Wizard created two files for you that encapsulate the actual agent. For AgentA these should be: c_AgentA.cpp and c_AgentA.h. Open up the file c_AgentA.cpp in the editor. You will see a large number of comments. These comments indicate what particular part of the agent is active at any one time.

     

    Open the file c_AgentA.h. In this file you will be able to see what is needed to build an agent. For our example, we only need to add the declaration of two variables. Add the following code to your class:

     

     

    Open the file c_AgentA.cpp and find the constructor definition for this class. Change the content of the constructor until it looks like this:

     

    BOOL CAgentB::process_message (char *data)

     

    The basic agent calls this method when a message arrives. As you can see, it is left empty by the Agent Application Wizard. Add code as the content of this method, so that the final font-family:"Courier New"'>BOOL CAgentB::process_message (char *data)

    }

     

    Let's examine the additions we have just made. The first thing you should notice is that the method returns a Boolean value. This is important when you start to build more complex agents or when you build agents that other people will build upon. If your agent code returns a TRUE value to the basic agent, this indicates that the method processed the message. In other words it tells the developer who uses your agent class that the message was meant for this class, and not for the derived agent class.

     

    Next, we enable our agent to (data)

     

    If this method fails the parser and tells the basic agent that it did not consume the message. If it was able to parse the message, it needs to find two important fields: sender and content. The sender field will tell our agent where to send the reply and the content will give our agent the value of the number.

     

    (Remember that AgentA and AgentB are identical, so the code you see here is also found in AgentB). Our agent checks to see if the sender string is not NULL and then proceeds by parsing the content field.

     

    One thing to remember about the Agent Communication Language is that any field can contain a number of other fields. In this case the content field contains the number field we created in the timer method. We create a new parser called r_parser and we call (content)

     

    If this method succeeds, we should be able to retrieve the number field from the r_parser. Look for a line that says:

     

     

    This will retrieve a pointer to a string called number from the parser. If we constructed our message properly the number string should point to a text representation of our number. The last task we do is to convert the text representation into our own variable change the step value

     

    We have changed the step variable and now the agent can wait the amount of seconds this variable indicates.

     

    You should now be able to build AgentB. The only difference between the two agents is that the constructor for AgentB looks slightly different than for AgentA. Here is the font-family:"Courier New"'>CAgentB::CAgentB(char *a_name) : CBasicAgent (a_name)


    Example Two: Adding an Information Agent

     

    In Example 1 we demonstrated a basic multi-agent system consisting of two agents, both of the same type. In the RETSINA architecture we define 4 basic agent types:

     

    1. Task Agents
    2. Information Agents
    3. Middle Agents
    4. Interface Agents


    The agents we used in the first example can be considered task agents. However, since we did not need our agents to perform complicated tasks, we used the most basic agent form from the AFC.

    We will now add a new agent to the scenario that is based on the AFC Information Agent. The agent we will add can tell us the time of the local system. In other words, when we ask it, it will tell us the date and time of the system on which it is running. Since we are running all the agents on the same system, we will be receiving the time of the local system. The agent that provides the time and date is named the DateTimeAgent.

     

    Note: There are four main ways of soliciting information from Information Agents in the RETSINA agent community, each with their corresponding Information agent behaviors:


    1. Single shot query: The requesting a gent asks for information once; the service provider implicitly de-commits to providing the service/information again after the first reply, or upon a timeout.

    2. Active monitor query: The requesting agent asks the information agent to actively monitor an information source and to provide information, typically on a periodic basis (e.g. every 60 seconds).  The Information Agent acknowledges the request, informing the requester how to end the service.  The service-providing info agent continues to provide the service until it receives an explicit message from the requester asking it not to provide the service any more.

    3. Passive monitor query: The requesting agent asks that the service-providing agent notify it of the occurrence of an event or condition, for example, a change in stock prices; the recognition of an explosion, enemy "query" to another information agent, asking it to update a database record or external archive. 

     

    In this example, we use the versions of AgentA and AgentB as found in the Step 2 folder).

  • Start AgentB.
  • Start DateTimeAgent
  •  

    AgentA sends a message to DateTimeAgent to start-up the active monitor query. The monitor query is set at 20 second intervals, but the programmer can set the value at any interval, to as low as 1 second. Every 20 seconds, the information agent informs AgentA of the current time. A-B messages are interrupted by the time monitor replies. This sets the second counter in AgentA to zero. AgentA and B communicate as in the above example (message+1).

     


    Building the Second Example Agents

     

    First we will demonstrate how to create the new Information Agent. Then we will show you how to integrate this new agent into the scenario.

     

    Start by re-creating AgentA and AgentB, or copy the two projects to a new directory.

     

    Create a new workspace with the RETSINA Application Wizard, naming the project DateTimeAgent. This should produce a new workspace with the files:

     

    c_DateTimeAgent.cpp and c_DateTimeAgent.h

     

    As in the first example, look at the header file that holds the new agent痴 (Information Agent) declaration. Open the file called c_DateTimeAgent.h. This file will appear to be very similar to that of the other agents you have built so far. To make the agent an Information Agent, you need to change the base class to look like this:

     

    class CDateTimeAgent : public CinfoAgentBase

     

    The new agent will have all of the normal event methods as defined by the basic agent, and will have the additional capabilities of the Information Agent. When we are dealing with specific agent types we do not need most of these methods. In fact, in our example we can remove all of the methods and replace them with one single event method. The Information Agent as defined by the RETSINA will call the external query function.

     

    Add an entry to your agent in the protected area and call it:

     

    char *external_queryfunction (CLList *);

    We need one more addition to complete the agent; add a private variable called

    b_message of type string. In your code this should look something like:

     

    private:

     

     

    This is all that is needed to setup the class of Information Agent.

     

    Open up the file c_DateTimeAgent.cpp.

     

    Since we are dealing with a more specialized agent here, we do not need a lot of the overhead we used in the other agents. In fact we only need to add code to three methods. First of all we need to initialize the string we will use to communicate the result of a query. Find the constructor of the agent and add the following:

     

    b_message=NULL;

     

    This will make sure the agent does not delete memory that it doesn稚 use.

     

    Next find the destructor of the agent and font-weight:normal'>

     

    This code cleans up the memory that was used to create the replies.

    All that is left to do now is to fill in the content of the external query function. You will manually have to add the method to your file, since the Agent Application Wizard did not add this method for us. When you are finished, your file should have the following method:

     

    char *CDateTimeAgent::external_queryfunction (CLList font-weight:normal'> 

    }

     

    Note that this method returns a string. This is how the agent provides the result of the query. In the example above the query will always fail, because a NULL is returned. Change the content of the method to reflect the following code:

     

    debug ("<CDateTimeAgent> processing external query function ...");

     

     

    CParameter *temp=(CParameter *) request->get_first_element ();

     

    A parameter is a class that has a name and a content field. The content is always a string. In every query that an Information Agent receives there will be a parameter called "primary-keys". This is borrowed from database technologies, and you will see that most of the queries resemble database queries. In our example the agent code checks to see whether the current parameter that was obtained from the list is the primary key. It accomplishes this by using the following piece mso-bidi-font-size:12.0pt;font-family:"Courier New";font-weight:normal'>if (strcmp (temp->get_name (),"primary-keys")==0)

     

    Once the Information Agent finds the primary key, it will need to examine its contents, which will tell it if the mso-bidi-font-size:12.0pt;font-family:"Courier New";font-weight:normal'>char *content=(char *) temp->get_content ();

     

    We need to obtain a pointer to the content field in the parameter object. Parameters are designed to hold a number of different data types. In our case we use strings exclusively, so we will cast the content to a string. As the content in this case is "time," the DateTimeAgent will process the request and send back the current time. To enable (b_message,"tell :time (%s)",string);

     

     

     

    We have now built our first Information Agent. However, in order to make use of it, we need to integrate it into our agent scenario. In order to do this we modify some code in AgentA. Open up the mso-bidi-font-size:12.0pt;font-family:"Courier New";font-weight:normal'>void CAgentA::process_init (void)

     

    If you do not have this method then add it to your source file and header file as either a protected method or a public ("tell",

    char *content =c_parser->find_content ();

     

    Below this line add:

     

     

    This will retrieve an ontology field from your message. The ontology indicates the subject of conversation. We will use it here to see if the message is coming from AgentB, or from the DateTimeAgent. The code below is the only additional code we add to our agent to have the from the DateTimeAgent

     

     


    Example Three: Using the Matchmaker

    So far, we have introduced basic task agents (A and B), and an information agent (DateTimeAgent). We have tested and built these agents, and observed their communications with each other. We will now introduce one of the most important components of the RETSINA MAS, the Matchmaker. The Matchmaker is an agent that helps make connections between agents that request services and agents that provide services. The Matchmaker serves as a "yellow pages" of agent capabilities, matching service providers with service requestors based on agent capability descriptions. The Matchmaker system allows agents to find each other by providing a mechanism for registering each agent's capabilities. An agent's registration information is stored as an "advertisement," which provides a short description of the agent, a sample query, input and output parameter declarations, and other constraints.

    In this example, AgentA does not know the name and location of the DateTimeAgent, and will have to find it, using the Matchmaker. The Matchmaker will find the DateTimeAgent in response to a request from AgentA for an agent with date/time capabilities. It deliver the requested agent capability in a reply to AgentA.

    This example will build on the agent scenario from Step 2. In order to demonstrate the functionality of the Matchmaker, we will have to start a different version of the task agent, one that does not know the DateTimeAgent (i.e., does not have hard-coded information on the DateTimeAgent in its cache). Be sure to use the AgentA and AgentB versions as found in Step 3.

    1.      Start the ANS server.

    2.      Start the DemoDisplay.

    3.      Start the Matchmaker: Program files\RETSINA\tools\java GinMatchmaker

    4.      Start the DateTimeAgent. The DateTimeAgent will advertise its capabilities with the Matchmaker. (This passing of this advertisement will not be discernable on the DemoDisplay).

    5.      Start AgentA (from the step 3 directory). Upon initialization, AgentA will query the Matchmaker for an agent that can provide the date and/or time, as shown below on the DemoDisplay:

    This query starts the monitor query as in Step 2.

    6.      Start AgentB (from the step 3 directory). AgentA and AgentB will communicate as in earlier steps, interrupted by the DateTimeAgent, which resets sequence as in step 2.

    Building the Third Example Agents

    Copy the projects and files from step 2 into a new location. We will use these projects and files to build upon and extend your agent's capabilities, so that it can use a middle agent.

    We need only make changes in order to extend our basic agent痴 capabilities to include the capability of using of a middle agent.

    Open the file c_AgentA.cpp and find the process_init method. In step 2 the agent used this method to initialize a monitor query with an information agent. In this step, the agent will request that the Matchmaker deliver information about any agents that can provide the time/date.

    Clean out the content of the process_init method and replace it with the following code:

     CMatchmakerClient *mmaker=get_mm_module ();

     if (mmaker!=NULL)
     {
      CFileBuffer *file=new CFileBuffer;
      char *buffer=file->load_a_file ("target-schema.txt");
      if (buffer!=NULL)
      {
       char *agent_monitor=new char [strlen (buffer)+1];
       strcpy (agent_monitor,buffer);

       if (mmaker!=NULL)
        mmaker->mm_monitorAdvertisements (agent_monitor);

       delete [] agent_monitor;
      }
      else
       debug ("<CAgentA> Unable to load the target information agent
    advertisement template needed for advertisement monitoring!");

      delete file;
     }


    With this code fragment, we load an advertisement into a file object. Then, we assign the file object to the contents and format of the advertisement. It is a small advertisement that tells the Matchmaker to look for similar capability advertisements from other agents. The actual request in the above code consists of two lines:

     if (mmaker!=NULL)
      mmaker->mm_monitorAdvertisements (agent_monitor);


    These lines direct a task agent client module dedicated to the Matchmaker to tell the Matchmaker to look for the advertisement given as a file object. The Matchmaker will tell AgentA whether or not any agents with such capabilities are available.

    In the test example, the DateTimeAgent advertised with the Matchmaker. Putting a file named adv-schema.txt in the directory from which the information agent starts creates this communication. The contents of this file is a capability advertisement like the one used in the code fragment above, which told the Matchmaker what capabilities our task agent is looking for. The content of this advertisement is written in an advertisement language called GIN.

    Now that Matchmaker is aware that an agent is available conforming to the request sent by AgentA, it will reply to AgentA with the name and advertisement of the DateTimeAgent. In order for AgentA to process this reply we add the following code at the very top of the process_message method:

     CMatchmakerClient *mmaker=get_mm_module ();

     if (mmaker!=NULL)
     {
      if (mmaker->get_updated ()==TRUE) // we received an answer from the
    Matchmaker
      {
       debug ("<CAgentA> Processing change in Matchmaker module");

       if (mmaker->get_last_operation ()==__MM_OP_NEWAD__)
       {
            CServiceInfo *service=mmaker->get_last_service ();
            if (service!=NULL)
            {
             // next see if the advertisement is a device ontology
             CGINAdvertisement *ad=(CGINAdvertisement *)
    service->get_first_element ();
             if (ad!=NULL)
             {
               char *reply=Communicator->comm_sendmessage ("tell",
      p;              ad->get_agentname (),
    p;              "default-language",
    p;              "default-ontology",
    p;              NULL,
    p;              NULL,
    p;              NULL,
    p;              "objective :name \"getInformation\"
    :parameters (listof (pval \"primary-keys\" \"time\") (pval \"trigger\"
    \"any-change\") (pval \"period\" \"20000\"))",
    p;              NULL);
              if (reply!=NULL)
           debug (reply);
        else
         debug ("Message sent to Agent");
             }
          }
          else
           debug ("<CAgentA> Unable to obtain new agent info");

          // done handling message from
    Matchmaker -------------------------------------------------
       }

       mmaker->set_updated (FALSE); // tell the Matchmaker we noticed the change
       return (TRUE);
      }
     }


    As you can see from the code above, we first obtain a pointer to the Matchmaker client module.

    CMatchmakerClient *mmaker=get_mm_module ();

    This module will be able to tell us whether the Matchmaker has sent a reply to the task agent. The following line --

     if (mmaker->get_updated ()==TRUE)

    -- indicates that a message came in and that indeed something changed within the Matchmaker. Now AgentA need only learn whether or not the Matchmaker has the name of an Information Agent that matches the capability requested.

    First, we check to see if the client has received a new advertisement, or in other words, news of a new agent:

     if (mmaker->get_last_operation ()==__MM_OP_NEWAD__)

    (Since we only have one Information Agent running, we know that this must
    be a match for AgentA痴 request. We obtain a pointer to the service description the Matchmaker client can provide us):

     CServiceInfo agent it seeks. Below is the code that will extract the advertisement from the service description:

     CGINAdvertisement *ad=(CGINAdvertisement *) service->get_first_element ();

    A service might have more than one advertisement, but since we are only
    looking for one capability we use the first advertisement in the list.

    Below, we show the difference between the code used by AgentA in step 2, and that used by AgentA in step 3. The difference is that we can now obtain the name of the DateTimeAgent without supplying it in our code. The string

     "DateTimeAgent"ad->get_agentname ()
    in step 3.

    This example should serve to get you started with basic Matchmaker interaction.



     

     

     

     

     

     

     

     

     

     

     

     







     

     

     

     

     

     

    Example Four: Using Discovery


    All of our demonstrations thus far have assumed a stable environment in which our agents live. In this example, we demonstrate a means by which agents can continue to function, even when their environment is changing, and when key components of the system come and go. Before testing this example, however, we discuss the features employed to make this possible, and the reasons for their development. You can skip to the instructions for testing, if you want to see these features in action before, or in lieu of, reading about them.

    As agent-based applications move beyond simple test-case scenarios, the truly dynamic and unreliable nature of the agent world becomes apparent. Peer agents can act erratically, middle agents and infrastructure services may become temporarily unavailable, and various aspects of the environment that the programmer assumed would be constant, turn out to be unpredictable. While the robustness of the agent code handles some of these difficulties, the infrastructure of the agent community should help with agent adaptation to ad-hoc and dynamic environments.

    As we have shown, the RETSINA MAS utilizes middle agents (especially ANS server and Matchmaker) to facilitate agent interactions. In addition to providing this middle agent infrastructure, we have provided agents with an enhanced means of locating and gaining access to them. A key technology that allows agents to Using Discovery, agents and servers can automatically maintain dynamically updated lists of available agents and servers. As agents, ANS servers and Matchmakers come and go from the network, these internal lists are expanded and contracted automatically. Agents can be initiated before an ANS server is online, and instead of failing, they will register with an ANS server when one becomes available and is discovered. ANS servers can be updated with knowledge about agents from other servers, because these servers were able to discover their peer ANS servers to provide redundancy.

    RETSINA agent services utilize the Simple Service Discovery Protocol (SSDP) that was developed as part of the Universal Plug-n-Play (UPnP) consortium痴 efforts to support small/home and ad-hoc networking. This protocol is utilized at the core services levels within the agent software libraries, to ensure that required available, their presence is made known throughout the community. Infrastructure services also use the Discovery protocols to coordinate interactions between each other, to ensure that agent information is appropriately replicated, load balanced, and/or accessible.

    We will briefly describe the SSDP protocol, and then proceed to discuss the within the Agent Foundation Classes (AFC) are described. Finally, we demonstrate some of these features in action.

     

    Simple Service Discovery Protocol

     

    The Simple Service Discovery Protocol (SSDP) utilizes multicast transmissions to allow systems to communicate with other nearby systems, without prior knowledge of their existence or their specific locations (other than the standard broadcast messages to tell other systems that they are 1) alive and available, or, 2) leaving and no longer available. SSDP clients (systems that are seeking to find services that advertise themselves via SSDP) will utilize multicast messages to search for providers that offer a specific (or all) service(s). SSDP service providers that receive the multicast search-request will send a unicast message (one-way, non-multicast) to the requesting client, using the return address that the client provided in its search.

    Unlike other Discovery protocols (such as SLP, Jini, etc.) the SSDP architecture is extremely lightweight. Responses to search requests are URL-style strings.

    A problem with multicast transmissions is that many routers and firewalls limit or prohibit their transmission. Given this limitation, the Discovery process traveling any more than three hops along the network. This restriction precludes problems that may arise from systems divulging internal numbering or architecture information to malicious packet-voyeurs on the public footnote'>[6]

     

    The Agent Name Service was the first RETSINA infrastructure component to support Discovery.

    As some simple string-based pattern. Agents can choose to communicate with other specific agents on the network in many ways, but they will ultimately request that their agent communications modules create a network link to the remote concerned with the specifics of the ANS client, just that it works).

    The Discovery process, as described in the previous section, is composed of clients and service providers, and their interactions. The Agent Name Service implements various combinations of processes between the Discovery latter feature allows ANS servers to discover each other in order to provide various levels of peer information sharing. And finally, the ANS client (that is part of every Agent) acts as a Discovery client, so that it also can discover the available ANS servers.

     

    Agent Discovery

     

    The ANS client also implements both service and client Discovery interfaces to susceptible to errors due to periodic outages of ANS servers, network links, or from other routing problems. It also allows agent applications to begin functioning without the existence of an ANS server, in case the startup procedure sequence (start ANS server, start Matchmaker, start other middle agents, then start agent applications) doesn稚 progress as anticipated. Once an ANS server comes online, the auto-register feature of agent痴 ANS client will Classes, a number of Discovery-based facilities allow agents to find each other without prior existence of desired lookup services on the network. Each agent is fitted with an ANS client and a Discovery client that act as part of the AFC痴 lookup modules. These two lookup modules are used by the Communicator to fill and maintain a common location lookup table. This table reflects the agent痴 view of the network. When an agent wishes to send a message to another agent, it will give the message to the Communicator and indicate the target agent. The Communicator in turn will either directly send the message, if the target痴 location information is available, or temporarily store the message, and send out a request for the target痴 location information. This location request is handed to all available AFC location modules. When an answer is obtained and the location lookup table has been updated, the original message will be sent. Since all available lookup modules work in parallel, and since they all use the same data-structure, the dependence on a specific lookup client diminishes. As long as there is at least one lookup client active, the location lookup table will be refreshed.

     

    Disabling Discovery Modules

     

    Discovery is an inherent component of the AFC. In some cases, however, agent developers will want to disable Discovery modules. For example, a group may be running sensitive experiments or demonstrations with a group of agents, and will not want the ANS Server and/or the agents to be discoverable to outsiders. You can configure the usage of both Discovery and ANS lookup in agents. You can also disable Discovery in ANS Servers.

    By default, both Discovery lookup and ANS lookup are enabled in the AFC agents. But, you can override one or both of them by calling the method

    set_lookup_config

    and the proper parameters. The set_lookup_config overrides the defaults and allows the developer to set the specific parameters desired for the functions. If you want to enable Discovery lookup only, you would call the method and set the parameter:

     
    set_lookup_config (LOOKUP_DISCOVERY);

    If you want to enable ANS lookup only, you would call the method as follows:

    set_lookup_config (LOOKUP_ANS);

    If you want to enable both lookups, you would call the method as follows:

    set_lookup_config (LOOKUP_DISCOVERY | LOOKUP_ANS);

    If you want your agent to be completely standalone, you can call the method as follows:

     set_lookup_config (LOOKUP_NONE);

    The settings for agent ANS or Discovery lookup parameters also control the enabling/disabling of an agent痴 discoverability by other agents. Thus, an agent that has disabled Discovery lookup is also non-discoverable by other agents.

    You can change the usage of lookup modules while the agent is running. Every lookup module is based on the CLookupModule class. This class has the following access methods:

     void enable     (BOOL);
     BOOL is_enabled (void);


    Use this method to enable or disable one of the lookup modules at runtime. In order for you to call the methods on the lookup modules, you will need to obtain a pointer to one of these lookup facilities. The following methods are available in the Communicator to do that:
    p;              -----------

    CANSClient *retrieve_ans_object (void);
    CDiscovery *retrieve_dsc_object (void);


    Remember that both the CANSClient and the CDiscovery classes are based
    on the CLookupModule class.


    To control the settings of the Discovery parameters of ANS Servers, we have provided an alternative menu item in Start|Programs|RETSINA|Tools. The two options are:

    -         Java ANS 2.7

    -        Java ANS 2.7 (no discovery).Managing a RETSINA ANS Server: ANS Server GUI

     

    Beginning with version 2.8, other ANS servers.

     

     

     

    Text Box: Current Server Information	New ANS
Server
AgentInformation	KnownServers
Server Console
Command Line	Misc. ButtonInterface
 

The Screen

     

    The GUI Screen has six interlinked panels as depicted in the table to the right.

     

    Since an ANS server may know about other ANS servers, you can, once connected to an ANS server, browse the List and the Hierarchy Partner List are both lists of ANS servers maintained by an ANS server. Both lists are preloaded from static files on server startup. The difference between them is that the Discovery/Peer Servers List is dynamically updated by the discovery mechanism after startup. The Hierarchy Partner List is the permanent list maintained in the cache of the ANS server for partners with which it regularly shares information. Entries in the Discovery/Peer List are typically dynamic, and servers are removed if they cannot be reached. Both are described more fully in the ANS v.8 document entitled, "javaANS.PDF." (included on CD distribution and on-line at:

    http://www.cs.cmu.edu/~softagents/ans/ANSv2.9.PDF)

     

     Once an entry appears in one of these fields, clicking on it once will populate the New ANS Server are provided, as well as to request that the server send out a new discovery message ("ReDiscover").

     

    The Agent Information panel will have its name displayed in the "Agent Information" field. Double clicking on agents in the "Registered Agent Names" list will perform a lookup operation for the selected agent name, which will fill in the rest of the boxes in the Agent Information panel (Hostname, Port/Socket #, Parameters). Parameters include such agent information as the name; ttl= (Time to Live--the number of seconds remaining in this registration's lease--a relative time); expires= (the time stamp when the server will discard this registration or no longer recognize it as valid -- in milliseconds of actual server time since a certain starting point); type= (for agent type, such as: retsina:Matchmaker); key= ( public key of agent); cert= (PKI X.509 certificate for agent). When a lookup command cannot be resolved locally, the entries of ANS servers in the Discovery/Peer

    Servers List will be queried server, and the text box below it will show the actual server response before it is parsed into appropriate GUI fields. You can enter any console command manually and hit enter, and see the results from the server.


    Version 2.8 of the ANS server will return status and server startup help screens to any attached user that requests into the ANS server console, the GUI will be reactivated if it has been closed.

     

    Server Console vs. Stand-Alone Modes

     

    The differences between the two modes -- attached as part of a specific ANS server versus running as a stand-alone management tool -- are apparent when moving towards a manually initiate a server connection.

     


     

    In this example, we demonstrate Discovery; agents discover the DemoDisplay, and each other, without the help of an ANS server. The use of an ANS location module is disabled within the agents. Their ability to find each other and is made possible by the Discovery process.

     

    As we have mentioned, each agent in AFC is fitted with a SSDP Discovery module. This module lives side by side with the ANS module in the basic agent. The Discovery and ANS modules use a common table to store location information. When there is no ANS module, only the Discovery module will fill this table. The Discovery client will populate the table with the replies to the look-ups that it sent out to the ANS Service environment (received and replied to by agent service modules). The result is that your agent will function quite happily without any lookup services on the local network.

     

    This example is identical to the previous example except that we added a line of code to each agent's 'Create' method, which disables the use of an ANS client module. Use the agents from Step 3.

     

    1. Compile the agents and start the sequence as before.

     

    2. Start the ANS server. (The ANS server is needed for the DemoDisplay to visualize the agents. However our agents will no longer use the ANS. No messages will pass to and from the ANS).

     

    3. In both AgentA and AgentB locate the 'Create' method. Change the content (which should be empty) to:

    void CAgentA::process_create (void)
    {
     if (Communicator!=NULL)
      Communicator->comm_disable_ans ();
     

    Example Five: Integrating Third-Party Reasoning Modules

     

    The AFC provides a complete set of libraries that allow an agent to connect to MAS infrastructure components and communicate with other agents. Through the AFC the interaction with the infrastructure and other agents in the agent world is highly efficient and fully automated. However it is up to the agent to make decisions on whether and when to initiate a conversation with other agents.

     

    The AFC provides facilities that allow the introduction of a problem-solving engine in the agent code, in order to control the actions of the agent in an intelligent way. The task of the programmer is twofold:

     

    1.      To link the agent code to a problem solving engine by deriving the problem solver module from the class CProblemSolver. This class provides some hooks that give easy access to the internals of the agent such as the BeliefDB and the Communicator.

     

    2.      To implement the actions that will allow the agent to operate in its environment. The class CPSActionCodes already provides some basic agent oriented actions. More actions can be added by deriving a new class from CPSActionCodes.

     

    The distinction between the problem-solver class and actions class adds flexibility to the agent architecture, because it allows the implementation of agents with exactly the same action code, but different problem-solving engines. Thus these agents can act differently because they think differently, and not because they have different capabilities. On the other hand, the AFC allows the implementation of agents that employ the same problem-solving engine but have different actions. These agents think in the same way, but act differently because of the way they perform their tasks.

     

    The CProblemSolver Class

     

    The class CProblemSolver provides the basic methods that have to be overloaded to link problem solvers to AFC-based agents. This is an abstract class that cannot be instantiated by itself. To make use of the functionality of this class the problem-solving engine used must be in a class derived from CProblemSolver. With the usual constructor and destructor methods that should be implemented to provide access to the problem-solving engine, CProblemSolver provides methods that allow access to the main facilities of the AFC.


    Specifically, the class provides the following methods:

     

    1.   BOOL GenerateSolution()

     

    This is a pure virtual method that must be defined in the child class and is used by the agent to activate the problem-solver. In a typical agent this method would either contain the core problem-solving algorithm or make calls to it seamlessly.

     

    2.   BOOL ExecuteActions()

     

    This is also a pure virtual method that must be defined in the child class and is used by the agent to execute the actions selected by the problem solver. This method basically implements an execution engine that transforms the problem-solver representation of the actions to the actual actions that can change the agent痴 feedback to the problem-solver, based on the success or failure of the actions.

     

    The AFC is not committed to any particular relation between the problem solving and the execution. This is left to the programmer who can choose to follow the traditional sequence of first generating solutions followed by their execution, or a more sophisticated interleaving of problem solving and execution.

     

    3.   CBelieveDB *GetBeliefDB()

     

    This method gives the problem-solver access to the general knowledge base used by the agent to mso-bidi-font-size:12.0pt;font-family:"Courier New"'>4.   SetBeliefDB(CbelieveDB *)

     

    The internal AFC framework calls this method to set the BeliefDB in the CproblemSolver class. The programmer can also call this method if the instance of the beliefDB ever needs to be changed or removed.

     

    5.   CCommunicator *GetCommunicator()

     

    This method retrieves a reference to the AFC Communicator to allow for any message that may need to be passed to other agents in the MAS. The AFC framework sets the Communicator tab-stops:list .25in'>6.   SetCommunicator(CCommunicator *)

     

    The internal AFC framework calls this method to set an instance of the communicator in the CproblemSolver class. This allows the problem-solving engine to access the communication facilities of the agent without the need for saving pointers to the main agent shell. The agent programmer can also call this method in case the instance of the Communicator needs to be changed or removed.

     

    7.   CPSActionCodes *GetActionCodes()

     

    This method provides access to the action codes that may be used by the agent. This is a pointer to the CPSActionCodes class (see below).

     

    8.   SetActionCode(CPSActionCodes *)

     

    The internal AFC framework calls this method to set the action codes that may be used by the planner. The base class for action codes is provided (CPSActionCodes), which has some basic actions codes that may be called by the agent.

     

    The CPSActionCodes Class

     

    The class CPSActionCodes allows the programmer to implement actions that the agent can perform. A few actions are provided that the agent can use to interact with other agents within the MAS. More actions can easily be added by simply deriving a new action codes class from CPSActionCodes. The basic actions provided are:

     

    1.   char *SendMessageToAgent(char *pszAgentName, char *pszContent)

     

    This method sends a message to another agent in the MAS. The return value is a string that indicates the error message if there was an error in sending the message. The first argument is the agent name and the second argument is the content of the message.

     

    2.   char *CPSActionCodes::SendMessageToAgent(char *, char *, char *, char*, char*)

     

    This is an overloaded method that can be used to send a message to an agent with more control over the header. The arguments are

     

    a.      Performative: This is the performative used in the header.

    b.      Ontology: This is the ontology descriptor used in the message.

    c.      Language: This is the language descriptor used in the message.

    d.      AgentName: This is the name of the agent that is the recipient of the message

    e.      Content: This is the content of the message.

     


    Example Five, Continued: Deriving an Agent that Uses the CProblemSolver Class

     

    This example illustrates the classes and their relationship in a simple agent that uses the facilities provided by the CProblemSolver class. This agent will be called the used to generate the agent workspace in Visual Studio, then the inheritance will need to be changed from CBasicAgent to CPlanningAgent. The class for our "c_afc.h"

     

    ////////////////////////////////////////////

    // CReasoningAgent Class Definition file used for Agent

    // ReasoningAgent

     

    class CReasoningAgent : public CPlanningAgent

    {

    public:

     

     

     

    protected:

    };

     

    The constructor of our reasoning agent will contain the following code:

     

    CReasoningAgent::CReasoningAgent()

    }

     

    Assuming that our agent uses a planner called MyNicePlanner, in a class derived from CproblemSolver, the class for our planner will be as follows:

     

     

    #include "c_afc.h"

     

    // CMyNicePlanner Class Definition file

     

    class CMyNicePlanner : public CProblemSolver

    {

    public:

     

    };

     

    The GenerateSolution() method of MyNicePlanner will be as follows:

     

    BOOL CMyNicePlanner::GenerateSolution()

    {

    //TRUE is returned.

    //if Planning fails then FALSE is returned

    //The belief DB can also be used while planning

    //and that can be obtained by calling GetBeliefDB()

    }


    The ExecuteActions() method of MyNicePlanner will be as follows:

     

    BOOL CMyNicePlanner::GenerateSolution()

    //Use the plans generated by the GenerateSolution()

    //method to execute them.

    //Action can be executed by selecting appropriate

    //from the set of action codes provided by the AFC.

    //This can be obtained from the GetActionCodes() method.

    //GetActionCodes()->SendMessageToAgent(...)

    }

     

    Deriving the Agent class from the CPlanningAgent gives the programmer the advantage of having any incoming message from the agent space passed directly to the planner. In other words the process_message() method of CPlanningAgent calls the GenerateSolution() method of the CProblemSolver class every time a new message comes in from the agent space.

     

    This allows the agent to immediately reason about any messages that arrive from other agents in the MAS. If the main agent is not derived from CPlanningAgent (but from CBasicAgent), then the programmer will need to add code to route the messages to the problem-solving engine, code that calls CProblemSolver::GenerateSolution().

     

    Class Hierarchy Diagram

     

    The hierarchical relationship between the classes used is shown in the class hierarchy diagram below.

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     


    Class Hierarchy diagram for the problem solver classes




    Example Six: Auction Demo

     

    In the following example, we show agents interoperate and negotiate in the process of an auction. This demo shows how developers, using the AFC toolkit, can deploy a fairly sophisticated and user-friendly set of agents and scenarios, as applied to a real-world market setting, without having to develop the underlying agent architecture and infrastructure. The negotiation protocol as demonstrated in this example is a simple one, but developers can modify the protocol as the situation warrants it.

     

    1. Start ANS.
    2. Start Matchmaker.
    3. Start DemoDisplay
    4. Open Auction folder.
    5. Open AuctionDemo folder (RETSINA/Examples/Misc/Auction/AuctionDemo).
    6. Click on Seller shortcut (starts Seller, registers it with an server, displays on DemoDisplay).
    7. Click on Seller1 shortcut (same as above).
    8. Start two buyers via shortcuts. (Buyer and Buyer1).
    9. Arrange icons on DemoDisplay so that all agents buttons. All buyers who wish to participate in the bidding process must submit their bids, via their bid buttons.
    10. Add sellers and buyers, each with different price requirements, and observe how low bidding buyers are pushed out of the market when new buyers are introduced. Note that market equilibrium is established via automated negotiation.

     

    Premises underlying the demo:

     

    1. When an agent bids, it is assumed that the agent is committed to the bid, which, if accepted by a seller, results in a firm deal.
    2. Sellers must all be advertised with the Matchmaker before buyers start bidding. This gives all buyers a chance to bid to all sellers of the same items, providing the buyers seek the items being sold.

    Example Seven: Distributing Your Agents Over a Number of Mchines.

     

    In all the example so far, we have assumed that you have been running all of the agents and infrastructure components on a single machine. The ANS, DemoDisplay and agents were compiled and started in sequence on the same CPU. However, for various reasons, including limitations of either memory or CPU power, you may need additional resources to execute all components at once. Since we are building multi-agent systems, we should be able to distribute the agents over a number of machines.

     

    In this section we will show you how to setup a number of computers to run your agent system. We will use three systems to distribute the agents from example 1. Below is an overview of the intended setup:

     

     

     

     

     

     

     

     

     

    Text Box: System C

     

     

     

     

     

     

     

     

     

     

     

     

    Text Box: System BText Box: System A

     

    In example 1. we use the following infrastructure components and agents:

    1.      Agent Name Server

    2.      DemoDisplay

    3.      AgentA

    4.      AgentB

    The list above also indicates the starting order for this particular example. Our objective is to keep the ANS and DemoDisplay on System C and move Agents A and B to systems A and B, respectively. We will not need to change the settings for the ANS and DemoDisplay since they will connect to the machine they reside on. However, we need to tell AgentA and AgentB to register with the ANS on System C.

     

    Before you can edit the configuration of those two agents, the following must be in place:

     

    1.      The AFC must be installed on all host machines

    2.      You need the IP address of System C.

     

    The first step is described at the beginning of this manual.

     

    The second step will need a bit more explanation. Every machine on the network has an IP address that uniquely identifies that system world-wide. You will need this address to connect to an ANS on a remote system. Go to the machine that you have designated System C that holds ANS. If you are running windows NT,2000 or XP start a command shell and type: ipconfig, at the prompt:

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     


    In this case the IP address is 128.2.213.149.

     

    If you are using the AFC under Windows 95 or Windows 98 then you will need to type winipcfg to obtain the same information. If you do you will see a dialog box that looks like:




    As you can see from the screenshot, the IP address for this particular system is: 128.2.178.76.

     

     

     

     

     

     

     

     

     

    Now that you know the address of the machine that runs the ANS, you will need to change both AgentA and AgentB so that they use that address to connect to the lookup service.

     

    Navigate to the RETSINA directory (on Systems A and B, for Agents A and B, respectively). You should find a subdirectory called:

     

    Examples\Steps\Step1\AgentA.

     

    In this directory you should find a .bat file called run.bat. This the generic name for the start scripts that we use to run our agents. Open that file in a text editor such as notepad. You do you should see the following text:

     

    AgentA.exe -name AgentA -port 6673 -ans 127.0.0.1 -ansport 6677 -ddp DemoDisplay

     

    You should notice that the ans field is set to 127.0.0.1. This address is a reserved for a local host, i.e., the machine on which the agent is currently running. If you want to have the agent use a different ANS, you need to fill in the IP address that we obtained from the steps listed above. Change the IP address and save the file. Do the same for AgentB, which you should be able to find under: Examples\Steps\Step1\AgentB\Debug. Of course, you need to edit the file for AgentA on system A, and the file of AgentB on system B. Once all of these files are complete, start the scenario as explained in 摘xample 10.0pt'> 

     

     

    PART III: Examining Your Agents

     

    Each agent goes through a number of phases or lifecycles. These lifecycles are illustrated in the code. You can use them to activate and manage your agent as it becomes active in a multi-agent system. What you should notice when you look at the code is that you are given events at every point in the source. Events are generated for incoming messages, for timers and even for certain startup procedures. Here is a brief overview of the events you will see in your agent:

     

    Agent construction.

    This is basically your agent constructor. Use this as you would any normal constructor. Be aware however called: CAgentA ( )

     

    Commandline parameter handing

     

    At this point in the agent's lifecycle the arguments for the basic agent have already been processed and you now have the opportunity to handle custom parameters. You are given these parameters in the form of a list of CParameter objects. If you want you an also retrieve the called: void handle_parse_args (CCommandLine *a_commandline)

     

    Agent creation

     

    When this event is triggered, the basic agent will create a number of important objects. For example, the Communicator object is created and initialized (but not started). The BelieveDB (belief system) is created and filled with basic information like agent name and location. All of the core event modules have been created and assigned to the Communicator. The main agent logfile is created and a timestamp is set. You can find this file in the system directory under you RETSINA path. The Communicator has been given a number of lookup modules to assist it in finding agent locations through multiple sources. (You will learn more about process_create (void)

     

    Agent Initialization

     

    Now that we have all the components in place internally, we can begin to start the agent. When you have arrived at this point in the code the following events will have taken place:

          The Communicator was started and you are now registered with a lookup service.

    b.      A number of event modules are now active and have registered with other agents if applicable. For example. the DemoDisplay module will have contacted a visualization client or a logging server, depending on the visualization setup. If a Matchmaker module was configured, it will have advertised the agent's capabilities with one or more Matchmakers.

     

     

    Agent message processing


    Your agent is running and fully active at this point. There is no one specific event associated with this stage. Instead, multiple events will be recorded. Each event indicates that either the environment changed or that a message arrived from another agent. This is the part of the agent you will be working font-family:"Courier New"'>Method called: BOOL process_message (char *data)

     

    Agent timer events

     

    Each agent is given a one second resolution timer. This timer is triggered for the agent so that it can do maintenance. For example, it is used by the information agent base code to font-family:"Courier New"'>Method called: void process_timer (void)

     

    Agent shutdown

     

    Your agent has been told to shutdown. This could have been done through a variety of means, such the user interface, or through a message from other agents or agent facilities. Certain emergency shutdowns will also trigger this event. When you arrive at this point in the agent's lifecycle your agent will still have access to other agents and agent facilities. Be careful what actions you take when your agent is in this stage. In such a scenario you might not be able to rely on communications. For example, your agent may not be able to inform other agents and/or facilities that it is going to shut down.

     

    Method called: void process_shutdown (void)

     

    Network BeliefDB Data Structures

    All information regarding agent location, agent type and advertisements are collected and stored in what is called the network beliefdb. The network beliefdb is the database that represents the agent痴 beliefs about its environment. This network beliefdb is a part of the global beliefdb as provided by the AFC.

     

    Remember that the AFC does not place any restrictions on what a belief should look like. As we will show in the following example, it only provides means to maintain and manage beliefs. Understanding the structure of this dataset will greatly enhance the capabilities of your agent.

     

    First, let's take a look at a simple code fragment that lists all the agents that your agent is aware of:

     // first find the network beliefdb within the total beliefdb

     CBelief *lookup=(CBelief *) BeliefDB->find_element ("lookup");
     if (lookup!=NULL)
     {
      // we know that the lookup table is a list so we can safely convert 
      CListBelief *network_lookup=(CListBelief *) lookup;
      CLList *services=network_lookup->get_value ();
      CServiceInfo *info=(CServiceInfo *) services->get_first_element ();
      while (info!=NULL)
      {
       if (info->get_type ()==SERVICETYPE_MATCHMAKER)
       {
        AfxMessageBox (info->get_name ());
       }

       info=(CServiceInfo *) info->get_next ();  
      }
     }


    This code will traverse the lookup table and display a dialog box with the name of an agent or service for every entry found. As you can see, it does not merely retrieve the name, but a full object instead. This object, called the 'CServiceInfo', contains information regarding one particular agent. The public appearance of this class is listed below:

    class CServiceInfo : public CLList
    {
     public:
               CServiceInfo (void);
      virtual ~CServiceInfo (void);

      void  set_location (char *); // url formatted
      void  set_location (CURL *); // url object
      CURL *get_location (void);   // pointer to internal location

      void  set_expiration   (int);
      int   get_expiration   (void);

      void  set_type         (int);
      int   get_type         (void);
    };

    As is apparent, the ServiceInfo class is derived from the AFC-defined linked list class. This means that the name of the agent can be obtained by calling get_name ();, since the linked list depends on the CListElement class, which the lookup modules will use to store the agent name. The reason for using a linked list as the basis for our class is that every agent might contain one or more advertisements. That is, we used a linked list so that the agent can retrieve all of
    the advertisements for a particular agent, which are associated with its name or unique ID.


    Each advertisement is added to the list and can be retrieved by using the standard methods for accessing an AFC linked list. You should also notice that the CServiceInfo class uses URLs to specify the network location. You will have to use the access methods within the URL object to obtain parameters such as hostname and port number. (For more information on the URL object, see the chapter on tools and utilities). Next we see two methods that will either set or get an expiration time from the service information object. This expiration time is given in seconds and is primarily used internally for leasing purposes. If you want this entry to be persistent regardless of the actual presence of the agent, then use the access method to set this value to: -1. The last two methods are used to obtain or change the infrastructure type of an entry in the network beliefdb. The AFC uses the following defines to identify the role an agent or service has within an MAS:

      #define SERVICETYPE_AGENT         1
     #define SERVICETYPE_MATCHMAKER    2
     #define SERVICETYPE_DHARMASERVER  3
     #define SERVICETYPE_ANS_SERVER    4
     #define SERVICETYPE_DEMODISPLAY   5
     #define SERVICETYPE_RECOMA_SERVER 6

    The following code is an example of how to use the service type to find all Matchmakers currently known to the agent:

     // first find the network beliefdb within the total beliefdb

     CBelief *lookup=(CBelief *) BeliefDB->find_element ("lookup");
     if (lookup!=NULL)
     {
      // we know that the lookup table is a list so we can safely convert
      CListBelief *services=(CListBelief *) temp; 
      CServiceInfo *info=(CServiceInfo *) services->get_first_element ();
      while (info!=NULL)
      {
       if (info->get_type ()==SERVICETYPE_MATCHMAKER)
       {
        AfxMessageBox (info->get_name ());
       }

       info=(CServiceInfo *) info->get_next ();  
      }
     }


    As you can see, we re-used the sources from our first example and added
    a simple test on the agent type. If an agent is identified as a Matchmaker, its name will be displayed in a dialog box.

     

    Agent Destruction

     

    Method called: ~CAgentA ( )

     

    Processing Updates to the Agent Environment

     

    AFC contains code for detection of newly arrived agents and detection of agent shutdowns. In order to enable the function, add the following method in your main path, which will be called every time the network beliefdb is changed:

     virtual void process_environment_change (void);

    In future updates you will be able to get very detailed information about an agent's view of its environment. For now we will show you how to learn whether an agent has been recently added to the network beliefdb, or whether it will be removed shortly, because it is no longer present on the network.

     

    In the AFC we represent the description of an external agent in a CServiceInfo class. This class contains all information needed to use this agent. The network beliefdb is an enumeration of CServiceInfo instances. For every agent on the network of which your agent is aware, there will be one such service object. Each of those objects contains a status parameter, which indicates whether it was recently created or whether it will be destroyed. If the status flag indicates that the object was just created, then the agent it represents just arrived on the network. If the status indicates that the object will be destroyed in the next main cycle, then you know that the remote agent either crashed or shutdown. Remember that the removal of an agent is not synonymous with a remote agent shutdown. Internal leases and expiration mechanisms can also trigger the removal of a CServiceInfo instance.

    Now that you have added to your agent a way of being informed of environmental changes, you will need a bit of code to investigate what actually happened. Below is a small example of code that will traverse the network beliefdb and report on what changes occurred:

    // -------------------------------------------------------------------------

    void CExampleAgent::process_environment_change (void)
    {
     debug ("process_environment_change ()"); // just so we can see where we are

     if (state!=__AGENT_STATE_RUNNING__)
     {
      debug ("Agent not ready yet to process environment changes");
      return;
     }

     // search through the network beliefdb to see what happened

     CLList *network_db=obtain_network_db (); // this is an AFC core method
     if (network_db!=NULL)
     {
      CServiceInfo *service=(CServiceInfo *) network_db->get_first_element ();
      while (service!=NULL)
      {
       if (service->get_new ()==TRUE)
       {
        // this agent just arrived
       }

       if (service->get_gone ()==TRUE)
       {
        // this agent just left and the entry will be removed after the
        // method exists
       }

       service=(CServiceInfo *) service->get_next ();
      }
     }
     else
      debug ("No network belief db available");
    }

    // -------------------------------------------------------------------------


    As you can see from the code above, you can obtain the state of a service and see if it will be removed. If you want to have a service object forcefully removed, then call the following method:

     service->set_gone (TRUE);

    Keep in mind, however, that this is only a hint towards the management system
    that maintains the internal state of the network belief db. If a remote agent indicates that it is still alive, a new CServiceInfo instance will be created. (You can try to change your agent痴 mind about its external environment, but you cannot get it to permanently deny the reality of other agents that actually exist).


    The addition of the 'process_environment_change' method will also allow
    you to add more refined awareness of the coming and goings of infrastructure
    components in a multi-agent system. For example, your agent may want to
    register with every Matchmaker that it becomes aware of. The following code
    demonstrates how to learn whether or not a new Matchmaker has started somewhere on your local network:


    // -------------------------------------------------------------------------

    void CExampleAgent::process_environment_change (void)
    {
     debug ("process_environment_change ()"); // just so we can see where we are

     if (state!=__AGENT_STATE_RUNNING__)
     {
      debug ("Agent not ready yet to process environment changes");
      return;
     }

     // search through the network beliefdb to see what happened

     CLList *network_db=obtain_network_db (); // this is an AFC core method
     if (network_db!=NULL)
     {
      CServiceInfo *service=(CServiceInfo *) network_db->get_first_element ();
      while (service!=NULL)
      {
       if (service->get_new ()==TRUE)
       {
        // this agent just arrived

        if (service->get_type ()==SERVICETYPE_MATCHMAKER)
        {
         // a new matchmaker just arrived
        }
       }
       service=(CServiceInfo *) service->get_next ();
      }
     }
     else
      debug ("No network belief db available");
    }

    // -------------------------------------------------------------------------

    The example above only demonstrates that a new infrastructure component of the Matchmaker type was found. The following constants will allow you to check for basic infrastructure components:

     #define SERVICETYPE_AGENT         1
     #define SERVICETYPE_MATCHMAKER    2
     #define SERVICETYPE_DHARMASERVER  3
     #define SERVICETYPE_ANS_SERVER    4
     #define SERVICETYPE_DEMODISPLAY   5
     #define SERVICETYPE_RECOMA_SERVER 6
     #define SERVICETYPE_UNKNOWN       7


    Now that you know that a new Matchmaker was found, you may want to register with it. The following example uses the same code as listed above but adds the capability to register a new client with the Communicator.

    // -------------------------------------------------------------------------

    void CExampleAgent::process_environment_change (void)
    {
     debug ("process_environment_change ()"); // just so we can see where we are

     if (state!=__AGENT_STATE_RUNNING__)
     {
      debug ("Agent not ready yet to process environment changes");
      return;
     }

     // search through the network beliefdb to see what happened

     CLList *network_db=obtain_network_db (); // this is an AFC core method
     if (network_db!=NULL)
     {
      CServiceInfo *service=(CServiceInfo *) network_db->get_first_element ();
      while (service!=NULL)
      {
       if (service->get_new ()==TRUE)
       {
        // this agent just arrived

        if (service->get_type ()==SERVICETYPE_MATCHMAKER)
        {
         // a new matchmaker just arrived

         CMatchMakerClient *mm_client=new CMatchMakerClient (service->get_name
    (),BeliefDB,Communicator,0);
         Communicator->add_display (mm_client); // this will add it as a custom
    client
         mm_client->set_logger (DemoLogger);    // make sure we can log to disk
         mm_client->parse_args (m_argc,m_argv); // allow the client to process
    out custom settings

         // The following methods are normally called by the Communicator, so
         // be careful !! They will start the client and add it to the agent's
    internal management

         mm_client->change_state (__CREATE__);
         mm_client->set_registered (FALSE);
        }
       }
       service=(CServiceInfo *) service->get_next ();
      }
     }
     else
      debug ("No network belief db available");
    }

    // -------------------------------------------------------------------------


    A couple of notes on the code above. First of all, you probably noticed that there is no advertisement assigned. In this example we assume that you use the default "adv-schema.txt" file in the agent's directory. Secondly, you can see that there is a fair amount of additional management you need to do to actually add the module to the agent. In future versions of the AFC, the code above will be replaced by a single API call and the above example will be reserved for situations in which you want to add custom clients to your agent.

     

    Working With Top-Level Agent States

     

    In the previous section you may have noticed a line in the example code that looked like:

     if (state!=__AGENT_STATE_RUNNING__)
     {
      debug ("Agent not ready yet to process environment changes");
      return;
     }

    Each agent will go through a number of states during its execution life-cycle. These states dictate what events can occur within the agent and they also drive a number of important events. The events currently defined within the AFC are:

    #define __AGENT_STATE_CONSTRUCTOR__    0
    #define __AGENT_STATE_INIT__           1
    #define __AGENT_STATE_CREATE__         2
    #define __AGENT_STATE_RUNNING__        3
    #define __AGENT_STATE_SHUTDOWN__       4
    #define __AGENT_STATE_DESTRUCTOR__     5
    #define __AGENT_STATE_TOP_LEVEL_END__  6

    The current state of your agent can be obtained by examining the 'state' variable present in every class derived from CBasicAgent. Each state is set after certain methods are completed. You will have seen these methods described earlier in the manual.

    The state variable is a block of memory that is protected by the agent core. You can set the state yourself by defining a new state:


     #define __MY_AGENT_STATE_TOP_LEVEL_END__  __AGENT_STATE_TOP_LEVEL_END__ + 1

    This code will define a new state, which you are free to use if the top-level state is in: __AGENT_STATE_RUNNING__ If the agent detects that a state is set to a custom setting in a top-level state other than __AGENT_STATE_RUNNING__, then the agent will forcefully change the state. It does this to protect numerous internal modules that maintain your agent. For example, the garbage collector (that is responsible for cleaning up the network belief db) will behave with slight differences in each of the top level states.

     

    Forcing Global Lookup Refresh


    Normally you would look at the network beliefdb to get an overview of what agents are registered on the network. Sometimes however, you may want to forcefully refresh the lookup table to be absolutely sure that all the registrations are valid. You can call the following method from within your agent:

     if (Communicator!=NULL)
      Communicator->listall_agents ();


    Remember that this method resides in the Communicator and you will therefore have to obtain a pointer if you want to call the method outside of your main agent class. When you call the method listed above, a number of things happen simultaneously. One, the Communicator locks the network beliefdb. You will not be able to directly modify any entries in that area of memory. Two, the Communicator will iterate through all registered lookup modules and activate their 'list-all' method. If you have all types of lookup mechanisms enabled and if the agent has instantiated multiple copies of these modules (one for each active infrastructure component e.g., multiple ANSs), then it might take quite some time for the 'listall_agents' method to return. Also, certain lookup methods will not wait for a direct reply but instead assume that answers to lookup requests will come in asynchronously. This may result in environment updates being generated for every agent that was found through this asynchronous mechanism. See Section 1 for information on how to process environment updates.

     

    Client Module

     

    The AFC provides a number of mechanisms do facilitate persistent connections. This capability was previously undocumented since it had not passed tests that were successfully completed on demonstrate the steps to take to set up a persistent connection.

    The client class is one of the mechanisms available to developers to create a persistent dialogue with other agents. This class represents the base class for all classes involved in setting up registrations with other agents. For example, the AFC uses the client class as the basis for interactions with middle-agent clients. These clients advertise the agent's capabilities with a middle agent. First, we will examine the basic client class and its capabilities:


    class CClientBase : public CLogFacility
    {
     public:

       CClientBase (char *,
    p;              CBeliefDB *,
    p;              CCommunicator *,
    p;              unsigned int);

       void  set_ontology       (char *);
       char *get_ontology       (void);

       void  set_performative   (char *);
       char *get_performative   (void);

       void  set_language       (char *);
       char *get_language       (void);

       void  set_create_string  (char *);
       char *get_create_string  (void);

       void  set_destroy_string (char *);
       char *get_destroy_string (void);
    };

    There are three important sections to be mentioned in the class definition above.

    A. Constructor
    B. Envelope Configuration
    C. Event Configuration

    A. Constructor

    The constructor takes a number of pointers to objects it needs to function independently in the background. The first parameter is a string that holds the name of the agent with which you wish to have a persistent connection. Next is a pointer to the BeliefDB, which can be obtained from within your agent code. Then, there is a pointer to the Communicator, which can also be obtained from any class derived from CBasicAgent. The last parameter is a flag that is used by the base class under certain conditions. This last parameter can be safely set to 0.

    B. Envelope configuration

    When your agent uses the object that results from using the client classes, it will ask the client to send messages at specific times. When a message is sent by your client module it might need additional
    information such as a performative and/or ontology etc. There are three methods available to configure these message settings. By default the following methods are called if no other preferences are specified. Every time your client module sends out a message it will use these variables:

     set_performative certain times, in response to external events or internal signals, the basic agent will generate events. All clients must be able to respond appropriately to these events to ensure proper functionality at all times. As we have written above, there are a fair number of events that can be generated at any time during an agent's execution lifecycle. It is important to recall the basic events, since you will see them occur when you start to build your own derived client classes or log modules:

     #define __IDLE__         0
     #define __PLANNING__     1
     #define __ADVERTISE__    2
     #define __CREATE__       3
     #define __DESTROY__      4

    There are a number of additional methods that you will need to know if you want to add more detailed interaction between the agent and your client module:

     public:
      virtual BOOL change_state        (unsigned int);
      virtual void process_timer       (void);
      virtual void process_message     (char *,int);
      virtual void process_create      (void);

    These methods are actually derived from the CLogFacility class and are used within your client for maintenance and connection management. When the basic agent generates a __CREATE__ event, the method 'change_state' is called in your client module to indicate that it will need to register with the server (middle agent for example). If a __DESTROY__ event is detected, the client module will be notified again using the 'change_state' method, but this time it will trigger the unregister method.

    At this point, we advise against overloading the four methods listed above, at least until you are familiar with the workings of the CLogFacility class. We've included the detailed information here to give you better insight into the inner workings, in case things go wrong in your agent. You should be able to determine from the logfile the state that the agent is in and how your client is responding to those events.

    Below is sample code that demonstrates how a client module can be assigned to an agent. We advise that you do this in the
    'process_create' method, but it can be added at any point if the agent is in either the '__AGENT_STATE_CREATE__' state or the '__AGENT_STATE_RUNNING__' state.

    void CExampleAgent::process_create (void)
    {
     CClientBase *client=new CClientBase ("Server",BeliefDB,Communicator,0);

     client->set_create_string ("(register)");
     client->set_destroy_string ("(unregister)");

     client->set_logger (DemoLogger);
     client->parse_args (m_argc,m_argv);

     Communicator->add_display (client);
    }

    The following steps were taken in the code above:

    1.

     CClientBase *client=new CClientBase ("Server",BeliefDB,Communicator,0);

    Create a new client and provide it with the proper parameters. In this case the client will try to connect to an agent called 'Server'. The BeliefDB and Communicator pointers were obtained from the basic agent and the last parameter was set to 0.

    2.


     client->set_create_string ("(register)");
     client->set_destroy_string ("(unregister)");

    We now configure the subscription and unsubscription behavior by providing a registration string and unregistration string. These two strings will be sent within the content field of the actual messages. The client will trigger subscription and unsubscription events when it detects that its agent either shuts down, crashes or boots.

    3.

    If you want your client to log messages to the global logfile then you may want to add the following line of code to the agent:

     client->set_logger (DemoLogger);

    This code will allow you to call the 'debug' method if you decide to overload the base client to build more refined classes.

    4.

     client->parse_args (m_argc,m_argv);

    If you want to process command line arguments within your class, or if you know that the client class takes specific command line parameters, then you will need to call this method in the client. This method is a virtual method and can be used in your own overloaded classes to process specific parameters for your class.

    5.

     Communicator->add_display (client);

    This code will tell the Communicator that there is a new client module that needs to be added to the total list of background client modules. In doing this, you make sure that your client's background management code is called at appropriate times.

    From this point, you do not have to manage the client; the basic agent will do this for you. The agent's core code also takes care of deleting the object when your agent shuts down; you should not do so.

     


    Agent User Behavior, Agent Naming Convention

     

    Open-network MASs face security threats from malicious agents. These agents may try to unregister their competitors from Agent Name Servers and Matchmakers, eavesdrop on supposedly private communications, and spoof other agents and agents and the humans who deploy them. System integrity demands that agent users be held accountable for problems caused by misbehaving agents.

     

    While in a future release of our ANS, the security architecture we are developing will counteract these threats by binding each agent to a unique Agent ID (or AID) (see JavaANS), in the current release of the AFC agents and ANS, we rely on the integrity of the agent users in the community to prevent such malfeasance.

     

    To prevent agent spoofing or the agent name would be

    miker.areolis.ri.cimds.cmu.edu

    Note that the agent need not be running at this location. The agent name is merely used to signify that the agent user痴 name and originating domain, not the location at which the agent integrated into the agent communities, it is necessary that all agent users adhere to the above-referenced naming convention. This is especially the case for those users/agents enabling Discovery of/by other agents and agent systems. (See section on Discovery for enabling/disenabling Discovery).

     

    The other users of the agent community will regard users who choose to ignore or subvert the agent naming convention as hostile and will treat them accordingly. Users who purposefully unregister or register agents not belonging to them will also be regarded as hostile to the agent community.

     

    We have added an additional command line parameter to the BasicAgent, which will allow you to make your agent name unique. If you start your agent the normal way then the name you specify on the command line or hardcode in your agent will be used in registrations 'as is'. However, if you specify the following parameter:

     
    -unique yes

    then the agent will append a globally unique ID to your agent name and use that during it's execution life-cycle.

     


    PART IV: VISUALIZATION TOOLS

     

    Using The KQML MESSAGE Sender



     

    Introduction


    No development environment or toolkit is complete without its set of testing utilities. The RETSINA architecture has its own set of tools, one of which is the KQML message-sending tool. This tool allows you to send customized messages to an agent and to examine the responses. Besides the basic message sending and receiving functionality, the tool offers testing sets to test the RETSINA visualization system and Agent Name Servers.

     

    Main WindowThe window is divided into three main areas dedicated to specific agent tasks. The top portion is dedicated to message generation and message inspection. In the middle you can see the controls available to manage and work with an Agent Name Server. Finally there is the visualization tool set at the bottom. Each of those areas will be discussed in detail in the following sections.

    Figure 1

     

     

    Before you can use any of registered itself with one of the ANS servers. (See the documentation of 鄭NS mso-position-horizontal:left' coordorigin="7200,12960" coordsize="3133,1461" wrapcoords="-207 -223 -207 14029 21703 14029 21703 -223 -207 -223">

    Figure 2

    In the next 5 steps we will demonstrate how to configure the message sender to represent an agent and register it with an ANS.

     

     

     

    First of all we need to give use. The following example is of an agent for local use only). The listening port does not have to be globally unique, but you cannot use the same port as other applications on your machine. By default the port is set to 6678.

     

     

     

     

     


    In this example we set our agent name to AgentA, as shown in Figure 3.

    Next we select an Agent Name properly set the

     


    If an error occurs you will see a message in the status bar at the bottom of the window and a dialog box will appear informing you of the specifics of the failure.

     

    Figure 5

     
    Most failures in registration occur because the listening port that was specified is already in use by another agent. Simply change the port number and try again.

     

     

     

    Message Management

     

    In this section we will demonstrate how to send messages to other agents. As we have mentioned above, the top part of the application痴 window is dedicated to message sending and receiving. On the left are controls to create the messages and on the right are two message boxes that will show different views of the messages coming in.


     

     


    Parsing Messages

     

    In the instructions that follow we assume that you have registered at least two agents with an Agent Name Server, and that you have launched at least two Win32KQMLCenter information.

     

    Choose one Win32KQMLCenter window to send a message to another agent.

     

     

    Configure the "KQML" Section to send a message to another agent. The "KQML" Section consists of the following fields as shown in Table 1:

     

     

    Performative

    The set of permissible operations that agents may attempt on each other's knowledge and goal stores. Examples include: "tell," "send" and "insert."

    Receiver

    The name of the agent that will receive the message.

    Content

    The content of the message.

    Reply-with

    A string of automatically generated characters. Each message generates a unique identifier. Pressing "generated string" will generate a different message ID.

    In-reply-to

    A blank field for entering a message's unique identifier. This field can be used to reply to specific messages.

    Ontology

    The ontology that the agents will use to communicate. Examples include: "satellites" and "stocks."

    Language

    The type of parsing language (e.g. gin 1.0) that the agents will use.

    blank template

    A menu for selecting different kinds of generic messages (e.g., advertisements). This menu item is not implemented.

    send message

    The button that sends the message to the agent specified in the "Receiver" field.


    Table 1

    The required fieldsare: Performative, Receiver, and Content. The optional fields are Reply-with, In-reply-to, Ontology, Language and blank template. The default settings for the optional fields are sufficient to test message formats to agents.

     

     

     


    Miscellaneous Tools

     

    Scattered across the application痴 window are a number of small tools that can give you information about the environment and the internals of the application. Figure 11 shows three buttons that are used to display system information about the software that was used to build the message tool. It is available in every agent and was designed to obtain low-level information about an agent痴 state and condition.

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     


    Figure 12 is the Communicator info window. It can be used to quickly obtain your network settings like IP address and local listening port. When reporting a bug within the Agent Foundation Classes or Communicator code, please provide the version number listed in this dialog box.


    PART V: Data Structures, Tools and Utilities

    Data Structures

     

    The Agent Foundation Classes support all basic concepts of software engineering. A variety of well-known structures and concepts are provided with agent development in mind. In this section, we will introduce each of the provided data structures and demonstrate how to use them. We will also show how they fit into the agent paradigm and what other important tools within the AFC depend on them.

     

    First of all, we need to describe and explain a basic concept of the AFC, known as the CListElement. The name is deceiving; the CListElement is not an element designed to be used in a list. In fact, it is used as the basis of virtually every class within the AFC. While there are a number of other classes beneath the CListElement class, they safely substitute CListElement for their class names. The CListElement supports a number of elementary methods used by all classes derived from it. These methods are:

     

     

    • A parent
    • A pointer to a following element
    • A pointer to a preceding element

     

    These pointers are private and can only be obtained and changed through the access methods. All point to objects of the same CListElement type.

     

    Following the access methods are two methods to manage a content pointer. The actual content is not stored in the object but rather a pointer to the location of the content. This means that when a CListElement object is destroyed the memory that the object points to will not be freed-up. You will have to manage this memory yourself. However, certain classes derived from CListElement cast this pointer to specific data types that are destroyed when the destructor is called. The CParameter class, for example, assumes the content pointer points to a string.

     

    The last two methods are the basis of what we call Collapsible Data Structures. These methods enable an object to collapse itself into an ACL-formatted string. The methods can also be used for recreating the object itself from a string. For now it is important to know that every class derived from a CListElement has this behavior.

     

    There are two more important methods that define the basic behavior of the CListElement. These methods are not listed above because they are inherited from the CDNA class. However for all intents and purposes they are part of the CListElement. The declaration of the methods is as follows:


    char *get_name (void);

    Every class has a label that can be accessed by these methods. It is not always necessary to give an object a label and omitting one will not interfere with the functioning of the object. The label/name is stored as a private string and cannot be accessed directly. Certain derived objects will not allow you to change the label once it has been set by the constructor. This characteristic protects the persistence of certain objects.

     

    There is one final method that can be used to obtain certain low-level information about the object. If you decide to construct your own basic types then you will have to become familiar with the other methods that related to this set of functions:


    int get_btype (void);

    Every object in the AFC has a base type. This is either a namespace string or a numerical ID. In the cases of the most fundamental types, a number expresses the type. The method above can be used to obtain this type. There is a finite set of types defined by the AFC, which lists the most basic types of data-structures. These are:


    Another component similar to the CListElement is the CTreeElement. This element can be used in binary trees, listing above, the tree element is designed to be used in a binary tree. (We do not provide more complex trees and tree representation, since we do not want to dictate to developers what these structures should look like).

     

    Every tree element contains three nodes of a similar type, to represent the tree. These are:

     

    -        A parent node

    -        A left leaf node

    -        A right leaf node


    Each of these nodes can be individually assigned and retrieved. Under most circumstances, however, the tree classes mechanism.

     

    Two other commonly used datastructures are available, the queue and the stack. Each of these classes are based on the CLList class and will therefore have all the functionality of that class. First let痴 take a look at the queue termed CQueue in the AFC. Only four methods are needed to turn an AFC list into a queue. We need a way of fixing the size of the queue and we need to add and remove elements from the style='font-family:"Courier New"'>


    Be aware that changing the size of an existing queue containing a number of elements might produce unwanted effects, if the size of the new queue is smaller than the number of elements currently present in the queue. By default, the queue size is set to 100 from with the CQueue access functions. The figure below shows the methods within the CStack class, and their interfaces.


     

    Tools and Utilities

     

    In this section, we will discuss a number of tools available within the AFC libraries that considerably facilitate agent-based development.

     

    Generating and using GUIDs


    The Agent Foundation classes fully support the generation of Globally Unique Identifiers (GUIDs). When you created your agent with the AFC Wizard, the AFC headers were incorporated in your agent, which automatically gave your agent GUID capability. Here is an example on how to generate a GUID:


    #include "c_afc.h"

     

    CGUID *uuid=new CGUID;

    printf ("Newly generated ID: [%s]\n",uuid->get_guid ());

    delete uuid;


    You can use an object instantiated from the CGUID class as a placeholder for one ID, or you can use the object to keep generating new ones. In the following example, we show how to generate 10 IDs:

    #include "c_afc.h"

     

    CGUID *uuid=new CGUID;

     

     

    delete uuid;


    While it is not useful from a developer痴 perspective, the class also contains a method to set the internal uuid to a specific string. This was implemented for internal use only. You can set the internal string by calling:

     

     

    When using the class you will notice that the id's the objects generate are like Windows registry keys. This was done intentionally because it is much easier for a developer to strip the outer parenthesis than it is to add them after the string has been created. Let's take a look at an example on how to convert a GUID to a general uuid string:


    #include "c_afc.h"

     

    CUtils utils;

    CGUID *uuid=new CGUID;

    printf ("Newly generated ID: [%s]\n",uuid->get_guid ());

    char *stripped=uuid->get_guid ();

    printf ("Stripped ID: [%s]\n",utils.remove_curly_brackets (stripped));

    delete uuid;

    The output of this code should look something like this:

    Newly generated ID: [{8D831E25_1DEE_11D5_A944_F95168027CA4}]

    Stripped ID:#include "c_afc.h"

    CUtils utils;

     

    Obtaining the RETSINA variable and home directory of the agents

     

    As you may have noticed, the RETSINA agents depend on an environment variable called RETSINA. This variable path from the RETINSA variable.


    char *home=utils.get_home ();

    if (home==NULL)

    else

    CLList *filelist=utils.file_list (".","*.txt");

    if (filelist==NULL)

    }

    while (temp!=NULL)

    {

    delete filelist;

     

    The benefit here is that the files are stored within a CLList as CListElements. You can use the tools that operate on these objects to accomplish more complex tasks.

     

    We致e separated the listing of files from the listing of directories to rule out any confusion about what is maintained in the listing. Also, the AFC has to compile on a variety of platforms, and not all platforms support a physical file-system; a directory listing may mean something completely different on a mobile phone. The example below demonstrates how to obtain a listing of all sub-directories in the current directory.


    CLList *dirlist=utils.dir_list (".");


     

    CFileBuffer filebuffer;


     

    The CFileBuffer class (shown above) may seem a bit strange at first. It is the first version of a class that will be managed by something called CIOBuffer. This class will present a virtual io layer to the agent, which allows it to load resources using URL's. The CIOBuffer will then instantiate the appropriate base class to do the actual work.

     

    Database File Access

     

    The AFC contains tools and utilities that are not necessarily designed for agents but will nonetheless assist and expedite development and research. In this section we will explain how to use the CDBFileIO class, with the accompanying class: CDBRecord. These tools were initially designed to give agents quick access to flat text-based database files. One of the later versions of the C++ used these classes to maintain a permanent cache file of known agent registrations for persistence purposes. Later, when the AFC started adopting the 'to_string' and 'from_string' technologies, another capability was added. Every database record and every database using those records can be collapsed into an ACL formatted string, which can be sent to another agent and expanded into an internal data-structure.

     

    Before we explain how the database class works, we need to demonstrate how a record is defined and used within the AFC. The public appearance of this class is:

    class CDBRecord : public CLList
    {
     public:
               CDBRecord (void);
      virtual ~CDBRecord (void);

      BOOL  from_string (char *);
      char *to_string   (void);
    };

    As you can see, the basic record class does not contain any specific references to data types held within the record. It does not contain an index either. The class listed above was designed to allow developers to construct their own records. Currently, the record assumes that its contents are a list of CListElement objects (See documentation on the CListElement class, above). The record uses the 'name' variable to store the content of a record field. No translation or casting is done on the data and the developer is responsible for refining this behavior. As you can see we have two ACL management methods defined in the record class:

     BOOL  from_string (char *);
     char *to_string   (void);

    You can use the 'from_string' method to fill a new record with data from an ACL formatted string. Any existing data within the record will be deleted. Here is an example of what this might look like:

     CDBRecord *record=new CDBRercord ();
     BOOL ret=record->from_string ("(record :element (first) :element
    (second))");
     if (ret==FALSE)
      printf ("Error expanding ACL string");
     else
      printf ("Successfully filled record");

    After the operations listed above the contents of the record would be:

     "first"
     "second"

    When you want to send the contents of a record to another agent, you can use the code below to collapse the data in the record into an ACL formatted string:

     char *string=record->to_string ();
     if (string==NULL)
      printf ("Unable to collapse data into ACL string");
     else
      printf ("Collapsed data into: %s",string);

    Now that we have described how a record is defined, we can proceed with the documentation of the database class. The public face of this class is defined as:

    class CDBFileIO : public CFileBuffer
    {
     public:

               CDBFileIO   (void);
      virtual ~CDBFileIO   (void);

      BOOL  load_records   (char *);
      BOOL  save_records   (char *);

      BOOL  from_string    (char *);
      char *to_string      (void);

      int   get_nr_columns (void);
      int   get_nr_rows    (void);

      BOOL  add_record     (CDBRecord *);

      void  reset_db       (void);
    };

    As you can see, the constructor does not take any parameters. Creating an object of this class will create an empty database. You can either start adding records by hand or load them from a file. Keep in mind that this particular class is the base class for all databases in the AFC. As such it represents a flat view of a database. Records are organized in a matrix representation whereby the first record contains the keys for the database. To clarify further how this flat database view works, let's look at an example:

     // create empty database
     CDBFileIO *database=new CDBFileIO ();

     // create the first record that will hold the list of keys
     CDBRecord *record=new CDBRecord ();

     // add a number of keys to the record ...
     CListElement *key1=new CListElement ();
     key1->set_name ("SSNR");

     CListElement *key2=new CListElement ();
     key2->set_name ("First Name");

     CListElement *key3=new CListElement ();
     key3->set_name ("Last Name");

     record->add_element (key1);
     record->add_element (key2);
     record->add_element (key3);

     if database->add_record (record)==FALSE)
      printf ("Unable to add record to database");

    The code shown above sets up a new database using code. Now that you have a formatted database, you can start adding fields. This works exactly the same way as adding the keys to the database. Below is a small fragment that demonstrates this:

     // create the first record that will hold the list of keys
     CDBRecord *record=new CDBRecord ();

     // add a number of keys to the record ...
     CListElement *field1=new CListElement ();
     field1->set_name ("123455652");

     CListElement *field2=new CListElement ();
     field2->set_name ("Martin");

     CListElement *field3=new CListElement ();
     field3->set_name ("van Velsen");

     record->add_element (field1);
     record->add_element (field2);
     record->add_element (field3);

     if database->add_record (record)==FALSE)
      printf ("Unable to add record to database");


    The database base class as described here was designed to give agent researchers a quick and easy tool to take their experimental results and stream them to a database file for future examination. Interaction with the actual files is achieved through two methods:

      BOOL  load_records (char *);
      BOOL  save_records (char *);

    We assume here that your files will be dos or unix text formatted files with TAB separations between columns. Use the 'load_records' method to fill a newly created database object with the contents of a file. The parameter that this method takes is the name of a file that holds the database. Subsequently you can save a database by calling the method 'save_records ()' with the name of a file to be saved as a parameter. In the case you want to flush databases using the last used filename, you can use the method 'save_records' with no file parameter. This will call 'save_records methods is:

      BOOL  from_string    (char *);
      char *to_string      (void);

    (Note: If you want to store your database as a KQML string on disk, you will have to maintain your own file pointers).

    After you have loaded or filled a database you can obtain some basic information from it by using:

      int   get_nr_columns (void);
      int   get_nr_rows    (void);

    These methods ultimately calculate the extend of the database matrix and return the result.

    In case you want to completely clear an existing database object, you can call:

      void  reset_db       (void);

    This will:

    • Remove all records
    • Reset the keys
    • Set the number of columns and rows to 0

     

     

    Wildcard Matching Support


    The purpose of this class is to store and manage a number of wildcard descriptions. Matching methods can be used to match a string to a set of wildcards. The wildcard class does not assume a file system model, although it can be used for that. Below is the public part of the wildcard class:


    class CWildCard : public CLList
    {
     public:

      CWildCard (void);
     ~CWildCard (void);

      BOOL  add_wildcard (char *);

      BOOL  match        (char *);
      BOOL  match_nocase (char *);

      BOOL  from_string  (char *);
      char *to_string    (void);
    };

    As you can see, the class is derived from a linked list. This allows a wildcard object to store multiple variations of the wildcard. No additional initialization is needed. Once the object is created, you can add one or more wildcard definitions. For example:

      CWildCard *wildcards=new CWildCard ();
      wildcards->add_wildcard ("infoagent*");
      wildcards->add_wildcard ("infoentity*");
      wildcards->add_wildcard ("info*");

    After you have configured the object with a number of examples, you can give it a string to examine. There are two methods available to inspect a sample string:

      BOOL match        (char *);
      BOOL match_nocase (char *);

    Either method will return TRUE if the string matches any of the patterns and FALSE if it matches none. Use the second method to disregard any case matching between wildcards and input string.

    As with most AFC classes, you can use the 'from_string' and 'to_string' methods to collapse the data into an ACL formatted string. In the CWildCard class, these methods are declared as:

      BOOL  from_string  (char *);
      char *to_string    (void);

    The resulting ACL string describes the list of wildcard patterns stored in that particular object.


    Adding Custom Sockets to Your Agent


    It is possible to add your own socket to an AFC agent. This might be useful in cases where the traffic going through the socket is of a type not supported by any existing AFC socket. What follows are instructions for adding a custom socket to your agent.

     

    You will need to create a new socket class based on one of the pre-defined AFC sockets. The possible socket base types are:

    CSocketBase      // basic TCP/IP socket
    CDataGramSocket  // modification on the previous one that supports UDP
    CMulticastSocket // multicast implementation of CDataGramSocket


    The CSocketBase and CDataGramSocket behave identically and support the same API. For the third type, you need to add two more methods to configure it:

      void  set_group_ip          (char *);
      char *get_group_ip          (void);

      void  set_group_port        (int);
      int   get_group_port        (void);


    These methods allow you to configure the multicast group and port. The AFC already supports multicast, but this socket is pre-configured to use the UPnP group. In an example below we will demonstrate how to properly use these methods. But first, there is one more method that is crucial for a custom socket:

     set_sockettype (__MY_SOCKET__);

    This method will identify your socket instance as a custom socket. Whenever
    data arrives on this channel, your agent will be informed through the CBasicAgent method:

     virtual void process_custom (CSocketBase *);

    When this method is called for your agent, you will be given a pointer to a socket base class. This is in actuality a pointer to an instance of your socket type, which you will have to cast to the proper type. Below is a full example of an agent that incorporates a custom multicast socket.

    #ifndef __CUSTOM_SOCKET__
    #define __CUSTOM_SOCKET__

    #include "c_afc.h"

    #define __MY_SOCKET__ _USER_+1

    class CMySocket : public CMulticastSocket
    {
     public:

       CMySocket (CWnd *);
      ~CMySocket (void);
    };

    #endif // __CUSTOM_SOCKET__

    Below is the implementation of our new socket. We only call three methods to
    configure our instance. The third one is mandatory, since this will properly identify our socket as a new type:


    /*--------------------------------------------------------------------------
    -----------*/
    CMySocket::CMySocket (CWnd *a_wnd) : CMulticastSocket (a_wnd)
    {
     set_group_ip   ("239.192.0.14");
     set_group_port (1900);
     set_sockettype (__MY_SOCKET__);
    }
    /*--------------------------------------------------------------------------
    -----------*/
    CMySocket::~CMySocket (void)
    {

    }
    /*--------------------------------------------------------------------------
    -----------*/


    Now that we have the layout of our custom socket, we can add it at runtime
    to our agent. Make sure you add the socket at the appropriate time in your
    agent. We need a running Communicator, which limits the place to insert our socket to either the
    'process_create' method or one of the event methods that can occur when the agent is in the __AGENT_STATE_RUNNING__ state.

    /*--------------------------------------------------------------------------
    -----------*/
    void CExampleAgent::process_create (void)
    {
     // create new socket and give a pointer to our message handler 'handler'

     CMySocket *simcast=new CMySocket (handler);

     // add the socket to our agent ...

     add_alternative_socket (simcast);

     // since this socket is a multicast socket, we need to join the multicast
    group

     int ret=simcast->JoinGroup (get_group_ip (),get_group_port (),3,FALSE);

     // see what happened ...

     if (ret==TRUE)
      debug ("<CExampleAgent> Sucessfully initialized custom socket");
     else
      debug ("<CExampleAgent> Error initializing custom socket");
    }
    /*--------------------------------------------------------------------------
    -----------*/


    We now have a new socket type running in our agent. Remember that sockets in the AFC are always fully duplex. The Communicator assumes that it will only use one socket to send and receive to an agent. There is no little amount of code present to guarantee that no more sockets than necessary are used to talk to an agent. You can freely send data over this socket using the 'mfc_send' method. When data arrives on the custom channel your agent will be notified using the:

     virtual void process_custom (CSocketBase *);

    method. If you override this method you will need to implement the necessary code to handle the data ready in the socket. Below is an example of how this can be done using our custom socket from the previous code fragments:

    /*--------------------------------------------------------------------------
    -----------*/
    void CExampleAgent::process_custom (CSocketBase *a_socket)
    {
     char message [1024];

     if (a_socket==NULL)
     {
      debug ("<CExampleAgent> Empty socket");
      return;
     }

     CMySocket *target=(CMySocket *) a_socket;

     AfxMessageBox (target->get_data (1));
    }
    /*--------------------------------------------------------------------------
    -----------*/

    Miscellaneous Utilities

     

    Text Box:  #include "c_afc.h"

 CUtils
utils;

Again, make sure you include the Agent Foundation Classes header and add an object of type CUtils:

     

    Since MASs are largely characterized by the use of messages being exchanged between agents in text format, we provide a number of tools to make development easier. The following tools demonstrate utilities to manipulate strings.

     

    White Space

     

    When working with KQML or XML messages, it is useful to know whether or not the content of a field contains readable characters. You can use the code shown below to determine whether a string contains white space only.

    Text Box:  BOOL empty=utils.is_empty_space ("hello world"); //
empty is FALSE

 BOOL empty=utils.is_empty_space ("     
     "); // empty is TRUE


    Tokenizing

     

    In the AFC the parser, classes will use string tools to create strings from lists and lists from strings. Any CLList object containing objects derived from CListElement can be expressed in a string, and any string containing elements separated by a elements. The code below demonstrates the creation of a list from a string of ("retsina.agent.middleagent.matchmaker",".",result);

     

     

    CListElement *temp=NULL;

     

     

    An ACL-formatted string encodes assumptions about the contents of the field stored in the string. Let us assume that it does not matter whether or not the fields are stored as uppercase or lowercase, but that the internal matching does depend on uppercase. It may then useful to convert an entire list to uppercase. The following code fragment demonstrates how this can be achieved. The resulting labels of the elements in the list will all be in uppercase.

     

    CListElement *temp=NULL;

     

     

    Below is a fragment of code you can use to display the contents of the labels of a list:

     

    CListElement *temp=result->get_first_element ();

    String Manipulation


    Since most agents communicate by exchanging strings, we provide a number of tools to manipulate and manage agent-specific strings. Most of the tools were developed to accommodate the easy development of code dealing with ACL fragments. The following related operations are example demonstrates how to remove parentheses. The same code can similarly be used to remove other characters.

    Text Box:  char *string=new char [strlen ("(hello
world)")+1];
 strcpy (string,"(hello
world)");

 char *formatted=utils.remove_parenthesis
(string);
 
 printf ("Formatted string:
[%s]\n",formatted);

    IMPORTANT! The methods listed above work directly on the strings you provide. This means you cannot use statically declared strings as parameters. Be careful with these methods

     

    URLs and Web Development

     

    The AFC contains two main classes to facilitate web-related development: the URL class and a web-socket class. First, we will show the URL class. Then we move to a web-socket class that can be used to obtain the contents of a URL or URI. Let us first look at an example of a simple URL operation:

    Text Box:  char formatted_url
[]="http://www.softagents.org:80/index.html";


CURL *url=new CURL;
 if (url->parse_url
(formatted_url)==TRUE)
 {
  printf ("url:    
[%s]\n",url->get_name ());
  printf

"-----------------------------------------------------\n");

 printf ("protcol: [%s]\n",url->get_proto ());
  printf
("host:    [%s]\n",url->get_host ());
  printf
("port:    [%d]\n",url->get_port ());
  printf
("page:    [%s]\n",url->get_webpage ());
  printf
("cgi:     [%s]\n",url->get_cgi ());

  // let's
change the hostname and see what the new url is 
  
 
url->set_host ("www.excite.com");

  printf
("url:     [%s]\n",url->get_name ());
  printf

"-----------------------------------------------------\n");

 printf ("protcol: [%s]\n",url->get_proto ());
  printf
("host:    [%s]\n",url->get_host ());
  printf
("port:    [%d]\n",url->get_port ());
  printf
("page:    [%s]\n",url->get_webpage ());
  printf
("cgi:     [%s]\n",url->get_cgi ());
 }

else
  printf ("Unable to parser url");


delete url;
As you can see, the name of the URL object always contains the completely formatted URL. You can access the different fields and change them to point the URL to a different location. Be aware, however, that some of the fields can be set to NULL. For example, if the URL does not contain a reference to a CGI script, then you will get a NULL back when you attempt to access that field.

     

    The CURL class is based on an unfinished CURI class, which is more generic than the URL and does not understand concepts such as port number and CGI scripts. Now that we have an object we can use to represent the location of resources on the web, we can start to use the CHTTPSocket class to retrieve this data. You can provide a pointer to either a CURL object, or to a string containing the formatted URL.

     

     

     

     

     

     

    Appendix A: Comparison of Agent-Building Systems



     

    JADE

    RETSINA

    Bee-gent

     

    Objective

    FIPA-Standard

    Middle agents allow heterogeneous agent types to interoperate successfully.

    To provide a coordination mechanism for already-existing applications and databases

     

    Version

    JADE 2.4 and LEAP 2.0

    AFC SDK 1.15

    Bee-gent 2.1

     

    Organization

    CSELT and LEAP Project

    Robotics Institute, CMU

    TOSHIBA Corp.

     

    Language

    Java

    C++,C,Java

    Java

     

    Platform / OS

    Any platform where Java VM is available

    Windows, SunOS, Linux

    Any platform where Java VM is available

     

    Agent Intelligence

     

    Interaction Protocol

    Useful behavioral patterns are provided. Also, FIPA-defined protocols are implemented in advance.

    A specific Interaction Protocol handling module cannot be found.

    IPs are described in UML state diagram and sequence diagram in the visual editor. Also, some patterns are provided.

     

    Planning facility

    Provided by JESS (Java shell of CLIPS)

    Generic planning facilities are provided

    Provided. It's similar to JACK.

     

    Reasoning facility

    Provided by JESS (Java shell of CLIPS)

    It seems to be there, but not in AFC SDK.

    Provided. It's similar to JACK.

     

    BDI model

    Agents' mental states are implicitly expressed at the execution of Interaction Protocol.

    Belief DB is present for agents and developers to use.

    Provided. It's similar to JACK.

     

    Agent utilities / tools

     

    Federated FIPA-DF with GUI

    ANS (currently not federated), UPnP discovery as alternative to ANS

    LDAP wrapper is provided.

     

    Others

    JSP support

    Matchmaker, User profile management, GPS tool, NLP, Grid display,
    Decision-making for route planning and team coordination, Mail, Speech, Movie

    Servlet support

     

    Message transport

    IIOP (CORBA), Http (SOAP is not yet), TCP/IP socket for inter-platforms. RMI for intra-platform. SMTP and WAP are under development.

    TCP/IP socket

    Http (SOAP is not yet)

     

     

     

    Security feature (tampering and leakage of communication)

    " Today no security aspect has been implemented in JADE. Good work is however ongoing and we expect to introduce security at the end of 2001. "

    No specific security component implemented. Provisions are present for future implementations.

     

     

     

    Safety feature

    I cannot find the description about safety.

    I cannot find the description about safety.

    Snapshots of agents to be taken at any time

     

     

     

    Mobility support

    Possible within a platform

    Planned in future versions

    Possible

     

     

     

    Device support

    LEAP (a subset of JADE) is for J2ME-CLDC and pJava.

    Smallest agent ever built: 32K for PalmOS

    Now implementing for cellar phones which installed J2ME-CLDC

     

     

     

    Architectural feature

    FIPA architecture, i.e., AMS, DF, MTP, etc.

    Containment of Agent Shell, Agent Container and Modules. Pre-defined Agent Shells are shipped with AFC.

    Wrapper architecture. Stationary agents can wrap the applications and mobile agents go around them.

     

     

     

    Standard spec.

    FIPA

    None

    FIPA (agent message only)

     

     

     

    Development, Monitoring and Management

     

    Development tools

    The following monitoring agents can be also used for debugging use.

    Integrated into Visual C++ 6.0 for Windows

    Interaction Protocol and Rules for Planning and Reasoning can be described in the visual editor.

     

     

     

    Monitoring tools

    Platform administration agent, message inspection agent, and message tracking agent with a graphical interface.

    - MessageCenter allows you to send custom messages, debug ACL messages, and interact with an ANS. DemoDisplay allows visualization of agent interaction. MatchMaker Tools allow investigation of middle agents

    Web browsers-based monitoring and management tool for agent current status and messages

     

     

     

    Documents

    Administrator's Guide, Programmer's Guide, (lots of) Tutorials, Java API document, FAQ, etc.

    AFC Developers Guide

    Getting Started, Tutorial, Tools Manual, Java API document, FAQ, etc.

     

     

     

    Source code

    Yes

    No

    No

     

     

     

    Samples

    14 examples (Ping to JESS)

    16 example (GUI agent to Matchmaker to Auction to Planning)

    11 examples (Hello world to Planning)

     

     

     


     

    Appendix B: RETSINA Software License

     

    RETSINA Software License

     

    CARNEGIE MELLON UNIVERSITY NON-EXCLUSIVE END-USER SOFTWARE shall be void.

     

    6. Loan, distribute, rent, lease, give, sublicense or otherwise transfer the CMU Software (or any copy of the CMU Software), in whole or in part, to any other person or entity.

     

    7. Alter, translate, decompile, disassemble, reverse notices or startup messages contained in the CMU Software.

     

    9. Export the CMU Software or the product components in violation of any United States export laws.

     

    Title to the CMU Software, including the ownership of all copyrights, patents, trademarks and all other intellectual property rights subsisting in the foregoing, and all adaptations to and modifications of the foregoing shall at all times remain with CMU. CMU retains all rights not expressly licensed under this Agreement.

     

    The CMU Software, including any images, graphics, photographs, animation, video, audio, music and text incorporated therein is owned by CMU or its suppliers and is protected by United States copyright laws waiver of CMU's rights under United States copyright law.

     

    This Agreement and your rights are governed by the laws of Agreement shall continue in full force and effect.

     

    THIS LICENSE SHALL TERMINATE AUTOMATICALLY if you fail to acknowledge and agree that providing such feedback will not obligate CMU to may be requested to load and run CMU Software for the purposes of executing WARRANTY ON CMU SOFTWARE

     

    You expressly acknowledge and agree that your use of the CMU Software is at

    your sole risk.

     

    THE CMU SOFTWARE IS PROVIDED "AS IS" AND WITHOUT INCLUDING NEGLIGENCE, SHALL CMU BE LIABLE UNDER ANY THEORY OR FOR ANY DAMAGES INCLUDING WITHOUT LIMITATION, DIRECT, INDIRECT, GENERAL, SPECIAL, CONSEQUENTIAL, INCIDENTAL, EXEMPLARY OR OTHER DAMAGES 

     

     

     

     



    [1] We define an Agent as an autonomous, (preferably) intelligent, communicative, collaborative, adaptive computational entity. Here, intelligence is the ability to infer and execute needed actions, and seek and incorporate relevant information, given certain goals.

    [4] For a description of the RETSINA Multi-Agent Infrastructure and its implications for nts.ri.cmu.edu/ans/ANSv2.9.PDF">http://www.cs.cmu.edu/~softagents/ans/ANSv2.9.PDF.

     

    [6] We consider the ANS server and ANS client as part of an Agent Name Service (ANS) package.