This document describes the features related to the definition of an agent in SARL. Before reading this document, we recommend that you read the General Syntax Reference, the Skill Reference, and the Built-in Capacity Reference.
An agent is an autonomous entity having a set of skills to realize the capacities it exhibits.
Before detailing the architecture and the definition tools of an agent, it may be helpful to understand where is “living” an agent in the multi-agent system.
The following figure illustrates the position of an agent (at the center of the figure) in different contexts. The details are discussed below.
When it is spawn, an agent is living inside the system in a place named “Context”.
A Context defines the boundary of a sub-system, and gathers a collection of spaces. A Space is the support of the interaction between agents respecting the rules defined in the spaces’ specification.
Important Note In each context, there is at least one particular space called Default Space to which all agents in this context belong.
It ensures the existence of a common shared space to all agents in the same context. Each agent can then create specific public or private spaces to achieve its personal goals (the blue space on the figure above).
Important Note Since their creation, agents are incorporated into a context called the Default Context. It is important to notice that the Default Context is not necessarily the same for every agent.
An agent has an identifier for each space it is involved in. For the case of event-based interaction spaces, this identifier is called “address”.
During its lifetime, an agent may join and participate in other contexts that are not the default context. They are called the external contexts of the agent.
Note There is no restriction about the number of contexts in which an agent is belonging to, except that it is always in its default context.
For joining or leaving a context, the agent must use the ExternalContextAccess
built-in capacity. It is detailed in the
Built-in Capacity Reference.
In 1967, Arthur Koestler coined the term holon as an attempt to conciliate holistic and reductionist visions of the world. A holon represents a part-whole construct that can be seen as a component of a higher level system or as whole composed of other self-similar holons as substructures.
Holonic Systems grew from the need to find comprehensive construct that could help explain social phenomena. Since then, it came to be used in a wide range of domains, including Philosophy, Manufacturing Systems, and Multi-Agents Systems.
Several works have studied this question and they have proposed a number of models inspired from their experience in different domains. In many cases, we find the idea of agents composed of other agents.
More recently, the importance of holonic multi-agent systems has been recognized by different methodologies such as ASPECS or O-MASE.
Note In SARL, we recognize that agents can be composed of other agents. Therefore, SARL agents are in fact holons that can compose each other to define hierarchical or recursive multi-agent system, called holarchies.
In order to achieve this, SARL agents are self-similar structures that compose each other via their contexts. Each agent defines its own context, called Inner Context. Because this inner context may be joined by other agents, or agents may be spawn inside this inner context, it is possible to build a holarchy.
Very Important Note An agent is always a participant of the default space of its inner space.
Important Note The unique identifier (usually a Unique Universal Identifier) of the inner context is equal to the unique identifier of its owning agent.
At the top level of the holarchy, we consider an omnipresent agent. It is named the Universe Agent (or Root Agent). The runtime environment will be in charge of spawning the first agents in the system as members of the Universe Agent.
The inner context of the Universe Agent is called the Universe Context, or the Janus Context if you are using the Janus runtime environment.
The architecture of an agent is illustrated by the following figure.
An agent has a set of built-in capacities considered essential to respect the commonly accepted competencies of agents, such autonomy, reactivity, pro-activity and social capacities.
The full set of Built-in Capacities will be presented in the corresponding
Reference document. Among these
built-in capacities, is the Behaviors
capacity that enables
agents to incorporate a collection of behaviors that will determine
its global conduct.
An agent has also a default behavior directly described within its definition.
A Behavior maps a collection of perceptions represented by Events to a sequence of Actions. The various behaviors of an agent communicate using an event-driven approach.
An Event is the specification of some occurrence in a Space that may potentially trigger effects by a listener (e.g., agent, behavior, etc.)
An agent is declared with the agent
keyword. In the agent’s body block, we can declare Mental States
(in the form of attributes), Actions and Behaviors.
The following code illustrates the definition of an agent named MyAgent
, and that is empty.
Basically, this agent does nothing, and does not react on events.
agent MyAgent {
}
The mental state of an agent is composed by the data in the knowledge of the agent. Most of the time, the mental state is implemented as a collection of attributes in the agent.
According to the General Syntax Reference,
the attributes may be modifiable (when declared with the var
keyword), or unmodifiable (when
declared with the val
keyword).
agent MyAgent {
// Defining a modifiable element of the mental state
var mentalStateElement1 : String
// Defining an unmodifiable element of the mental state
val mentalStateElement2 : boolean = true
}
It is allowed to define actions (methods) in the agent. The syntax described is described in the General Syntax Reference.
The example below illustrates the creation of two actions in the agent.
agent MyAgent {
uses Logging
// Defining an action without parameter nor return type
def myAction1 {
info("Hello world")
}
// Defining an action with a variadic parameter and no return type
def myAction2(param : int*) {
info("params are " + param)
}
}
In some use cases, it is useful to specialize the definition of an agent. This mechanism is supported by the inheritance feature of SARL, which has the same semantic as the inheritance mechanism as the Java object-oriented language.
The extended agent is specified just after the extends
keyword.
Very Important Note An agent type can extend only one other agent type. This is close to the constraint on the extension of classes in the Java language.
In the following code, a first agent is defined with the name
MyAgent
and an attribute named attr
.
A second agent MySubAgent
is defined as the extension of the first agent. It contains a function named
action
, which is displaying the inherited attribute.
agent MyAgent {
protected var attr : String
}
agent MySubAgent extends MyAgent {
uses Logging
def action {
info(attr)
}
}
Modifiers are used to modify declarations of types and type members. This section introduces the modifiers for the agent. The modifiers are usually written before the keyword for defining the agent.
The complete description of the modifiers’ semantic is available in this section.
An agent may be declared with one or more modifiers, which affect its runtime behavior:
public
: the agent is accessible from any other type (default);package
: the agent is accessible from only the types in the same package.abstract
: the agent is abstract and cannot be instanced.final
: avoid to be derived.Examples:
public agent Example1 {
}
package agent Example2 {
}
abstract agent Example3 {
}
final agent Example4 {
}
The modifiers for the fields in an agent are:
protected
: the field is accessible within the same package, and derived agents;package
: the field is accessible only within the same package of its agent;private
: the field is accessible only within its agent (default).static
: the field is a class field, not an instance field.Examples:
protected var example1 : Object
package var example2 : Object
private var example3 : Object
static var example4 : Object
The modifiers for the methods in an agent are:
protected
: the method is accessible within the same package, and derived classes (default);package
: the method is accessible only within the same package as its class;private
: the method is accessible only within its class.abstract
: the method has no implementation in the class.dispatch
: the method provides an implementation for the dispatch method mechanism.final
: the method cannot be overridden in derived classes.static
: the method is a class method, not an instance method.synchronized
: the method is synchronized on the class instance.Examples:
// Protected access function
protected def example1 { }
// Package access function
package def example2 { }
// Private access function
private def example3 { }
// Abstract function
abstract def example4
// Not-overridable function
final def example5 { }
// Static function
static def example6 { }
// Synchronized function
synchronized def example7 { }
// Dispatch functions
dispatch def example8(p : Integer) { }
dispatch def example8(p : Float) { }
All the modifiers for the nested types are allowed except
public
.
The behaviors of an agent correspond to the units that are executed by the agent for exhibiting its general behavior.
The execution of the behaviors are related to the life cycle of the agents. Every agent is following the steps:
Initialize
event;Destroy
event.The definition of the reactive behaviors is based on the event handling mechanism of SARL. Events may be emitted in spaces, and received by the agents belonging to these spaces. An agent may indicate that it is interesting for receiving an event by specifying an event handler using the following syntax:
on EventName [Guard] {
Statements
}
EventName
is the name of the events to wait for. It could contains generic type declaration. Guard
is the optional definition of a predicate
that may be true for executing the Statements
. The statements are executed only if an event with the given name is
received, and if the guard is true.
In the guard and the statements, it is possible to use the instance of the received event: the occurrence.
This instance is represented by the occurrence
keyword. It is an implicit
variable as the keywords this
and it
.
When an agent is ready to be executed by the runtime environment, it receives the Initialize
event.
This event is defined as:
event Initialize {
val parameters : Object[]
val spawner : UUID
}
It contains the list of the parameters given to the spawning function (as specified in the built-in capacities).
agent MyAgent {
uses Logging
on Initialize {
info("My spawner is " + occurrence.spawner)
info(
"My initialization parameters are: "
+ occurrence.parameters )
}
}
Because Initialize
is an event, the handler in the agent could use a guard. This feature enables
the developer to write different initialization blocks depending on the guards of the handlers.
In the following example, the first event handler is executed when the Initialize
event has
no parameter. The second event handler is executed when the event has at least one parameter.
agent MyAgent {
uses Logging
on Initialize [ occurrence.parameters.empty ] {
info("Initialization without parameters")
}
on Initialize [ ! occurrence.parameters.empty ] {
info("Initialization with parameters: "
+ occurrence.parameters )
}
}
The on Initialize
event handler in agents is a bit special, as it is the code run when an agent is born.
As such, its execution is more “synchronous” than other on-behavior rules. In particular:
on Initialize
, will not be processed until that
on Initialize
code finishes. So, your agent initialization should not depend
(and wait) on any fired event being processed, as they won’t!on Initialize
, the spawn instructions will return only
after the agent has been created. However, creation of the agent (i.e., of the
corresponding object) does not include initialization of the agent via its
on Initialize
handler. Said so, the Java thread manager may process those
initialization processes of the new agent before continuing with the execution
of the spawning agent (and this seems to be the case in many Linux boxes
where the executor service of Java tends to have the same behavior during
all the runs). If you change computer, it may be different. In the following
example, the thread executor service of Java seems to give the priority to
the on Initialize
of Agent2
instead of continuing the run of the
spawn function.agent Agent1 {
uses Logging, Lifecycle
var agent_name = "agent1"
on Initialize {
info(agent_name + " spawned")
info(agent_name + " spawning Agent2")
spawn(Agent2)
info(agent_name + " end")
}
}
agent Agent2 {
uses Logging
var agent_name = "agent2"
on Initialize {
info(agent_name + " spawned")
info(agent_name + " sleeping")
Thread::sleep(5000)
info(agent_name + " woke up")
info(agent_name + " end")
}
on Initialize {
info(agent_name + " init2")
info(agent_name + " init2 end")
}
}
The output has been:
Launching the agent: Agent1
agent1 spawned
agent1 spawning Agent2
agent2 spawned
agent2 init2
agent2 sleeping
agent2 init2 end
agent2 woke up
agent2 end
agent1 end
Here it appears as the on Initialize
behaviors have been run all before
the execution resumes after the spawn()
statement, but this is just one way
and one should not rely on that behavior being guaranteed: once the spawned
agent is created, the spawn()
commands returns.
It is allowed to declare multiple initialization handlers into a single agent type, as illustrated by:
agent Agent3 {
uses Logging
on Initialize {
info("1")
}
on Initialize {
info("2")
}
on Initialize {
info("3")
}
}
According to the SARL operational semantic, the three event handlers for Initialize
are run in parallel.
The initialization event handlers are not constructors (as defined in object-oriented programming paradigm),
they are reacting to the receiving of an Initialize
occurrence.
Consequently, we could say that there is a single Initialize
occurrence during the whole life of an agent;
But, it may have multiple handlers to react to the receiving of this event.
The example in the previous section could be extended in order to illustrate how the initialization handlers
are run when the type of the agent (here Agent4
) is declared within a inheritance hierarchy.
agent Agent4 extends Agent3 {
uses Logging
on Initialize {
info("4")
}
on Initialize {
info("5")
}
}
According to the SARL operational semantic, all the initialization handlers are run in parallel.
In the previous example, five event handlers will be run: three are defined into Agent3
, and
two are defined into Agent4
. This mechanism is generalized to all the events within an agent.
The counterpart of Initialize
is the event Destroy
. This event is defined as:
event Destroy {
}
Example:
agent MyAgent {
uses Logging
on Destroy {
info("Destroying the agent")
}
}
As for Initialize
, the handlers of the Destroy
event could be guarded.
In the following example, the first event handler is executed when the Destroy
is receivedand there is resource stored in the corresponding field. The second event handler
is executed when there is no resource.
agent MyAgent {
uses Logging
var resource : Object
on Destroy [ resource !== null ] {
info("Destroying the agent when there is a resource")
}
on Destroy [ resource === null ] {
info("Destroying the agent when there is no resource")
}
}
The reactive behavior of an agent is specified with a collection of event handlers. The principle of a reactive behavior is to execute a part of the global agent behavior when something has happening in the agent, or in its environment.
In the following example, the agent is reacting to the reception of the SomethingChanged
event.
As for all the event handlers, it could be guarded by a predicate.
agent MyAgent {
uses Logging
on SomethingChanged {
info("Reactive behavior")
}
}
When an event is received and the guard of the corresponding handler is true, the event handler is said to be triggered.
When multiple event handlers are triggered at the same time, they are all executed in parallel.
In the following example, the two handlers for the SomethingChanged
event are executed in parallel.
agent MyAgent {
uses Logging
on SomethingChanged {
info("First reactive behavior")
}
on SomethingChanged {
info("Second reactive behavior")
}
}
A proactive behavior is a part of the global behavior of an agent that the agent is deciding to execute by itself. The execution of a reactive behavior is initiated by a part of the code external to this behavior. In opposite, the initiator of the execution of a proactive behavior is the agent itself.
In SARL, a proactive behavior is a behavior that is scheduled by the agent. The scheduling
mechanism is provided by the Schedules
built-in capacity.
In the following example, the agent execute its proactive behavior every second.
agent MyAgent {
uses Schedules, Logging
on Initialize {
every(1000) [
info("Run a pro-active behavior")
]
}
}
Since the version 0.14
of SARL, it is possible to define an event with generic types.
The counterpart of this type of event definition is related to the definition of generic types in the event handlers.
The specific of the generic types within the event handlers could be assimilated to an implict guard condition.
In other word, the event handler is run only if the received event is matching the specific generic types.
Let the following example in which the event MyGenericEvent
is defined with two generic types T1
and T2
.
Several event handlers have been defined for illustrating multiple cases.
event MyGenericEvent<T1, T2 extends Number> {
var field0 : T1
var field1 : T2
}
agent MyAgent {
uses Logging
on MyGenericEvent {
info("Run whatever the values of field0 and field1")
}
on MyGenericEvent<?, ?> {
info("Run whatever the values of field0 and field1")
}
on MyGenericEvent<String, ?> {
info("Run if field0 is a String, whatever the value of field1")
}
on MyGenericEvent<?, Double> {
info("Run if field1 is a Double, whatever the value of field0")
}
on MyGenericEvent<String, Double> {
info("Run only if field0 is a String and field1 is a Double")
}
on MyGenericEvent<? extends String, Double> {
info("Run only if field0 is a String and field1 is a Double")
}
on MyGenericEvent<Number, Integer> {
info("Run only if field0 is a String and field1 is a Double")
}
}
Basically, when an event MyGenericEvent
is received, it is associated to the definition of concrete types for T1
and T2
.
For example, the event corresponding to new MyGenericEvent<String, Double>
is received, then T1
is assimilated to String
and T2
to Double
.
From this example of event occurrence, the activated event handlers will be because the declared geneic types ar ematching <String, Double>
from the event:
on MyGenericEvent
on MyGenericEvent<?, ?>
on MyGenericEvent<String, ?>
on MyGenericEvent<?, Double>
on MyGenericEvent<String, Double>
on MyGenericEvent<? extends String, Double>
But, on MyGenericEvent<Number, Integer>
is not run because the generic types are not matching those from the event.
It is possible to define the behaviors of an agent according to a specific and already known agent architecture. SARL API provides a list of well-known agent architectures that are listed here.
An agent is an autonomous entity having a set of skills to realize the capacities it exhibits. An agent has a set of built-in capacities considered essential to respect the commonly accepted competencies of agents, such autonomy, reactivity, pro-activity and social capacities.
The definition of a capacity or a skill is out of the scope of this reference document. For details, please read the Capacity Reference, and the Skill Reference.
In the rest of this section, it is assumed that the following capacity and skill are defined:
capacity Cap {
def action
}
skill Ski implements Cap {
uses Logging
def action {
info("Action")
}
}
When an agent must use a capacity in one of its behaviors, it must own an implementation of this capacity: a skill.
For assigning a skill to an agent, the instance of the skill must be created. Then, it is associated with the
implemented capacity.
In the following example, the agent is creating the Ski
skill. This instance is associated with
the corresponding capacity Cap
with the function setSkill(Skill, Class<? extends Capacity>*)
.
When the function setSkill
is returning, the agent becomes able to use the skill.
agent MyAgent {
on Initialize {
var theSkill = new Ski
setSkill(theSkill)
}
}
If some cases, you may want to set the skill if one was not set up before. The specific behavior
is supported by setSkillIfAbsent(Skill, Class<? extends Capacity>*)
.
agent MyAgent {
on Initialize {
var theSkill = new Ski
setSkillIfAbsent(theSkill)
}
}
Because the built-in capacities are supported by the runtime environment, the corresponding skills are given to the agent. It means that there is no need for an agent to set a skill for a built-in capacity.
However, in rare cases, it is possible to use the function setSkill(Skill, Class<? extends Capacity>*)
for
changing the implementation of a built-in capacity.
After a skill is registered into the agent, it could be invoked.
For invoking a function implemented by a skill, the two following steps must be done:
getSkill(Class<? extends Capacity>)
permits retrieving the skill associated to the given capacity;Note This method of invocation is not recommended by the SARL developers. You should prefer the use of the extension methods (see below).
agent MyAgent {
on Initialize {
var s = new Ski
setSkill( s, Cap )
}
on SomeEvent {
// Retreive the capacity implementation
var s = getSkill(Cap)
// Run the action of the skill
s.action
}
}
The built-in capacities are accessible in the same way as the other capacities, with the getters.
agent MyAgent {
on SomeEvent {
// Retreive the capacity implementation
var s = getSkill(Lifecycle)
// Run the action of the skill
s.killMe
}
}
Invoking a capacity/skill with the getter method is not user-friendly. Since the General Syntax Reference describes the “extension method” mechanism, it is possible to use it for invoking the capacities.
But, instead of using an import
directive, the uses
keyword is provided for importing the
capacities into the agent. In the following example, the Cap
capacity is imported.
After a capacity was “imported”, it is possible to directly call the functions of the capacity
(according to the extension method syntax). In the following example, the action
with the name action
is invoked. This action is defined in the Cap
capacity.
agent MyAgent {
uses Cap
on Initialize {
var s = new Ski
setSkill(s)
}
on SomeEvent {
// Run the action of the skill
action
}
}
The built-in capacities are accessible in the same way as the other capacities, with the extension methods.
Example:
agent MyAgent {
uses Lifecycle
on SomeEvent {
// Run the action of the skill
killMe
}
}
Copyright © 2014-2024 SARL.io, the Original Authors and Main Authors.
Documentation text and medias are licensed under the Creative Common CC-BY-SA-4.0; you may not use this file except in compliance with CC-BY-SA-4.0. You may obtain a copy of CC-BY-4.0.
Examples of SARL code are licensed under the Apache License, Version 2.0; you may not use this file except in compliance with the Apache License. You may obtain a copy of the Apache License.
You are free to reproduce the content of this page on copyleft websites such as Wikipedia.
Generated with the translator docs.generator 0.14.0.