An adapter is the engine's interface to the outside world, which is anything outside the bounds of the engine. Adapters can represent an application program, a physical device or logical device. Typically, an adapter tries to represent one part of the outside world to the engine, which is referred to as the adapter's domain. Adapters tell the engine about changes in their domain, answer questions about their domain and perform actions in their domain at the engine's request.
To tell the engine about an event in the adapter's domain, the adapter notifies the engine, passing a trigger event object which describes the event. Adapters provide effectors and sensors. Engines call effectors to request that an action be performed in the adapter's domain. Engines call sensors to request more information about the adapter's domain. E-mail is a typical domain for an adapter. An e-mail adapter will do such things as:
This is done through building a trigger event object to represent the event and passing that trigger event object to the engine. This is often referred to as notifying the engine.
These are done through sensor calls.
These are done through effector calls.
An adapter does not have to perform all three functions. For example, you might have an adapter to represent a hard disk in a PC. The adapter might provide sensors to query the amount of free space and effectors to create new files, but the adapter does not create any trigger events.
Some adapters are useful to all applications, such as adapters that deal with scheduling events or with basic operating system functions, such as the file system. These types of adapters are called "core adapters". The time adapter is an example of a core adapter. The functions it provides, such as the ability to schedule alarms, are applicable to many applications.
The other category of adapters are "application adapters" Application adapters can represent anything that can be described in terms of its events, conditions and actions. Adapters can represent:
The NNTP adapter is an example of an application adapter. The function it provides, representing NNTP (network news transport protocol) to the engine, is only applicable to news related applications.
An adapter represents a domain to an agent. An agent is a particular configuration of engines and adapters. When designing your adapter, the natural tendency is to think about sensors and effectors within the context of the domain's events. However, do not assume that all sensors or effectors must be within the context of the adapter's own events. For example, an e-mail adapter may provide a reply effector, which requires a mail identifier in order to resolve the reply action. The e-mail adapter may also provide a sendmail effector, which does not require a mail identifier; any rule at any time can invoke SENDMAIL so long as it sends all the required arguments (addressee, body text, etc). In short, think about:
Within a domain, there is usually one critical event that most users will consider as the most significant domain event. It is crucial to identify that event, since it serves as a significant reference point for most of the adapter design, influencing the design of the sensors and effectors. For an e-mail adapter, arrival of mail is probably the most significant event. For the time adapter, the most significant event would be a timer occurring.
Once the critical event has been identified, these questions need to be answered:
There are two ways to provide information about the event. A fact set can be constructed and passed with the trigger event. The other way is to provide sensors that can be called to get the information. Deciding between the two approaches is currently a "black art". As our best attempt to shed some light into that black art, we offer the following recommendations:
Other events within the domain can also be presented. While these other events are not as significant as the main event, including them will enable a more powerful manipulation of the adapter's domain. However, there is a balance between covering every event and covering those events with some end-user relevance.
Sensors are queries directed to the adapter from the engine to determine the truth or falsehood of an atom in the antecedent of the rule and, sometimes, to return sets of values for unbound variables in the list of terms. Boolean sensors require a fully bound term list and determine the truth or falsehood of the atom. Non-boolean sensors have at least one free variable in the term list. Non-boolean sensors return sets of values for all the variables in the list of terms.
The key design point for sensors is to identify all the pertinent information that the adapter has in, or could extract from, its domain. An e-mail adapter has information about an incoming mail item, such as the sender of the mail and the time sent. Some of this information can be provided on the trigger event through the fact set. Sensors could be used to return additional information. Examples of e-mail sensors are:
A sensor may query other sources (its outside world) to obtain the information. A time adapter may have a sensor that would be a query with an unbound variable representing the current day of the week. The time adapter would then query some time facility to get the current day of the week and return it as a "binding" for that particular unbound variable.
A sensor is used by an engine to express a query to an adapter. Sensors are defined as a part of an adapter's interface and are established at agent start-up time when an adapter registers its sensors. A sensor's arguments have the form of an atom, which is like a fact. Instead of a predicate name, a sensor has a sensor name. Like atoms, sensors have terms and these terms have signatures. The signatures are also established at the time of sensor registration. Sensors can be thought of as providing a virtual pool of facts (through queries) to an inference engine.
Sensors are used by rules by mapping the predicate names of atoms to sensor names. In the metadata that is associated with a RuleSet, the Sensors section defines the mapping between predicate names and sensor names. This mapping occurs when the rule (in a RuleSet) is edited, establishing the intention of the rule author that a sensor be used to dynamically obtain more information at run time for rule inferencing.
Predicate signatures in the RuleSet metadata must agree with the signature of the associated sensor based on the adapter's registration of the sensor. Multiple predicates can share the same sensor as long as the predicates all have the same signature.
During rule evaluation, after matching rule atoms with facts for binding variables and establishing the truth of the atoms, as described above, atoms are checked to see if they employ sensors. This is done by comparing the predicate name of the atom with the predicate-to-sensor mapping names in the metadata of the RuleSet. Refer to "RuleSet Metadata Required for RuleSets" for details of this metadata. When a match occurs, the sensor is employed by the engine.
Now, more specifically, how can an engine use a sensor?
When a sensor's signature specifies that a particular term of the sensor is a variable, there are two possibilities:
The difference between 1 and 2 above is significant and is affected by the way the rule is written. In the first case, the adapter's capability for binding the variable is overridden. In the second case, the adapter is in control of the binding. In either case, multiple bindings can occur for the same variable. That is, multiple values can be substituted for the same variable name and the rule is evaluated for each such value substitution. When multiple variables are involved, there is an evaluation of the rule for each of the combinations of value substitutions.
An engine uses testCondition to invoke a boolean sensor (type 1 above), and answerQuery to invoke a non-boolean sensor (type 2 above). Refer to Chapter 3. "Adapter Reference Material" of the Components and Adapter Reference book for details of this interface. Refer to "Registering Sensors and Effectors" for details of adapter registration of sensors.
Examples of sensors include determining if a keyword exists in a mail item (Mail Adapter) and determining the current date and time (Time Adapter).
To handle actions that may result from an event, effectors must be provided to perform actions in the adapter's domain. Effectors provide a way for an engine to affect the adapter's domain. An effector action is caused by an effector atom in the consequent of a rule. Only bound terms are allowed in the term list for an effector and there is no return value.
Effectors should be provided to perform actions that may result from an event from the adapter's domain. For example, in response to receiving new mail, a user might want to reply to that mail. Therefore, one effector an e-mail adapter might want to provide is a reply effector.
However, the effector set should not be limited to only those actions that are associated with an event. The effectors should represent all the functions of the adapter's domain. Continuing with the e-mail example, the reply effector requires a mail identifier in order to resolve the reply action. However, a sendmail effector assumes no context; any rule at any time can invoke sendmail so long as it sends all the required arguments (addressee, body text, etc).
An effector can create new trigger events, which it can pass to the engine. For example, a checkNews effector may tell the news adapter to look for the arrival of new news items. If the news adapter finds new items, it will create trigger events and notify the engine. Those trigger events will be processed by the engine in subsequent inferencing episodes.
Because there are no return values on effectors, if an effector encounters an error, it must report this error to the engine by the notification process.
Effectors, like sensors, are defined as a part of an adapter's interface and are established at agent start-up time when an adapter registers its effectors. Also like sensors, effectors' arguments take the form of an atom, which is like a fact. Instead of a predicate name, an effector has an effector name. Like atoms, effectors have terms and these terms have signatures. The signatures are also established at the time of effector registration.
Although consequents of rules may have atoms that map to effectors that also have unbound terms, all such terms must be bound by the engine before the associated effector is invoked. This binding is accomplished just as for any non-effector consequent atom.
Effector signatures must agree with the signature of the associated predicate in the RuleSet metadata. Of course, many predicates can share the same effector as long as the predicates all have the same signature.
Effectors are used by rules by mapping the predicate names of consequent atoms to effector names. In the metadata that is associated with a RuleSet, the Effectors section defines the mapping between predicate names and effector names. This mapping occurs when the rule (in a RuleSet) is edited, establishing the intention of the rule author that a effector be used to cause an action when rule is fired successfully.
Predicate signatures in the RuleSet metadata must agree with the signature of the associated effector, based on the registration of the effector by the adapter. Multiple predicates can share the same effector as long as the predicates all have the same signature.
An effector is executed with a performAction invocation by an inference engine. Refer to Chapter 3. "Adapter Reference Material" of the Components and Adapter Reference book for details of this interface. Refer to "Registering Sensors and Effectors" for details of adapter registration of effectors.
Examples of effector actions include setting an alarm (Time Adapter) which causes a later trigger event, sending mail (Mail Adapter), sending a message to a pager (using a Pager Adapter), and writing to a file (using a File Adapter).
One of the design decisions that an adapter must make is how to exploit the selector information in the trigger event. The event header associated with a trigger event contains a piece of information called the selector. It is up to each engine and adapter to determine how it uses the selector. The selector can be used to "select" what rule set should be used for inferencing a particular trigger event. If the selector is null, that "selects" all rule sets should be used for inferencing. The RAISE inference engine uses the selector in this manner.
When an adapter is called to perform a sensor or effector call, it receives as input the event header associated with the trigger event that caused the inferencing episode to be started. If the adapter wants to exploit the selector information, it can get the information from the event header and use it when performing the function. For example, when an effector in the Time adapter sets an alarm, it associates the alarm with the selector.
When an adapter is creating a trigger event to pass to the engine on a notify call, it must decide what value to supply for the selector. If the adapter wants all rule sets to get the trigger event, it can leave the selector null. If the adapter wants a specific rule set to get the trigger event, it can supply the selector value.
A typical case of supplying a non-null selector value is when the trigger event is created as a result of an effector call and the selector in the event header on the effector call was non-null. To continue with the Time adapter example, when an alarm goes off, the Time adapter checks to see it a selector was provided on the effector call that set the alarm. If it was, then the Time adapter uses that same selector value on the trigger event that represents the alarm.
It may be helpful, as an adapter writer, to write some sample rules for the adapter and some scenarios with rules to verify the adapter interface. Also, it is helpful to look at other adapters' sensors, effectors, and actions that are related to the current adapter. Consider the scenarios for using the adapter alone or along with other adapters and then write some rules for those scenarios.
Rules are written to guide the engine in inferencing and to tell the engine how to relate to its environment. Since it is through the adapter that the engine relates to its environment, the power of the rules is determined by the power of the specific adapter interface.
Here is the flow of rules inferencing related to adapters:
Note that the fact set may be empty.
This notification starts the engine inferencing its rules. The fact set passed with the event is turned into short terms facts and are used in the inferencing.
The sensor and effector names do not directly appear in the rules. Sensor and effector names are mapped to predicate names in the conduct set. The predicate names appear in the rules. Facts passed with the trigger event are written to reflect the predicate as they appear in the rules.
This section describes the key implementation considerations for coding an adapter in C++. Those considerations are:
If you are using Microsoft Visual C++ Version 4.2, your adapter cannot make use of the Microsoft Foundation Classes (MFC).
This section describes the key implementation considerations for coding an adapter in Java. Those considerations are:
Adapters must be a subclass of the IAAdapter class. The C++ statement for including the IAAdapter header follows:
#include <iatk/adapter.h>For C++, this is the only IBM Agent Building Environment Developer's Toolkit header file that you must explicitly include in your code. The above header file takes care of including the header files for the other classes mentioned in this section.
The following member functions of IAAdapter are available for use by subclasses:
Note: Both the notify and registerProcedure member function will throw an object of type IAError as an exception when an error occurs. In this case, the errormsg member function can be used to obtain information about the cause of the failure.
The following member functions are virtual functions of IAAdapter and should be overridden in the subclass:
If the adapter does not have any non-boolean sensors, it does not have to provide an implementation of this member function.
If the adapter has no clean up to do for events, it does not have to provide an implementation of this member function.
Note: The identify member function is a pure virtual function, so the subclass must provide an implementation of this function.
If the adapter does not have any effectors, it does not have to provide an implementation of this member function.
If the adapter does not generate trigger events and it is always ready to perform sensor or effector calls, the adapter does not have to provide an implementation of this member function.
If the adapter does not generate trigger events and it is has no termination clean up to perform, the adapter does not have to provide an implementation of this member function.
If the adapter does not have any boolean sensors, it does not have to provide an implementation of this member function.
Once your adapter is coded, the executable form of your adapter should be packaged as a DLL. As part of the DLL, you must include an implementation of the newAdapter function, which has the following signature:
extern "C" IAEXPORT1 IAAdapter * IAEXPORT2 IACLINK newAdapter(const char *domain, void *parm);
where domain is the domain of your adapter and parm are any start-up parameters your adapters requires. The newAdapter function is responsible for creating your adapter object.
Your adapter must override the newAdapter member function of the IAAdapter class. The signature of the newAdapter function is:
public static IAAdapter newAdapter(String domain, String parm) throws IAAdapterException
where domain is the domain of your adapter and parm are any start-up parameters your adapters requires. The newAdapter function is responsible for creating your adapter object.
This section contains steps in the life span of an adapter, starting with the loading of the adapter's DLL and continuing through inferencing on loaded conduct sets. The execution steps are as follows:
For adapters written in C++, the following information is in the configuration file:
For adapters written in Java, the following information is in the configuration file:
Once the agent has that information, it does the following for each adapter:
For adapters written in Java, the agent loads the adapter's class.
After the identify member function has completed, your adapter must be able to respond to the subset of sensor and effector calls that are used when configuring the adapter.
During the inferencing episode for the "AGENT:CONFIG" trigger event, sensors and effectors will be called as necessary. All adapters will receive event complete (by invoking the eventComplete()) method of IAAdapter) for this event.
During the inferencing episode for the "AGENT:STARTING" trigger event, sensors and effectors will be called as necessary. Only adapters that have sensors effectors in rules/conduct sets conditioned by this event will receive event complete (by invoking the eventComplete()) method of IAAdapter) for this event.
Your adapter should now be set up to handle answerQuery, performAction and testCondition calls from the agent.
The following discusses the steps that the agent goes through as part of loading or unloading a conduct set after the agent start up procedure has been completed. Those steps are:
The agent will then complete processing of all trigger events that it has on its internal queues.
The following discusses the steps that the agent goes through as part of doing a reset of a selector after the agent start up procedure has been completed. Those steps are:
This section contains steps that the agent goes through as part of shutdown processing. .When the agent is ready to stop, it performs a two-step operation for each adapter:
All sensors and adapters must be registered when the adapter's identify is called. To register a procedure (which can be either a sensor or effector) the adapter invokes the IAAdapter::registerProcedure member function, which has the following C++ signature:
IAAdapter::registerProcedure(const char *procedure_ name, IAProcType procedure_type , const char *signature_list, IAProcToken procedure_token)
and the following Java signature:
public boolean registerProcedure(String procName, int ProcType, String signature, IAProcToken token)
The input parameters are:
For C++, The IAProcType enumeration is defined in iatk/adapter.h.
Integer - bound integer term
IntegerLVar - free integer term
String - bound string term
StringLVar - free string term
Symbol - bound symbol term
SymbolLVar - free symbol term
The following is an example of registering an effector with two terms, a bound integer and a bound string.
registerProcedure( "setIntervalAlarm", IAEffectorProc, "Integer String", IA_SETINTERVALALARM);
The following is an example of registering a sensor with a single term, and a bound string.
registerProcedure( "isWeekend", IASensorProc, "String", IA_ISWEEKEND)
The following is an example of registering a sensor with a single term, and a free string.
registerProcedure( "currentTime", IASensorProc, "StringLVar", IA_TIME)
While an adapter is free to manage its own event scheduling, all adapters should rely on the time adapter to schedule alarms. Rather than each adapter defining its own scheduling system and accompanying rule editing and schedule administration graphical interface, the time adapter should handle this specialty for all adapters.
Future releases of the IBM Agent Building Environment Developer's Toolkit will include view components, so there should be a single visual metaphor for dealing with time functions.
Before an adapter can notify the engine of an event, it must construct an event header object, a fact set object and a trigger event object. The event header contains information about the event such as the type of the event, the time that the event occurred and a unique identifier of the event. The fact set contains the adapter's facts about the event. The trigger event object is composed of an event header and a fact set.
The fact set is an object of type IAFactSet. Fact set objects are composed of objects of type IAAtom The steps for creating and manipulating the fact set object for the trigger event are:
The trigger event is an object of type IATriggerEvent. An event header object and a fact set must be supplied to the IATriggerEvent object before it is passed on the IAAdapter::notify() member function: After the trigger event object is passed to the engine, the engine will start an inferencing episode for the trigger event. Since the engine could be receiving trigger events from multiple adapters, there is no guarantee of when the inferencing episode will start.
When the trigger event object is created, it creates the event header, which is an object of type IAEventHeader. The following information is contained in the event header. This same information is converted to short term facts that become part of the fact pool that is used for inferencing about the event.
The EventDomain information is turned into a fact about the EventDomain predicate. The signature of the EventDomain predicate is (string).
The EventTime information is turned into a fact about the EventTime predicate. The signature of the EventTime predicate is (string,string), where:
This event ID is passed in on subsequent sensor calls related to this event and also with the eventComplete call.
Note: The event type is the only information that the adapter must explicitly supply for the event header creation.
An effector causes an invocation of the adapter's performAction method. The information needed for the action is passed as a binding string, which is a list of terms from the atom in the consequent of the rule representing the effector. The effector is asynchronous, in that, it returns a void and may not necessarily have completed the action at that time. The adapter writer must determine whether the needed actions can be performed quickly and therefore before returning on the effector call or whether the actions should be started on a separate thread and then returning from the effector call.
All effector calls are funneled to the adapter through the performAction member function. One of the parameters on the performAction call is the procedure_token, which is used by the adapter to determine which effector to execute.
The event header object which caused the start of the inferencing episode, is also passed as a parameter on every effector call.
A sensor is a query that is delivered from the engine interface in the form of a testCondition or a answerQuery member function call to the adapter. The adapter gathers the information by either inquiring of its outside world interface or from information it has stored from an event. The event header object which caused the start of the inferencing episode, is also passed as a parameter on every sensor call, so that the adapter knows what event the information it needs to return is in reference to.
Sensors are synchronous calls to the adapter. The sensor first gathers the information it needs and then returns to the engine. The adapter may "cache" information it has received either from events or sensor processing for use in future sensor calls.
In the case where sensors are used to determine the truth or falsehood of an atom in a rule, all the terms in the term list are bound terms. The sensor is invoked through the testCondition member function. If the sensor determines that the atom is true, it returns a "1". If the sensor determines that the atom is false, it returns a "0".
In the case where the term list contains unbound variables, the sensor is invoked through the answerQuery member function. The sensor returns all possible sets of correct answers for the unbound variables. Note that this set can be empty.
All sensor calls are funneled to the adapter through the testCondition and answerQuery member functions. One of the parameters on the performAction call is the procedure_token, which is used by the adapter to determine which sensor to execute.
The following section contains some sample rule sets, illustrating different aspects of how rules and adapters work together. Although the rules are presented individually, remember that the inferencing engine treats the rules as a set. Every rule in the set is evaluated during every inferencing episode. To keep the examples readable, rules that are false, their antecedent is evaluated to be false, during an inferencing episode are omitted.
Another simplification made in these rules is that the procedure name is the same as the predicate name. Remember that predicate names are mapped to effector and sensor names in the conduct set.
This example illustrates how rules can be used to configure an adapter. The news adapter has a CheckNews effector, that needs to be called to check for new news items. To place control to the frequency of checking under user control, it is implemented as rules. The following rules show how this can be accomplished:
(=>(EventName "AGENT:CONFIG")(setIntervalAlarm 30 "minutes"))
For the inferencing episode for the "AGENT:CONFIG" trigger event, the above antecedent is true. This results in a call to the setIntervalAlarm effector, which sets an alarm for every 30 minutes.
When the alarm goes off, the time adapter will create a "Time:IntervalAlarm" trigger event. It will also create a fact set for the event, which will include the fact:
(Interval 30 "minutes")
After the engine has been notified of this event, it will start an inferencing episode for the event. During this inferencing episode, the following rule will be found to be true:
(=>(AND(EventType "IntervalAlarm")(Interval 10 "minutes")) (CheckNews "token"))
Invocation of the CheckNews effector will cause the adapter to look for new news items. If any are found, the notification mechanism is used to inform the engine of their existence. This will result in future inferencing episodes by the engine.