JAF HowTo

JAF, the Java Agent Framework, is a component-based agent architecture which allows the designer to use and build software agents. It consists of a design methodology and a set of reusable components, which together allow you to design, extend and integrate components into a cohesive whole. This document will help you obtain and understand JAF, and provide directions on how you can use it to make autonomous agents of your own.

This document refers to materials contained in the JAF distribution. Directions for obtaining the distribution are provided in the next section.

The JAF Distribution

Download the JAF distribution and look through the README and CONTENTS files. Keep it close by as a reference as you read this document.

The JAF distribution can be downloaded here. You will need to uncompress it, and also have a Java runtime or software development toolkit available to use it.

The distribution consists of the JAF libraries themselves, a set of utility scripts that simplify agent construction and execution, and some example code and configuration files. The architecture will work on any platform that supports Java, with two exceptions:

  • The agent.mass.Scheduler component only functions on platforms which are supported by the DTC scheduler.
  • The provided Makefile and supporting scripts will only work in a Unix-like environment. These are for convenience purposes only, however, and it is quite possible to run without them. Details on running outside of Unix, or more generally in an environment without make or perl, are in the README file.

More details on the individual files and their purposes can be found in the included CONTENTS file.

The example agent provided in the distribution is a functional program designed to work with the MASS simulation environment. The MASS simulation environment, which can be obtained here, provides the agent with communication, execution and resource modeling services, and thus allows more rapid development of agents. Agents designed to work with MASS can be re-targeted to work independently as well.

Instructions for running the example agent can be found in the README file in the JAF distribution. When running, the example agent will connect to the MASS simulator, generate a plan and schedule from a Taems task structure, and perform the actions specified in the schedule. To monitor the progress of the agent, you can either watch the logging statements or look at the conditioned taems task structure as it is performed. To do the latter, select "Conditioned Taems Views" from the agent.simplest.WindowManager menu.

Architecture

The components inside a JAF agent interact almost like a miniature multi-agent system of their own.

Agents constructed in JAF are built using a component-based approach, similar to Sun's Java Beans. A typical agent will be composed of a set of components, each of which is a relatively autonomous entity within the agent. Component designs can range from a simple utility class, to one which acts by reacting to the behaviors of other components, to a proactive, independent object possessing its own beliefs and goals. These components then work in concert at runtime, hopefully achieving the agent-level goal(s) the designer intended.

Generally speaking, a component is an encapsulated object that addresses a particular need. As with any object-oriented system, the component should be designed in such a way that it is as generic as possible, so it may be easily reused and recombined with other components to address new goals. In a more specific sense, however, a JAF component is any Java Object which directly or indirectly extends agent.base.AgentComponent. This class implements the lowest level functionality needed for an Object to act as a component, including the methods and interfaces needed for it to be recognized and controlled as a JAF component. The designer can then build upon this base to produce a productive and compliant component that addresses their needs.

JAF components are designed under a common set of conventions, which allows them to more easily both operate independently and interact with one another. These conventions are covered below.

Control Flow

A phased control sequence provides structure to components' setup and execution.

The Control component is primarily responsible for performing the flow of control in the agent. The control flow is a set of phases which each component is put through, allowing them time to do setup tasks, proactively perform activities, and satisfy termination requirements. From a component's perspective, each of the phases corresponds to a particular function which will be called, along with a set of conventions and suggestions for what actions can be done in that function. The phases are described below:

  1. Construction - The component constructors are called. Component dependencies and state properties should be declared here, in addition to any low level setup the component might need.
  2. Initialization - The component init() methods are called. Components typically obtain handles to other components they will need, although they should not be used in this phase (with a few exceptions). Any setup that can be performed independently of other components should be finished here. Upon completion of this phase, your component should be able to be used by other components in the agent.
  3. Begin - The component begin() methods are called. Other components may now be used. The component's setup should be finished.
  4. Pulse - The component's pulse() method is called. This function will usually be called repeatedly during the agent's lifetime. Along with event reactions, activities placed here should implement the bulk of the agent's runtime behavior.
  5. End - The component's end() method is called. This will be performed after the agent has decided it should complete its execution (for instance, Control.quit() is called). Any needed completion activities should be performed here (i.e. closing file handles or network connections, consolidating statistics, etc.).

