Agent interpreter: intentions, plans and events
Nuin agents' high-level behaviour is specified by the plans that they can carry out. Detailed domain-specific behaviours can be added to the agent through the plug-in services mechanism, but the overall shape of an agent's responses to its environment is specified in the set of plans that an agent has available. In the present version, agent plans are pre-generated, and typically supplied to the agent as part of the configuration file. However, like other programming abstractions in Nuin, plans are simply Java objects conforming to a particular Java interface. There is thus no reason why plans have to be hand-generated before the agent runs. Plans could, in future be generated by an on-line planning system, or be created as the output from a learning system. However, in the current release of Nuin plans are typically generated by parsing pre-written Nuinscript files, and this is how the examples in this section are shown. It is important to remember, however, that these script-encoded plans are not a pre-requisite of the architecture.
Plans
Plan structure
A plan may have any of the following components. None of them are mandatory, though a plan with no internal structure at all is not very useful!
| plan component | summary |
|---|---|
| name | The name of the plan as a URI. Should be unique within the scope of a single agent. Unnamed (anonymous) plans are permitted, but cannot be invoked by name. Optionally, the plan can be a logical term, not just a name, in which case the term arguments become arguments passed into or out of the plan. |
| label | A user-friendly display name for the plan. |
| comment | A comment string describing the intended use or effect of the plan |
| weight | A numerical value that can be used to assist the scheduler in the interpreter when multiple different intentions could be executed. |
| trigger | An event expression that denotes conditions under which the plan can cause an intention to be added to the set of current intentions. |
| postcondition | A sentence which denotes the post-condition |
| action | An action expression denoting the set of things that the agent will do when executing this plan. |
| with intention | provides a mechanism for connecting plan stages that are separated in time, without losing state variables. For example when waiting for a message from another agent before continuing processing. |
These elements are explained in more detail in the following sections.
Plan names
As noted above, plans in Nuin do not need to be named. Naming a plan is only really useful if the agent's behaviour requires plans to be invoked by name (e.g. from other plans). However, plan names may be used in debug messages and exceptions, so it can be useful to name plans that do not strictly require them.
In Nuinscript, a plan is named by placing the plan name after the
initial plan keyword. As noted above, plan names can be
symbols or terms. A term plan name will be unified with an
argument term in the calling plan, thus allowing arguments to be
passed into and out of called plans, depending on the state of the
bound variables at the point of calling.
Example plan names:
plan <http://nuin.org/2004/01/example:plan1>
end.
plan example:plan1
end.
plan example:plan2( "arg", ?result )
end.
plan foo
// note: in default namespace
end.
Plan label and comment
These documentation components are not used in Nuin at present, but may be employed by future debuggers, configurers or other tools.
Plan weight
In normal operation, the agent interpreter takes care of choosing which should be the focus of the agent's attention during the next interpreter cycle. Different policies may be specified, e.g. to make the agent more responsive to the environment by responding to events before processing existing scheduled plans. There are times at which the default scheduling policies cannot be relied upon to select the appropriate behaviour. In these cases, the plan weight can be used as a hint to the scheduler: intentions for plans of a higher numerical weight are scheduled ahead of those with a lower weight. This should be used sparingly, however, as in appropriate choice of weights can shut-out lower priority but still important tasks altogether.
Example plan weight:
plan weight 5 end.
Plan triggers
A trigger defines a pattern over events that the agent may
perceive, and which then schedule an intention to perform the
triggered plan. A typical example of such an event is the arrival of
a message, but other event types are also possible. A class of
special events is the lifecycle events, which record changes
in the agent's state. A particularly useful life-cycle event is
event:startup.
Each cycle, the agent interpreter chooses either to process the next
step in one of the active intentions, or to process an event from the
event queue. In the latter case, the event is denoted the current
event. The agent may be configured to remember a fixed number of
previous events, with a default of 25. The predicate
on( ?e ) succeeds if ?e unifies with the
current event. The predicate after( ?e )
succeeds if ?e unifies with either the current event, or
one of the events in the event queue. The unification argument can be
a variable, a constant term, or a partially-ground term, allowing
quite precise triggering conditions to be specified. The argument,
denoting the type of the event, can be a symbol or a term, in which
case the arguments of the term are regarded as the arguments of the
event.
For example, the following expressions are all well formed event-trigger predicates:
on event( ?e )
on event( event:startup )
on event( example:sensorA( true, ?x )
after event( example:test() ) {name = "fu"}
Note that the last example, using after, is equivalent
to an on match using the same event expression, because
the event will trigger when the example:test event is first detected.
After tests are more useful when building Boolean
combinations of event trigger expressions, using the connectives
and and or. For example:
on event( proximityAlarm ) && after event( stairEdgeBeacon )
A message is regarded as a sub-type of event, and can be directly matched in a event trigger. Typically the components of a message are unified in the named rather than fixed (positional) parameters of the message term. For example:
on message() {acl:performative = acl:inform, acl:content = ?content}
Using variables in the trigger term, the various components of the message can be extracted. However, it is sometimes useful to be able to bind the whole message object (or, equivalently, the event) to a variable. The syntax for this is:
on ?msg as message() {acl:performative = acl:inform, acl:content = ?content}
Plan postcondition
A plan can be invoked by name, it can be triggered by an event or message, and it can be invoked in a goal-directed manner by unifying with the post-condition. The post-condition is a logical sentence (see TODO knowledge representation), denoting the condition that is stated to be achieved by the plan. Note that it is currently the responsibility of the agent programmer to ensure that the genuinely obtains as a result of successful execution of the plan.
Example syntax:
plan postcondition p(10) && q(?x) end.
Currently, the use of the pre-condition to invoke plans is rather limited. This will change in future.
Plan action
The body of a plan is the action that it will carry out. A plan has
only one action, but the action operators ; for
sequential composition and | for alternate choice allow
arbitrary composite actions to be constructed. The individual action
primitives are further described in the scripting languge section
(see @TODO).
Example syntax:
plan
do
holds p(?x) ;
println( ?x, " is true of p()" )
end.
Note that, because ; is an operator applying to two
action operands, there is no trailing semi-colon at the end of the
action expression.
With intention
This is a rather complex mechanism for chaining plans together over time. Its very complexity suggests that a better solution should be sought, so we will not explain it further here. The intrepid explorer can venture into the details in the codebase.
Intentions
An intention represents a course of action that the agent has committed to. This may be represented as a series of actions that the agent will perform, a logical goal that it is attempting to achieve, or a mixture of these. It is quite possible and rational for an agent to have multiple intentions at one time. For example, a negotiation agent might be participating in simultaneous conversations with two different agents to decide the price of a good. Each conversation is represented in the Nuin agent as a separate intention. However, each conversation may be controlled by the same plan. Thus we can see that intentions also serve to hold task-specific state information. This might include parameters of the task, such as the symbol that serves to uniquely identify the conversation, or local parameters such as the current value of a plan script variable.
In general, Nuin defines three types of intention:
- intention-to, which is a commitment to perform a certain action or set of actions (represented by a plan body)
- intention-that, which represents a commitment to achieve a certain goal
- maintain which represents a commitment to achieve a certain goal or state of the world, and to act so as to maintain that goal.
Of these, only the intention-to has been explored in detail in the current implementation. Maintain intentions are not properly implemented at all (yet), and intention-that intentions are rather weak in the absence of a planning component.
An intention-to is the standard intention type; it is this intention type that is created when a plan is triggered by an event pattern. Specifically, it is the intention-to perform the triggered plan.
An intention-that is created by passing an instance of the class IntentionThat
to the agent's adopt() method. This is accomplished in Nuinscript by the
achieve action, which is executed at run-time by the ActionAchieve
action object. The argument to achieve is a sentence, that will be matched
with the post-conditions of the known plans. Note that this is not a true
reasoning step: the matching of achieves to post-conditions is performed by a simple structural
comparison on the operands of the sentence. This is an area that is expected to receive
significant revision as Nuin develops.
holds hasState( ?component, broken ) ; achieve fixed( ?component )
Note that an intention-that is not the same as a goal. In the BDI agent model, the I modality represents courses of actions that the agent has committed to bringing about. By contrast, the D modality (desires, aka goals) represents states of the world that the agent would like to bring about, but isn't necessarily actively committed to. A truly rational agent should not hold inconsistent intentions, but may rationally hold inconsistent desires.
Testing intentions
There are two tests for the intentions that the agent holds:
has intention to plan-name has intention achieve sentence
In addition, the library action lib:getIntentionID can be used to access the
UUID that is the identifier for this intention. Since this ID is unique to the intention,
it can make a convenient identifier for activities related to the intention, e.g. a
conversation ID.
Accessing intentions from Java
Intentions are at the heart of the operation of Nuin agents. Consequently, they are moderately complex objects. Full details of the Intention API can be found in the Javadoc [todo reference]. In this section we summarise some of the key points.
The main class is org.nuin.agent.Intention, which is abstract, and sub-classes
IntentionTo and IntentionThat in the same package.
Class org.nuin.agent.IntentionMaintain will be added in future.
Every intention is associated with exactly one Nuin agent, which can be accessed via the
getAgent() method. The getBindings() method returns the current
set of variable bindings for the intention, and the getAction() method
returns the current action object that is being performed.
Intentions can be suspended after they have been started (see script actions [todo]). If a given
intention is suspended, it will return true to the isSuspended() method. An intention
that has not yet completed will return true to isActive() - it is possible for an
intention to be both active and suspended simultaneously, since suspension occurs while the
intention's action is being performed.
In order to allow the same action object to be re-used by multiple simultaneous intentions, it is necessary that plan action objects do not store state information internally. Instead, action objects that require internal state can use the state variable machinery from the intention object to store and retrieve internal state. Note: these methods are only of use to programmers who wish to develop new action objects; users who just re-use the built-in actions from a script do not need to understand or use intention state variables.
Every intention has a unique identifer, accessed via getUniqueID(). In addition,
the intention name, which is taken from the executing plan name with variables bound,
is accessed via getBoundName().
« prev: developing agents | intentions and plans | next: knowledge representation »