CASA How To


Run an agent

Agents are usually started from the command line using the main() method of the CASACommandLine class. The syntax is explained in the CASACommandLine class documentaiton or if you run casa (through CASACommandLine.main()) without any arguments. Note that a LAC agent should normally be running before you run any other CASA agent. The -f (take commands from a file) is useful for running collections of agents, and the CASA distribution has several example script files (.txt files).

If you're using Windows, the CASA distribution provides a couple of .bat files to simplify the command line. casaRun.bat is used to run casa with only the code that compes packaged in the CASA jar file:

$ casaRun <param>...

When you've written your own agents and placed them in a jar, use casaUse.bat to run CASA, but with your agents available:

$ casaUse <jarfile> <param>...

 


Write a basic agent

All you need to do to implement an agent is write your class extending casa.Agent. Make sure you override Agent's construtor with the lines:
    public myAgent(Qualifiers quals) throws IPSocketException {
      super(quals); 
    }
You can then extend the agent as appropriate.

Use Eclipse Templates to make writing and extending agents easier

There are templates to help you code:

Installing the Templates

You will find a file called templates.xml in the root directory of your CASA download. You must load it into Eclipse to use the templates. To load it:

  1. In Eclipse, choose menu bar item Window|Prefereces....
  2. In the Preferences dialog box (left side list) chose Java|Editor|Templates
  3. On the right side, click the Import... button
  4. In the file selection dialog box, navigate to CASA's root directory and choose the file templates.xml.

Note. if you are updating the templates (importing them for the second time), you should remove all the casa templates bevore doing this proceedure to avoid a long list of duplicates. You can do this by:

  1. In Eclipse, choose menu bar item Window|Prefereces....
  2. In the Preferences dialog box (left side list) chose Java|Editor|Templates
  3. On the right side, choose all the casa templates.
  4. Click the Remove button.

Using the Templates

To actually use the templates when in the Java editor:

  1. type "casa" followed by <ctrl>-<space>
  2. A long list of menu items will appear. The CASA templates all appear as "casa - ..." in the last.
  3. Choose the one that you want.
  4. If the templates has variables, they will be highlighted.
  5. Fill in the variables with appropriate text, and they will be copied throughout the template.

Execute code once when an agent starts up/shuts down

There are methods that you can override during an agents start-up. They all have different uses, and you should be mindful of whether the method is executing in the agent's thread or the thread of the caller of the agent's constructor.
MethodThreadDescription
Constructorcaller/creatorBe sure the parent constructor first in the constructor. Since the agent isn't initialized at all here, there is not likely much you'd want to do in the contructor.
initializeRun(AbstractProcess.Qualifiers)agentcalled just before the message loop begins; the agent is not fully initialized at this point -- it's not yet registered with the LAC. You can override this method to deal with qualifiers specific to your agent. See {@link casa.AbstractProcess#initializeRun(Qualifiers)} and the top-level documentation of {@link casa.AbstractProcess} for more detail.
initRTCommandInterface()agentoverride this (but be sure to call the super method) to register additional run-time commands for your agent.
init()agentcalled just after the agent is registered with the LAC -- the agent is now fully initialized.
pendingFinishRun()agentcalled when an exit() has been called while the agent is still running. Called only once
finishRun()agentcalled after the message loop has exited (when the agent has just stopped running). Called only once

Execute code during idle time

If you need to execute code during the agent's idle time (the time when the agent isn't handling incomming messages etc), you need only override the agent's doIdle() method:

    @Override
    protected boolean doIdle () {
      boolean superRet = super.doIdle();
      boolean ret = code;
      return superRet || ret;
    }

Don't forget to call the super version of the method. You should return true iff either the super implementation returns true or your code does something. You should avoid doing anything too lengthy during the idle call, and return control to the outer processing loop witnin a seconf or two. You may need to divide up your work: do some of it now, return, and carry on next time the doIde() method is called.

Warning: doIdle() is called whenever an agent becomes idle (ie: many times), so any code here will be run many times. You have to be smart about having the code run selectively.


Defering execution of code until later

There are situations where you want to execute code later or the current threat is not the agent's "normal" thread (eg: the AWT thread) and the code must be executed in the agent's main thread. To do this, just wrap the code in a Runnable object, and call the method AbstractProcess.defer(Runnable) or AbstractProcess.defer(Runnable, long) where the first parameter is a Runable object (typically an anonymous class type) and the second parameter is represents the minium delay time in milliseconds. For example, the following code embeded in an agent, will print "Hello world" to standard out one second or more after it is executed in the thread of the agent:

  ...
  Runnable runnable = new Runnable(){public void run(){
    println("Hello world");
    }};
  defer(runnable, 1000);
  ...

Handle an incoming message

