Practical 2 JACK Beliefset Relations

Introductory notes

JACK beliefs

JACK beliefset relations are intended to be used for the maintenance of agent beliefs, which are central to the notion of BDI. Normal Java data structures can also be used for this purpose, but JACK beliefset relations have been designed specifically to work within the agent-oriented paradigm, and provide facilities not available with other data storage techniques. These additional facilities include:

Note that JACK only provides the infrastructure for adding, removing and retrieving beliefs. If other capabilities are required (such as belief propagation), then they must be implemented by the user. Also note that beliefs are associated with one agent only – the notion of a collective belief structure which is maintained by more than one agent is not supported. JACK allows an agent's beliefs to be made available to other agents but with major restrictions regarding population and access.

In JACK, beliefs are modelled as tuples. Every tuple must have a unique key which is composed of zero or more fields. In addition to the key, the tuple contains zero or more data fields. If the key contains zero fields, only one tuple is allowed in the beliefset. It is permissible for the key to be composed of all fields in the tuple.

A JACK beliefset is populated using the BeliefSet add() method. Retraction and retrieval of beliefs also follow the relational model in that both functions support the notion of tuple specification. A tuple specification consists of components for each tuple field where each component can either be a value or a wildcard symbol (e.g. *) which indicates that any value for that field is acceptable.

In JACK, tuple specification is implemented as an argument list consisting of JACK expressions (corresponding to values) and unbound logical members (corresponding to wildcards). Logical members follow the semantic behaviour of variables from logic programming languages such as Prolog, where variable binding occurs through the process of unification. Unification can potentially provide multiple bindings for the unbound logical members – JACK provides access to these bindings (if required) through a beliefset cursor. This access can be transparent to the user (e.g. when a beliefset query appears in a composite logical expression) or under user control. In the latter case, the cursor method next() is available to provide access to the next binding.

Once a logical member has been bound, its value cannot be changed and an accessor function (e.g. getValue()) is required to access the value of the bound logical member.

Belief retraction is achieved in JACK through the remove() and removeAll() methods. Note that remove() is a BeliefSet method (and takes a tuple specification as its argument list) whereas removeAll() is a BeliefSetCursor method and takes no arguments.

Belief retrieval is achieved in JACK through user defined query methods which take a tuple specification as their argument list. Overloading of query methods is permitted. A query method with an argument list consisting solely of (non-logical) JACK expressions is valid – it is used to determine whether or not a particular belief is held. JACK also supports (through the view construct) the retrieval of beliefs which are constructed from multiple beliefsets.

Once asserted, a JACK belief cannot be modified directly. The only way to change a belief is to:

JACK supports the posting of events when an agent's beliefs change (e.g. through an add() or remove()). The following beliefset callbacks are available for this purpose – addfact(), newfact(), delfact(), endfact() and modfact(). If this capability is required in a particular application, the developer must provide implementations for the callbacks that are actually used.

If you wish to populate a beliefset from a data file, then you can read records from a data file and explicitly add the records to the beliefset using add(). Typically, you would perform this activity from within the beliefset constructor, with the filename passed as an argument.

Alternatively, you can initialise tuple objects directly using the JACOB Object Modelling Language, which is described in the JACK™ Intelligent Agents JACOB Manual. Beliefsets are provided with a read() method which takes a JACOB file as its argument and populates the beliefset according to the contents of the file. JACOB can be used for initialising any JACK objects, not just beliefsets and is also used to exchange objects between processes. In this latter capacity, it provides a lightweight alternative to Java serialization and CORBA.

JACK beliefset definition