As part of the Construction phase, Control also checks any dependencies which have been declared by each component. A dependency should be declared with the addDependency() function whenever a component must be available for the dependent component to function. For example, most components will declare a dependency for the Log component, because it is used extensively for logging purposes. See the constructor of ExampleProblemSolver for examples.

Note that a dependency does not need to be declared for every component being used. For instance, the Observe component (a statistics gathering component) is usually not essential for a particular component. However, if it were to be used without a dependency being declared, the component using it should gracefully handle instances where Observe is not available.

The source code for the ExampleProblemSolver has some brief examples of activity in the various phases. More details can be found in the API documentation for the AgentComponent class.

Events

Events allow components to monitor and react to the activities of other components in the agent.

As with any program, components may interact with one another in conventional ways, by using direct method invocation or indirectly using global data. They also make use of a more sophisticated and dynamic system using events. Events, which are fired by the component that produces them, can be monitored and reacted to by the components that listen to them.

As it runs, a particular component can produce and advertise an event stream. For instance, a communication component might produce a stream which tells when messages have been sent or received. Another component can implement the interface which allows it to listen to that event stream. The listening component may then dynamically register to the producer's stream at runtime. After it is registered, the event producer will include the new listener in its list of recipients for the stream, and subsequent events will be sent to that component when they are fired. The listener can then use these events as the basis for whatever reaction is necessary.

The ExampleProblemSolver in the JAF distribution listens to three different event streams, for state property changes, communication message events, and execution actions. The component's definition first implements the three different *EventListener interfaces. In the init() function the three calls can be seen registering the component as a listener to State, Communicate and Execute's event streams (look for calls to add*EventListener). Later on in the code are the several property*, message* and action* functions which implement their respective interfaces. These are the functions which are called when the various events are fired.

On the other side of the coin, a component may also be an event producer. In this case, the event listener interface, and the event class itself must be created (e.g. ExampleEventListener and ExampleEvent) and the event producing class must implement both the registration and unregistration procedures, as well as the method which actually fires the event. For instance, the ExampleProblemSolver produces an ExampleEvent stream. The registration procedures can be found as addExampleEventListener() and removeExampleEventListener(), which add and remove components from the event stream, respectively. It also implements fireEvent(), which fires an ExampleEvent to all registered listeners. An example showing how to fire an event is in the pulse() method, which will fire a dummy ExampleEvent on every pulse.

Note the naming conventions for the various classes and methods involved in the event delivery system. These should be followed when creating new event streams.

Properties and State

The State component provides an agent-global data repository, automatic access to external parameters, and means of finding other components in the agent.

Inside each JAF agent should be a State component (or some derivative), which is responsible for providing a data repository global to the agent. Data stored inside State are known as properties. Information which might be useful to other components can be published this way by the producer. For instance, a scheduling component could place its schedule in State, so that the execution component can find and make use of it. See the begin() function in ExampleProblemSolver to see how to get access to a property.

As alluded to in the previous section, state implements a property event stream, which can also be used to access properties and drive state based reactions. For instance, that same execute component might only decide to look for a new schedule when a propertyChanged event has taken place for the schedule property.

An important aspect of any JAF agent is the set of parameters supplied to it when it is executed. Individual components first declare each parameter's name in the construction phase, along with their type, a brief description, and an optional default value using State.addParameterInfo(). See the example agent's constructor for an example, where it defines a boolean property named PerformActions which controls its runtime behavior. State then uses these declarations to process the information supplied in the agent's configuration file (the .gnt file), or in environment variables. Once read in, a parameter is indistinguishable from a regular property, and can be accessed and reacted to in the same manner. The ExampleAgent's example.cfg file uses the WRITE command to generate the .gnt file containing its static parameters. The Makefile also uses the makeagentenv script to extract parameters from available environment variables. Select "Print all parameters" from the top of State's menu at runtime to print a list of all parameters along with their current values.

Information which is too long to fit inside a simple parameter declaration can be stored inside the external configuration file. This is a consolidated, compressed jar file which should contain all the agent's file-based configuration data. For example, the Taems structure used by the ExampleAgent is stored in this file. The JAR command at the end of its example.cfg in the distribution shows this being generated. See the example agent's begin() method and State for more information.