Most incomming messages are handled as part of agent conversations. Conversations are implemented by a series of "callback" methods (consistenly named) that are automatically called by the policies at each step in the conversation. The easiest way to implement these to use templates. For a description of the conversations, see conversations. All the "callback" methods can return a PerformDescriptor with a negative Status value to cause the system to generate a error reply (not-understood).

If you really need to handle a message directly, you can override TransientAgent.handleMessage (MLMessage), but this is not recommended.


Defering processing a "callback" from an incoming message

Assuming you're using "callbacks" (see the previous section) to handle messages, you will occasionally run into the situation where you have to wait for something else to happen (eg: a user making a decision in a dialog box; a conversation with another agent to terminate; etc.). The easiest way to handle this is to just let your callback be called, and in it's body, check on the event you are waiting on. If it's happend, proceed; but it it hasn't yet happened just return the value of DEFER_ACTION (a constrant defined in TransientAgent). Since all "callbacks" return PerformDescriptor, your return call will be:

new PerformDescriptor(new Status(DEFER_ACTION));

This return will cause the system to call your "callback" again at a later time. You can repeat returning DEFER_ACTION as many times as you want.


Ignore an incoming request (and not reply)

From a consider_*() method, you can just not reply by returning new PerformDescriptor(new Status(DROP_ACTION)). In the social commitment communication paradigm, this will discard the associated commitment to reply only if the originating request was a broadcasted message (MLMessage.isBroadcaste()).


Send a message to another agent

Call one of the following methods:

The second one calls the first, but constructions a message for you out of the parameters (the list is an array of key/value pairs (keys are even, values are odd). Key may not be null, but values may be.

These methods send the message and waits for a resonse message that matches any one of the messageDescriptors and returns that message. If none of the messageDescriptors are matched within timeout milliseconds of the current time, the method returns a negative status and a null in place of the MLMessage.


Making an agent and it's attributes persistent

An agent is persistent if it inherits from the Agent class and sets the attribute persistent to true (which can be done either in the agent code of via the command line). The agent stores the information in a file according the the LAC's setup. All you have to do to make at attribute persistent is to mark it's declaration with the @Persistent annotation. For example:
  /** a simple persistent boolean attribute that will be stored in the properties under "myFlag" */
  @Persistent
  boolean myFlag;
  
  /** 
   * a persistent object that will be stored in the properties under "options.x" 
   * and "options.y" because it has at least one @Persistent attribute itself.
   */  
  public class Options {
    @Persistent int x = 4;
    @Persistent double y = 7.5;
  }
  
  @Persistent
  Options options;

  /**
   * a persistent object that will be stored in the properties under "stuff" in 
   * the standard CASA serial format (because none of it's properties are marked
   * @Persistent).  Note that the class Stuff MUST have a toString()
   * method and a corresponding constructor that takes a single string.
   */ 
  public class Stuff {
    int x = 4;
    double y = 7.5;
    public Stuff() {...}
    public Stuff(String persistData) {...}
    @Override
    public toString() {...}
  }
  
  @Persistent
  Stuff stuff = new Stuff();

Send a request through a cooperation domain

This section gives and example of how to carry on a "request" conversation between two or more agents who are members of a cooperation domain using the CD as an intermediary for the conversation.

To send create and send the inital request message. This is done by constructing a typical request message and then using CooperationDomain.constructCDProxy() to stuff the request in into a proxy message before sending it. The form of CooperationDomain.constructCDProxy() shown here will construct a broadcaste message (signified by a "*" for the final receiver). You can exercise more control over the destination address by using the two additional polymorphic forms of CooperationDomain.constructCDProxy().

 MLMessage msg = MLMessage.getNewMLMessage().setParameters(new String[]{
ML.PERFORMATIVE, ML.REQUEST,
ML.ACT, "myAct",
ML.CONTENT, "myContent" ML.REPLY_WITH "A UNIQUE STRING" // <- this is important - the system won't //match the reply with the request without it
});
//Since we're going through a CD, we have 3 options for how we want the the server
//to reply: we can ask it to reply directly to us, reply indirectly through the
//CD, or indirectly through the CD but also let everyone else "hear". switch (replyMethod) {
case direct: // have the server reply directly to me, bypassing the CD
msg.setParameter(ML.REPLY_TO, getURL().toString()); //explicitly set the REPLY_TO to be direct
break;
case publicThruCD: //have the server reply through the CD in directed mode (everyone hears)
cd.setDataValue("directed", null); //This tells the CD to use directed mode
break;
case thruCD: //have the server reply through the CD (in whisper mode by default)
//this is the default behaviour, so no need to do anything special
break;
} MLMessage proxy = CooperationDomain.constructCDProxyMessage(msg, getURL(), cd);
sendMessage(proxy);