The general form for a Closed World JACK beliefset definition is:

  beliefset RelationName extends ClosedWorld
    // JAL declarations.
    // The following declarations can be used in a
    // JAL beliefset relation:
    // Zero or more key field declarations.
    #key field FieldType FieldName;
    // Zero or more value field declarations.
    #value field FieldType FieldName;

    // Declaration of any events that are posted when changes are
    // made to the tuples. This only happens when a beliefset callback
    // is defined.
    #posts event EventType ref;

    // Declaration of queries that the agent can perform on a relation.
    // They can include:
    #indexed query queryName( parameter list);
    #linear query queryName(parameter list);
    #complex query queryName(parameter list) {method body}
    #function query ReturnType queryName(parameter list) { method body}
    // Note that only the prototype is declared for indexed and linear
    // queries - the actual query class (which will be a beliefset
    // cursor) is generated automatically by the JACK compiler.

    // Beliefset callbacks (if required). The prototypes are:
    public void addfact(Tuple t,BeliefState is)
    // Called when a belief is added.
    public void newfact(Tuple t,BeliefState is,BeliefState was)
    // Called when a new belief is added.
    public void delfact(Tuple t,BeliefState was)
    // Called when a belief is removed.
    public void endfact(Tuple t,BeliefState was,BeliefState is)
    // Called when a belief is removed.
    public void modfact(Tuple t,BeliefState is,
                        Tuple knocked,Tuple negated)
    // Called when a belief is modified.
    public void moddb()
    // Catch-all.

The open world's beliefset relation definition takes the same form as that shown for the closed world except that in the first line ClosedWorld is replaced with OpenWorld.

An example

The following JACK beliefset could be used to maintain beliefs regarding politicians.

  beliefset Politician extends OpenWorld
    #key field String name;
    #value field String party;
    #value field String portfolio;

    #indexed query get(
          String name,String party,logical String portfolio);
    #indexed query get(
          logical String name,String party,String portfolio);
    #indexed query get(
          String name,logical String party,logical String portfolio);
    #indexed query get(
          logical String name,String party,logical String portfolio);

    #posts event Elected e;

    // Callback is invoked when a new belief is added.
    public void newfact(Tuple t, BeliefState is, BeliefState was)
       // The Politician__Tuple class is generated by JACK.
       // NB: Politician__Tuple has two underscores.
       Politician__Tuple pt = (Politician__Tuple) t;

       // Only post an event if the politician added is the prime
       // minister.
       if(pt.portfolio.equals("Prime Minister"))

An agent can then have read/write access to a beliefset of this type by including a declaration like the following in the agent definition:

   #private data Politician mps();

The agent might populate the beliefset through a plan called NewMember:

  plan NewMember extends Plan
    #handles event elected e;
    #modifies data Politician mps;


Other plans might then access the beliefset in various ways.

  // Example 1.
  // Is Humphrey Bear, a member of the Honey Party, Prime Minister?
  // Note: the following code will only work as intended if there can
  // only be one Prime Minister. Why?
      logical String name;
      mps.get(name,"Honey Party","Prime Minister");
      if (name.getValue().equals("Humphrey Bear")
        // Do something

  // Example 2.
  // Are there any elected members of the Honey Party whose first
  // name is Rupert?
  // What happens in this example if the first match found is not
  // Rupert?
  // Refer to the section on Composite Logical Expressions in the
  // Plans chapter in Agent Manual for a more detailed discussion.
      logical String name;
      logical String portfolio;
      if (mps.get(name,"Honey Party",portfolio) &&
               (name.getValue().indexOf("Rupert") == 0))
        // Do something.

  // Example 3.
  // Have a picnic for all elected members of the Honey Party when
  // Paddington Bear gets elected.

    // Wait until Paddington Bear is elected.
    logical String portfolio;
    #posts event SendInvitations sie;
    @waitFor(mps.get("Paddington Bear","Honey Party",portfolio));
    @post(sie.invite("Honey Party"));

  //The sending of invitations is achieved with the following plan
  //and event.
  plan PrintInvitations extends Plan
    #handles event SendInvitations si;
    #uses data Politician mps;
    logical String name;
    logical String portfolio;

       mps.get(name, si.politicalParty, portfolio);

      System.out.println("send invitation to "+name.getValue());

  event SendInvitations extends InferenceGoalEvent
    String politicalParty;

    #posted as invite(String p)
      politicalParty = p;

  // Example 4.
  // Sack Little Bear.
    mps.remove("Little Bear", "Honey Party", "Treasurer");

  // Example 5.
  // Sack the entire Honey Party.
    logical String name;
    logical String portfolio;
    mps.get(name,"Honey Party",portfolio).removeAll();

Note: You may now complete Practical 2 exercises 1 to 5.

Exercise 1