Agents communicate with each other using a logic-based language called the Interagent Communication Language (ICL). Typically, agents use the ICL to register their capabilities with a Facilitator, and then to make requests of the agent community to accomplish tasks, read or write data, and install distributed triggers or monitors.
Creating an OAA agent is very simple. Every agent must:
During the callback functions, an agent may need to make requests of the community to perform tasks, read or write data, or install distributed triggers, and the agent library provides primitives for each of these functions.
In OAA, the main component of an agent capability definition is a Prolog-style declaration such as send(email, Person, Msg, AdditionalParams). Incoming requests will be unified with the declaration, and if unification succeeds, the request will be forwarded to the agent who published the capability.
Agents generally create a list of all their capabilities declarations (also called "Solvables") and pass them to the oaa_Register() function during the initialization of their agent:
// Java: Register an agent with a Facilitator agent. // oaa_Register(ConnectionId, AgentName, Solvables, Params) // name = 'email', 2 capabilities (forward & send), empty parameter list oaa_Register("parent", "email", IclUtils.icl("[send(email,Person,Msg),forward(email,Person,Msg)]"), new IclList());// Prolog: Register an agent with a Facilitator agent. oaa_Register(parent, email, [send(email,Person,Msg),forward(email,Person,Msg)], [])
Solvable declarations can be dynamically added, changed, or removed during the course of the agent's lifetime using the functions oaa_Declare(Solvable, Params), oaa_Redeclare(Solvable, Params), and oaa_Undeclare(Solvable, Params).
In addition to "simple" solvable declarations as expressed above, programmers may also include special attributes and permissions associated with a solvable. For instance, a programmer might specify certain preconditions which must be true before it will be able to handle the result. Or perhaps a programmer might choose to give an indication of how good the agent is expected to be at solving the task associated with a solvable. For any of these needs, the solvable specification can be written in a more longhand form solvable(SimpleSpec, ParamList,PermisionList). For instance, here is a solvable which says that the agent can return the location of person on a given day and time only if the day is tuesday. However, if it returns a solution, it is expected that the solution will be better than average (a score of 7 out of 10).
solvable(where(Person,Day,Time, Place), [utility(7),test(Day = tuesday)],[])
The complete list of attribute parameters and permissions that may be associated with a solvable declaration can be found in the OAA reference manual or developer's guide, but here are a few of them:
As indicated by the type() parameter, solvables may be of type "procedure", for which an agent programmer will define a callback event to handle the request, or of type "data", for which the agent library will automatically manage a data store corresponding to the declaration. Data solvables are registered with a Facilitator agent in the same way as procedure solvables, and and will be used during automated delegation of data among agents. For data solvables, here are some of the relevant parameters::
// Good: given a Child, return Parent as answer in a separate arg parent(Child, Parent) // NOT Parent = parent(Child)
// In Java, register a callback corresponding the default request handler // oaa_AppDoEvent() that calls my function myOAADoEvent to process the request. oaa.oaa_RegisterCallback("oaa_AppDoEvent", new OAAEventListener() { public boolean doOAAEvent(IclTerm goal, IclList params, IclList answers) { return myOAADoEvent(goal, params, answers); } });// In Prolog, register the function myAppDoEvent to define the default request // handler oaa_AppDoEvent. oaa_RegisterCallback(oaa_AppDoEvent, myAppDoEvent).
Once the callbacks are registered, you should actually write the code to handle the events. The handler you've defined will be called with the incoming request and parameters associated with the request. Typically, you will pull out the arguments from the request, call an API to process the request, and then return a list of "solutions" to the request indicating success, failure, or multiple answers that are solutions to the request.
Example: An agent has published the capability declaration: send(email, Person, Msg, Params) and here we define how matching requests should be handled.
Prolog:
// send a message to a person by asking the agent community if anyone // knows the email address for person, and if so, uses the UNIX mail // command to send the message. MailParams can contain a subject line // for the message. myAppDoEvent(send(email, Person, Msg, MailParams), InParams) :- oaa_Solve(email(Person,EmailAddr), []), (memberchk(subject(Subj), MailParams) ; Subj = 'No Subject'), sprintf(Cmd, 'mail -s ~p ~p < ~p', [Subj, EmailAddr, Msg]), system(unix(Cmd)), !.
Java:
// Callback to handle my solvable declarations IclList myAppDoEvent(IclTerm request, IclList params) { // Empty solution list means request has FAILED IclList solutionList = new IclList(); // send(email, PersonName, Msg, MailParams) if (request.iclStr().equals("send") && (request.iclNumTerms() = 4)) { IclTerm personName = request.iclNthTerm(2); IclTerm msg = request.iclNthTerm(3); IclTerm mailParams = request.iclNthTerm(4); IclList ans = new IclList(); boolean ok = false;// Look up the email address for PersonName by asking the community if (oaa_Solve(new IclStruct("email", personName, new IclVar("Addr")), new IclList(), ans) { // ans will be in form [email(Person,EmailAddr)] so pull out EmailAddr IclTerm emailAddr = ans.iclNthTerm(1).iclNthTerm(2); IclTerm subject = icl_ParamValue("subject", null, mailParams); String subj = "No Subject"; if (subject != null) subj = subject.iclStr();// call a function to send the mail: // callMailAPI(String addr, String subject, String Msg) {...} ok = callMailAPI(emailAddr.iclStr(), subj, msg.iclStr()); }if (ok) solutionList.iclAddToList(request, true); } return solutionList; }
In non-Prolog
languages, oaa_AppDoEvent() callbacks must return a list of
solutions, where the empty list signifies failure and a list containing one
element matching the complete original request (with all variables filled in)
signifies success. Many Java or C programmers have the tendency to want to
return only the answer, not the full request.
Example: An agent wants to publish the min() function, returning the lesser of two values. This should be declared in a relational style as min(X, Y, Min), with Min returning the minimum. oaa_AppDoEvent() must return a solution list:
Request: min(7, 2, X)
WRONG: solutionList = 2
WRONG: solutionList = [2]
RIGHT!: solutionList = [min(7,2,2)]
If there are multiple solutions to a problem, they can be returned in the solution list, each as a different instance of the original request:
request: squareroot(4, X) solutionList = [squareroot(4, 2), squareroot(4, -2)]
Prolog: oaa_Solve(email('Adam Cheyer', X), [])Java: IclList params = new IclList(); // Empty parameter list IclList answers = new IclList(); // Answers will be added in this list if (oaa_Solve(IclUtils.icl("email('Adam Cheyer', X)"), params, answers) { // Here we have one or more solutions to the request, stored in answers }
The default behavior oaa_Solve() (parameter list is empty) is to send the request in parallel to all agents who have declared solvables that match the request, gather their answers, and return them all at once in the answers list. This behavior can be adjusted using combinations of parameters, which provide interaction control at either a high level ("This is a problem of type XXX") or at a low leverl ("Use these specific agents and routing pattern"). Again, please see the developer's guide or reference manual for a complete list of parameters, but here are a few :
New in OAA 2.0 is the possibility to have parameters return values from an oaa_Solve() request. Examples of these special parameters are:
Examples (expressed in Prolog, but only slight syntactic adjustment for other languages):
% ask agent community for the FaxNumber of Adam Cheyer, sending request to all
% relevant databases in parallel, filtering out duplicate answers, returning them
% in a list.
solve("fax_number('Adam Cheyer', FaxNumber)", "[strategy(query)]").
% send a fax to the Fax number: strategy=action so only one fax is sent even if
% many fax agents are connected.
solve(send(fax, FaxNumber, Document, [from('Bob Jones')]), [strategy(action)]).
% Broadcast to anyone that cares that the fax was sent. % No response required from community. solve(inform(fax, status(sent)), [strategy(inform)]).
The primary data manipulation functions are oaa_AddData(DataElement, Params), oaa_RemoveData(DataElement, Params), and oaa_ReplaceData(DataElement, Params).
Examples (expressed in Prolog, but only slight syntactic adjustment for other languages):
% Write status information on the Facilitator (parent), using it like a blackboard % Other agents can read it using oaa_Solve(status(phone, S), []), or set triggers % on it to be proactively informed when the status changes. oaa_AddData(status(phone, busy), [address(parent)])% A database agent declares that the word "boss" is a new noun meaning "manager". % This information will be routed to all natural language agents who will use % this information. oaa_AddData(noun(manager, boss, [language(english)]), [])
A trigger must have a type, a condition to be tested, and an action. Optional parameters for triggers include:
Many parameters from oaa_Solve() and oaa_AddData() can also be used for oaa_AddTrigger(), such as address(A), block(TrueOrFalse), reply(TrueOrNone), get_address(L), and so forth. Please see OAA documentation for the complete list of parameters that can be used for trigger commands.
Examples (in Java, but similar in other languages):
// DATA TRIGGER: spy on changes to Facilitator data
// Local agent receives update_agent_info() solvable whenever a new agent
// connects to Facilitator (because the agent library
// writes the agent_data() fact on connection/disconnection).
oaa.oaa_AddTrigger(new IclStr("data"), // Type = DATA trigger
IclUtils.icl("agent_data(Id,ready,Sv,Name)"), // Cond = agent_data()
// Action: send to ME a message containing info about new agent
IclUtils.icl("oaa_Solve(update_agent_info(add,[Id,ready,Sv,Name]),
[reply(none),address(" + me.toString() + ")])"),
// Params: do this forever (whenever), but only as new agents connect op(add)
new IclList(new IclStruct("recurrence", new IclStr("whenever")),
new IclStruct("on", new IclStr("add"))));
// COMM TRIGGER: look for low-level event arriving from Facilitator
// When an agent disconnects, the Facilitator broadcasts the event
// ev_agent_disconnected(Addr) to all agents. This trigger captures that
// event and call one of my oaa_AppDoEvent() functions using oaa_Interpret()
oaa.oaa_AddTrigger(new IclStr("comm"), // Type = COMM trigger
// Condition: Event we are looking for: ev_agent_disconnected
IclUtils.icl("event(_FromId,ev_agent_disconnected(AgtId),_P)"),
// Action: call myself with update_agent_status solvable
IclUtils.icl("oaa_Interpret(update_agent_status(removed,AgtId),[])"),
// Params: recurrence forever, trigger installed on myself
(IclList)IclUtils.icl("[recurrence(whenever), address(self)]"));
When publishing ICL capability declarations with a Facilitator agent, is there any standard convention to what the published declaration should be like? Agent programmers are, of course, allowed to register solvable declarations however they want, as long as they are legal ICL expressions. However, we can give you a few suggestions about how to have good "style" when publishing agent capabilities.
Tips for writing "good" solvable
declarations:
Agents are free to communicate as they
please, but there are several styles of communication that are useful in
different situations. Consider creating a phone agent who controls a phone
line and can determine whether it is busy or free. Agents in the
community will want to ask the phone agent about the status of the phone
line. Here are four ways that this information could be communicated among
agents.
This example should suggest some of the rich ways that OAA agents can use the services of the architecture to optimize the types of interactions they have with each other.