State provides one other important function by allowing components to find and obtain references to the other components resident in the agent. When components are instantiated by Control, they automatically register themselves with State, using their class name (although this can be overridden with the setDescriptor function). A component can then use State's findComponent call to obtain a reference to any other that is available. Although this function can be called at any time, for efficiency purposes it usually makes sense to obtain and store a reference to other components during the initialization phase. See ExampleProblemSolver's init() function for examples.

Required Components

A few components, or some derivative of them, will need to appear in any JAF agent that is created. State and Control are needed for the functionality that is described above. In addition, almost every component makes use of Log for debugging and informational purposes.

Making an Agent

JAF is primarily a development environment. To best exploit its strengths you should design and implement new components to address your specific needs.

A JAF agent is composed of two basic types of data: the components that compose the agent itself, and the configuration data that is provided to it at runtime.

Constructing a new agent component, or extending an existing one, is largely a matter of first determining what capabilities the component should have, and then determining how best to design them within the framework outlined in the Architecture section above. The designer will need to determine what other components can be utilized, and how their existing capabilities can best be exploited. How should this new component be organized to maximize its usefulness to other components? Should it produce an event stream detailing its actions? What parameters should it accept to control its behavior? Like any development effort, there is no one best way to create a component, and the design will be dictated by the specific needs that will be placed on it.

The .cfg file is used to generate the .gnt and .jar files used by your agent.

Once the components have been created, and other components required by the agent identified, the agent's configuration must be generated. The end result of this will be a .gnt file, which is used by the Control component to determine the component and parameter lists. While this file is human readable and editable, it has been found to be useful to generate this from a master configuration file, the .cfg file. This separate configuration file can be used to include data from different source files and generate multiple .gnt and jar files in one step, simplifying the configuration process for multiple-agent systems.

Of particular importance in the .cfg file is the Components list, which identifies the components to be included in the agent. All required components should be listed here with their full classname (e.g. agent.example.ExampleProblemSolver). Following this will usually be a set of agent parameters, after which a WRITE command will appear, which will produce the .gnt file. As mentioned earlier, the JAR command is also used in the configuration file to produce the external configuration data file.

The .gnt file is used by Control to instantiate and execute the agent.

The README file in the distribution covers how agents are executed. This essentially entails running a Control object and passing in the location of a .gnt file on the command line (or equivalent). Ignoring the Java classpath and environment variables, the included example agent could be executed with a command that looks like 'java agent.mass.Control --config config/example/example.gnt'. The included Makefile will do this for you automatically if you run 'make config/example/example.gnt'. Note that other Control objects (such as agent.simplest.Control, or a new one derived for a specific environment) might also be used in other situations.

Components

It is useful to know about the JAF components in the same way it is useful to know about the various Objects provided with the Java language.

This section will briefly describe some of the base components available in JAF. This should give a rough idea what capabilities are built in, and what can built upon to generate an agent. This is not an exhaustive list, however; for more details on these and other components, see the Documentation section at the end.

State

Most of State's important functionality was covered in the Architecture section. The API documentation for agent.simplest.State provides much more detail.

Execute

The Execute component is responsible for managing the "actions" of the agent. In an agent using Taems structures to model its behavior, actions correspond to the methods in the structure which are performed. Outside of a Taems structure, actions can represent arbitrary behaviors. Typically, the Execute component will use the Schedule property in the agent to determine what actions need to take place, although actions may be performed directly with the executeAction method. If a simulator is being used (such as MASS), Execute would be responsible for notifying the engine that an action is being performed, and obtaining the results when it has completed. Outside of simulation, components will usually perform specific activities in response to an actionStarted event. Results are dispersed to other components with the actionCompleted event. agent.mass.Execute also offers an AutoExecute parameter which will cause it to automatically execute Taems schedules if they are found.

Log

The Log component provides a textual, level-based logging service to the components. Several different logging levels are provided, where a given log line will print only if it's log level is less than or equal to the current specified log level. Logged strings are also flagged with the caller's "facility code", which is essentially an id number that allows Log to recognize the originator of the string. These codes are also used to allow different per-component log levels, which allows you to create a configuration where only the details most important to you are being actively logged.