There's not a lot of difference between an ordinary consider() and one recieving a broadcaste, but the difference is important. This example is for a shortcutting server. Most of the code here is directly from the template for a shortcutting request server (see above), but also the code in red is used to ensure that the client is forced to reply back through through the CD. You need not have the client reply through the CD (and have the client reply directly back) if you want. You could also force the client to reply back in "directed mode" (everyone can "hear") by adding a line "sender.setDataValue("directed", null)" and adding the sender in ML.RECEIVER in the return PerformDescriptor (as we did in the previous section).

 public PerformDescriptor consider_myAct(MLMessage msg) {
   in("myServer.consider_myAct");
   URLDescriptor sender=null;
   try {
     sender = new URLDescriptor(msg.getParameter(ML.SENDER));
   } catch (URLDescriptorException e) {
     e.printStackTrace();
   }
   PerformDescriptor ret = new PerformDescriptor();

   ret.put(ML.PERFORMATIVE, ML.SUCCESS); //shortcutting
   ret.put(ML.ACT, msg.getAct().push(ML.DISCHARGE).toString());
   // if we received this msg through a CD, we want to force the client to reply to us through the CD too.
   if (msg.getParameter(ML.CD)!=null) {
     URLDescriptor replyto = getURL();
     replyto.pushViaAtEnd(sender); //can use sender or cd -- hopefully they're the same.
     ret.put(ML.REPLY_TO, replyto.toString());
   }

   //TODO compute the result and put it in the CONTENT of the return message
   ret.put(ML.CONTENT, "myContent");
   out("myServer.consider_MyAct");
   return ret;
   }

The client code for release_myAct() should similar code to the red code above in order to properly direct any replies. The same applies to any code that leads to the generation of messages.


Paying attention too (or ignoring) messages arriving from CDs not addressed to you

Messages sent through a CD in "directed mode" (so everyone in the CD can "hear"), will arrive in every member agent's message inbox even though they are not addressed to that agent. By default CASA will merely dropped these messages since they are not addressed to you. But you can change that be calling setObserveMessages(true), which will allow processing of the incoming messages and call handler methods (consider_*()-type methods) in your agent. The methods called are similar to the "normal" ones but with "_evesdrop" appended to them. Eg: instead of conclude_act(MLMessage), it's conclude_act_evesdrop(MLMessage). If the appropriate evesdrop method isn't found, TransientAgent.evesdrop(MLMessage) will be called (which does nothing be return DROP_ACTION); your agent can override this to exhibit whatever behaviour you'd like.


Add a runtime command to an agent

Override TransientAgent.initRTCommandInterface(), which is most easily done by using a Template. Otherwise, the declaration is

  @Override
  protected void initRTCommandInterface () {
super.initRTCommandInterface();
try {
// commandInterpreter.put() calls } catch (ParameterParserException ex) {
println ("error",
"Customer.initRTCommandInterface: Unexepected exception when executing commandInterpreter.put()'s", ex);
}
}

CommandInterpreter.put(String, casa.Command) has 2 parameters: a String specification describing the command syntax, and an object of some subtype of the abstract class casa.Command. Details of the command spec may be found in the casa.RTCommandInterpreter documentation. The 2nd parameter is typically an instance of an anonymous class which overrides Command's abstract method execute(String, Map, AgentUI) to provide the code to actually implement the command. For example:

  commandInterpreter.put (
"request quote | "
+ "merchant(type=int; required; default=7711; valuerequired; help=\"merchant port to request a quote from\") "
+ "item(type=string; required; default=\"p/n=3433, descr=box of pencils, quantity=1\"; valuerequired; help=\"item description\") "
+ "?(help=\"Request a quote from a Merchant.\"; "
+ "category=\"request|quote\")",
new Command () {
public Status execute (String line, Map params, AgentUI ui) {
String merchantString = (String) params.get ("merchant");
URLDescriptor merchant = new URLDescriptor(Integer.parseInt(merchantString));
String itemString = (String) params.get ("item");
ItemDescription item = new ItemDescription(itemString);
return doRequestQuote(merchant, item);
}
});

Add a tab pane to an agent's default graphical interface

Subclass TransientAgentInternalFrame (or AgentFrame), override addPanels(Container contentPane, int index) and include this code:

   contentPanel.add( "myTab", contentPanel.new MobileFactory() {
        public JComponent build( int i ) { return myPanel; }});

where myPanel is some JComponent (usually a JPanel) that serves as the panel in the tab. But to actually use an instance of your new class (sub classed from TransientAgentInternalFrame or AgentFrame) in the agents interface, you have to tell the about it by overiding TransientAgent.makeDefaultInternalFrame(TransientAgentInterface agent, String title, Container aFrame) and returning an instance of your class.

If you want to give your agent a completely new interface, see the next section.


Add a new interface to an agent

The type of the interface is dictated by whether or not a graphic environment is present. So there are two different methods you might override. Code in TransientAgent will decide which to call. Both return an instance of casa.ui.AgentUI or null (if no interface is desired). Note that these methods will only be called if a specific interface isn't specified on the command line.