WindowManager

This is a lightweight component who's sole function is to manage the various windows that the agent might create as part of its execution. The createWindow function can be used in place of the normal JFrame constructor. When this is done, the window which is created is automatically added to the WindowManager's menu and it's visibility flag set according to the WinVis parameter.

LogViewer

The LogViewer is a utility component which allows you to graphically interpret logfiles from multiple sources along a timeline. Various filters are available, and new ones can be added, which parse the logfiles looking for particular log constructs. The filters then extract the relevant information, which is used to draw tokens along the timeline representing the filtered activity. The LogViewer can be used as a component to monitor the logs in real time, or run offline on the same files as part of a later analysis. More information on the LogViewer and how to create new filters for it can be found it its API documentation.

SimpleTaemsReader

Particularly useful when working with Taems structures, this component provides ways to read in and store the task structures. The readTTaems methods can be used to directly read from a source, or the ObjTaems, SubTaems and ConTaems parameters can be used to automatically read from particular sources. The storeTaems function is useful when storing Taems objects locally, and handleNewObjectiveStructure can be used to send a structure to a MASS simulator. See its API documentation for more details.

Scheduler

The Scheduler component is used to access the DTC scheduling program, if it is available. The scheduleTaems function uses DTC to plan and schedule a Taems task structure, and read in the resulting schedules. Some newer Taems syntax is not supported by DTC, so Scheduler also provides evaluateTaems, which will compensate for this by modifying a copy of the structure to be sent to DTC (note you may lose some of the semantics of your structure in this process). The Scheduler also has an AutoSchedule parameter, which when set will cause it to automatically produce a schedule when new task structures are observed.

DirectoryService

This is a generic directory service component, in which you can store arbitrary multi-field textual data. Queries can be made locally or remotely, using a boolean expression language. This component may serve as the basis for a variety of information support contexts, such as brokers, yellow pages or bulletin boards. See the DirectoryService API for more information.

PartialOrderScheduler

The partial order scheduler extends the agent.mass.Scheduler component by taking a loose, parallel approach towards execution. In this context, DTC is used only as a planner, to determine the most appropriate set of methods in a particular Task structure. The PartialOrderScheduler then uses this plan, along with constraints in the task structure and in existing schedules and commitments to deduce a precedence ordering. This ordering, which implicitly allows parallelism where no precedence constraints are found, is then used during execution to determine what can be performed when. It is also used during rescheduling events to quickly shift activities when possible.

ResourceModeler

The ResourceModeler component maintains probabilistic usage expectations for the resources known to the agent. This can be used to support scheduling and execution decisions when there are consequences to under- or overloading resource bounds, or to keep track of remote resource usage as part of a belief structure. If the AutoModel parameter is true, ResourceModeler will attempt to track the resources itself. If this is insufficient, you can maintain them directly using the addUsage function. The models can be searched for available times using the findAvailibility function. See the API for more information.

SRTA

The Soft Real Time Architecture (SRTA) is not a single component in JAF. It is the combination of the PartialOrderScheduler (with DTC), ResourceModeler, Execute and the optional ConflictResolution component. As goals are recognized and formulated into Taems task structures, the PartialOrderScheduler uses DTC to generate an appropriate plan. It then uses the ResourceModeler, along with known constraints and preconditions to merge the plan into the existing schedule of activity.

This combined schedule is used by the Execute component to perform the appropriate actions. At any given time, Execute first determines the executability of a particular scheduled method by checking things like its preconditions and resource requirements, and performs it if appropriate. If minor changes are needed to the schedule, such as a method needing to be shifted, the PartialOrderScheduler is used to do so. Later, if a failure is detected and the ConflictResolution component is available, an attempt will be made to automatically resolve the failure.

In this way, high level goals may be formulated by a problem solving or coordination component, and the low level details of scheduling and execution in a limited resource, unpredictable environment may be handled independently by the SRTA architecture.

More Documentation

See the doc directory in the distribution for information on the included example source files.

The API documentation for the various component and classes is the best place to find detailed functionality information.

More high level information can be found in the web pages, and references listed there, of JAF, Taems, DTC, and MASS.