Agents, Behaviors & Containers

Fjage.jl currently supports standalone containers and slave containers. Standalone containers may be used to deploy Julia-only agent applications. Slave containers are used to connect to Java master containers that host multi-language agent applications.

The agents, behaviors and containers API is modeled on the Java version, and hence the fjåge developer's guide provides a good introduction to developing agents.

Example

using Fjage

@agent struct MyAgent
  count::Int = 0
end

function Fjage.startup(a::MyAgent)
  add(a, TickerBehavior(5000) do a, b
    a.count += 1
    @info "Tick $(a.count)"
  end)
end

# start the agent in a container
c = Container()
add(c, "myagent", MyAgent())
start(c)

# when you've had enough, shutdown the container
sleep(30)
shutdown(c)

More examples are available in the examples folder for reference.

Agent, Behaviors & Container API

Fjage.AgentIDMethod
AgentID(a::Agent)

Get the AgentID of the agent.

Fjage.ContainerType
Container()
Container(platform::Platform)
Container(platform::Platform, name)

Create a standalone container running on a real-time platform (if unspecified). If a name is not specified, a unique name is randomly generated.

Fjage.CyclicBehaviorMethod
CyclicBehavior(action)

Create a cyclic behavior that runs repeatedly at the earliest available opportunity. The action(a::Agent, b::Behavior) function is called when the behavior runs. The onstart and onend fields in the behavior may be set to functions that are called when the behavior is initialized and terminates. Both functions are called with similar parameters as action.

The running of cyclic behaviors may be controlled using block(b), restart(b) and stop(b).

Examples:

using Fjage

@agent struct MyAgent end

function Fjage.startup(a::MyAgent)
  add(a, CyclicBehavior() do a, b
    @info "CyclicBehavior running..."
  end)
end
Fjage.MessageBehaviorMethod
MessageBehavior(action, millis)
MessageBehavior(action, filt, millis)

Create a behavior that runs every time a message arrives. The action(a::Agent, b::Behavior, msg) function is called when a message arrives. The onstart and onend fields in the behavior may be set to functions that are called when the behavior is initialized and terminates. Both functions are called with similar parameters as action.

If a filter filt is specified, only messages matching the filter trigger this behavior. A filter may be a message class or a function that takes the message as an argument and returns true to accept, false to reject.

If multiple MessageBehavior that match a message are active, only one of them will receive the message. The behavior to receive is the message is chosen based on its priority field. Messages with filters are given higher default priority than ones without filters.

The default init() for an agent automatically adds a MessageBehavior to dispatch messages to a processrequest() or processmessage() method. An agent may therefore process messages by providing methods for those functions. However, if an agent provides its own init() method, it should use MessageBehavior to handle incoming messages.

Examples:

using Fjage

const MySpecialNtf = MessageClass(@__MODULE__, "MySpecialNtf")

@agent struct MyAgent end

function Fjage.init(a::MyAgent)
  add(a, MessageBehavior(MySpecialNtf) do a, b, msg
    @info "Got a special message: $msg"
  end)
  add(a, MessageBehavior() do a, b, msg
    @info "Got a not-so-special message: $msg"
  end)
end
Fjage.OneShotBehaviorMethod
OneShotBehavior(action)

Create a one-shot behavior that runs exactly once at the earliest available opportunity. The action(a::Agent, b::Behavior) function is called when the behavior runs. The onstart and onend fields in the behavior may be set to functions that are called when the behavior is initialized and terminates. Both functions are called with similar parameters as action.

Examples:

using Fjage

@agent struct MyAgent end

function Fjage.startup(a::MyAgent)
  add(a, OneShotBehavior() do a, b
    @info "OneShotBehavior just ran"
  end)
end
Fjage.PoissonBehaviorMethod
PoissonBehavior(action, millis)

Create a behavior that runs randomly, on an averge once every millis milliseconds. The action(a::Agent, b::Behavior) function is called when the behavior runs. The onstart and onend fields in the behavior may be set to functions that are called when the behavior is initialized and terminates. Both functions are called with similar parameters as action.

Examples:

using Fjage

@agent struct MyAgent end

function Fjage.startup(a::MyAgent)
  add(a, PoissonBehavior(5000) do a, b
    @info "PoissonBehavior ran!"
  end)
end
Fjage.SlaveContainerMethod
SlaveContainer(host, port)
SlaveContainer(host, port, name)
SlaveContainer(platform::Platform, host, port)
SlaveContainer(platform::Platform, host, port, name)

Create a slave container running on a real-time platform (if unspecified), optionally with a specified name. If a name is not specified, a unique name is randomly generated.

Fjage.TickerBehaviorMethod
TickerBehavior(action, millis)

Create a behavior that runs periodically every millis milliseconds. The action(a::Agent, b::Behavior) function is called when the behavior runs. The onstart and onend fields in the behavior may be set to functions that are called when the behavior is initialized and terminates. Both functions are called with similar parameters as action.

Examples:

using Fjage

@agent struct MyAgent end

function Fjage.startup(a::MyAgent)
  add(a, TickerBehavior(5000) do a, b
    @info "Tick!"
  end)
end
Fjage.WakerBehaviorMethod
WakerBehavior(action, millis)

Create a behavior that runs exactly once after millis milliseconds. The action(a::Agent, b::Behavior) function is called when the behavior runs. The onstart and onend fields in the behavior may be set to functions that are called when the behavior is initialized and terminates. Both functions are called with similar parameters as action.

Examples:

using Fjage

@agent struct MyAgent end

function Fjage.startup(a::MyAgent)
  add(a, WakerBehavior(5000) do a, b
    @info "Awake after 5 seconds!"
  end)
end
Base.flushMethod
flush(a::Agent)

Flush agent's incoming message queue.

Base.killMethod
kill(container::Container, aid::AgentID)
kill(container::Container, name::String)
kill(container::Container, agent::Agent)

Stop an agent running in a container.

Base.waitMethod
wait(platform::Platform)

Wait for platform to finish running. Blocks until all containers running on the platform have shutdown.

Fjage.BackoffBehaviorMethod
BackoffBehavior(action, millis)

Create a behavior that runs after millis milliseconds. The action(a::Agent, b::Behavior) function is called when the behavior runs. The behavior may be scheduled to re-run in t milliseconds by calling backoff(b, t).

The onstart and onend fields in the behavior may be set to functions that are called when the behavior is initialized and terminates. Both functions are called with similar parameters as action.

The BackoffBehavior constructor is simply syntactic sugar for a WakerBehavior that is intended to be rescheduled often using backoff().

Examples:

using Fjage

@agent struct MyAgent end

function Fjage.startup(a::MyAgent)
  # a behavior that will run for the first time in 5 seconds, and subsequently
  # every 2 seconds
  add(a, BackoffBehavior(5000) do a, b
    @info "Backoff!"
    backoff(b, 2000)
  end)
end
Fjage.ParameterMessageBehaviorMethod
ParameterMessageBehavior()

ParameterMessageBehavior simplifies the task of an agent wishing to support parameters via ParameterReq and ParameterRsp messages. An agent providing parameters can advertise its parameters by providing an implementation for the params(a) method (or params(a, ndx) method for indexed parameters). The method returns a list of name-symbol pairs. Each entry represents a parameter with the specified name, and dispatched using the specified symbol. Get and set requests for the parameter are dispatched to get(a, Val(symbol)) and set(a, Val(symbol), value) methods (or get(a, Val(symbol), ndx) and set(a, Val(symbol), ndx, value) for indexed parameters). If the method isn't defined, and an agent struct field with the same name is present, it is used to back the parameter.

Setters should return the value that is set, so that it can be sent back to the requesting agent. If a setter returns nothing, the actual value is fetched using the getter and then sent to the requesting agent.

An agent may choose to avoid advertising specific parameters by defining isunlisted(Val(symbol)) method for the parameter to return true. Similarly, an agent may choose to mark a parameter as read-only by defining the isreadonly(Val(symbol)) method for the parameter to return true.

Parameter change events may be captured by defining a onparamchange(a::Agent, b::Behavior, param, ndx, value) method for the parameter.

The default init() for an agent automatically adds a ParameterMessageBehavior to dispatch handle parameters for an agent, and so an agent can benefit from this behavior without explicitly adding it. If an agent provides its own init() method and wishes to support parameters, it should add this behavior during init().

Examples:

using Fjage

@agent struct MyAgent
  param1::Int = 1
  param2::Float64 = 0.0
  secret::String = "top secret message"
  x::Int = 2
end

Fjage.param(a::MyAgent) = [
  "MyAgent.param1" => :param1,    # backed by a.param1
  "MyAgent.param2" => :param2,    # backed by a.param2, but readonly
  "MyAgent.X" => :X,              # backed by getter and setter
  "MyAgent.Y" => :Y,              # backed by getter only, so readonly
  "MyAgent.secret" => :secret     # backed by a.secret, but unlisted
]

Fjage.isreadonly(a::MyAgent, ::Val{:param2}) = true
Fjage.isunlisted(a::MyAgent, ::Val{:secret}) = true

Fjage.get(a::MyAgent, ::Val{:X}) = a.x
Fjage.set(a::MyAgent, ::Val{:X}, value) = (a.x = clamp(value, 0, 10))
Fjage.get(a::MyAgent, ::Val{:Y}) = a.x + 27
Fjage.actionFunction
action(b::Behavior)

The action function for a behavior is repeatedly called when a behavior runs. Typically, each type of Behavior provides an action method that implements its intended behavior.

Fjage.addMethod
add(a::Agent, b::Behavior)

Add a behavior to an agent.

Fjage.addMethod
add(container::Container, agent)
add(container::Container, name, agent)

Run an agent in a container. If the name is not specified, a unique name is randomly generated.

Fjage.addMethod
add(platform::Platform, container::Container)

Run a container on a platform.

Fjage.addlistenerMethod
addlistener(container::Container, listener)

Add message listener to container. Unimplemented.

Fjage.agentMethod
agent(a::Agent, name::String)

Generate an owned AgentID for an agent with the given name.

Fjage.agentMethod
agent(b::Behavior)

Get the agent owning the behavior.

Fjage.agentMethod
agent(container::Container, aid::AgentID)
agent(container::Container, name::String)

Get the agent ID of an agent specified by name or its agent ID.

Fjage.agentforserviceMethod
agentforservice(a::Agent, svc::String)

Find an agent providing a specified service. Returns an owned AgentID for the service provider, if one is found, nothing otherwise.

Fjage.agentforserviceMethod
agentforservice(c::Container, svc::String, owner::AgentID)

Lookup any agent providing the service svc, and return an AgentID owned by owner. Returns nothing if no agent providing specified service found.

Fjage.agentsMethod
agents(container::Container)

Get list of agents running in the container.

Fjage.agentsforserviceMethod
agentsforservice(a::Agent, svc::String)

Get a list of agents providing a specified service. Returns a list of owned AgentID for the service providers. The list may be empty if no service providers are found.

Fjage.agentsforserviceMethod
agentsforservice(c::Container, svc::String, owner::AgentID)

Lookup all agents providing the service svc, and return list of AgentID owned by owner. Returns an empty list if no agent providing specified service found.

Fjage.autoclone!Method
autoclone!(container::Container, b)

Configure container to automatically clone (or not clone) messages on send. Currently auto-cloning is unimplemented, and so b can only be false.

Fjage.autocloneMethod
autoclone(container::Container)

Check if the container is configured to automatically clone messages on send.

Fjage.backoffMethod
backoff(b::WakerBehavior, millis)

Schedule the behavior to re-run in millis milliseconds.

Fjage.blockMethod
block(b::Behavior)
block(b::Behavior, millis)

Marks a behavior as blocked, and prevents it from running until it is restarted using restart(b). If millis is specified, the behavior is automatically restarted after millis milliseconds.

Fjage.buildversionMethod
buildversion(platform::Platform)

Get build version of the platform.

Fjage.canlocateagentMethod
canlocateagent(container::Container, aid::AgentID)
canlocateagent(container::Container, name::String)

Check if an agent is running in the container, or in any of the remote containers.

Fjage.containerMethod
container(a::Agent)

Get container in which the agent is running.

Fjage.containersMethod
containers(platform::Platform)

Get list of containers running on the platform.

Fjage.containsagentMethod
containsagent(container::Container, aid::AgentID)
containsagent(container::Container, name::String)

Check if an agent is running in the container.

Fjage.currenttimemillisMethod
currenttimemillis(platform::Platform)

Get current time in milliseconds for the platform.

Fjage.delayMethod
delay(a::Agent, millis)

Delay the execution of the agent by millis milliseconds.

Fjage.delayMethod
delay(platform::Platform, millis)

Sleep for millis ms on the platform.

Fjage.deregisterMethod
deregister(a::Agent, svc::String)

Deregister agent from providing a specied service.

Fjage.deregisterMethod
deregister(c::Container, aid::AgentID, svc::String)

Deregister agent aid from providing service svc.

Fjage.deregisterMethod
deregister(c::Container, aid::AgentID)

Deregister agent aid from providing any services.

Fjage.doneMethod
done(b::Behavior)

Check if a behavior is completed.

Fjage.initMethod
init(a::Agent)

Initialization function for an agent. The default implementation calls setup(a), and adds a ParameterMessageBehavior to support agent parameters, a MessageBehavior that calls processrequest(a, msg) for REQUEST messages or processmessage(a, msg) for all other messages, and a OneShotBehavior that calls startup(a) once the agent is running. An agent may provide a method if these default behaviors are desired.

Examples:

using Fjage

@agent struct MyBareAgent end

function Fjage.init(a::MyBareAgent)
  @info "MyBareAgent init"
end
Fjage.isblockedMethod
isblocked(b::Behavior)

Check if a behavior is currently blocked.

Fjage.isidleMethod
isidle(container::Container)

Check if container is idle. Unimplemented. Currently always returns true.

Fjage.isidleMethod
isidle(platform::Platform)

Check if platform is idle. Unimplemented. Currently always returns true.

Fjage.isrunningMethod
isrunning(container::Container)

Check if the container is running.

Fjage.isrunningMethod
isrunning(platform::Platform)

Check if platform is running.

Fjage.loglevel!Method
loglevel!(level)

Set log level. Supported levels include :debug, :info, :warn, :error, :none. The equivalent Julia Logging.Debug, Logging.Info, etc levels may also be used.

Fjage.name!Method
name!(container::Container, s)

Set name of the container.

Fjage.nameMethod
name(a::Agent)

Get the name of the agent.

Fjage.nameMethod
name(container::Container)

Get name of the container.

Fjage.nanotimeMethod
nanotime(a::Agent)

Get current time in nanoseconds for the agent.

Fjage.nanotimeMethod
nanotime(platform::Platform)

Get current time in nanoseconds for the platform.

Fjage.platformMethod
platform(a::Agent)

Get platform on which the agent's container is running.

Fjage.platformMethod
platform(container::Container)

Get platform on which the container is running.

Fjage.platformsendMethod
platformsend(a::Agent, msg::Message)

Send a message to agents running on all containers on a platform. Currently unimplemented.

Fjage.priorityMethod
priority(b::Behavior)

Get the priority associated with a behavior.

Fjage.processmessageMethod
processmessage(a::Agent, msg)

Unless an agent overrides its init(a) function, the default behavior for an agent is to add a MessageBehavior that calls processmessage(a, msg) when it receives any message (with the exception of messages with performative REQUEST, for which processrequest(a, msg) is called instead). An agent may provide methods to handle specific messages.

Examples:

using Fjage

const MySpecialNtf = MessageClass(@__MODULE__, "MySpecialNtf")

@agent struct MyAgent end

function Fjage.processmessage(a::MyAgent, msg::MySpecialNtf)
  # do something useful with the message here...
end
Fjage.processrequestMethod
processrequest(a::Agent, req)

Unless an agent overrides its init(a) function, the default behavior for an agent is to add a MessageBehavior that calls processrequest(a, req) when it receives any message with a performative REQUEST. The return value of the function must be either nothing or a response message. If a response message is returned, it is sent. If nothing is returned, a default response with performative NOT_UNDERSTOOD is sent back. An agent may provide methods to handle specific messages. For unhandled requests, the default implementation just returns a nothing.

Examples:

using Fjage

const MySpecialReq = MessageClass(@__MODULE__, "MySpecialReq", nothing, Performative.REQUEST)

@agent struct MyAgent end

function Fjage.processrequest(a::MyAgent, req::MySpecialReq)
  # do something useful with the request here...
  # and return an AGREE response
  Message(req, Performative.AGREE)
end
Fjage.psMethod
ps(c::Container)

Get a list of agents running in a container. The list contains tuples of agent name and agent type. The agent type may be an empty string for agents running in remote containers, if the containers do not support type query.

Fjage.queuesize!Method
queuesize!(a::Agent, n)

Set the incoming message queue size for an agent. Currently unimplemented.

Fjage.receiveFunction
receive(a::Agent, timeout::Int=0; priority)
receive(a::Agent, filt, timeout::Int=0; priority)

Receive a message, optionally matching the specified filter. The call blocks for at most timeout milliseconds, if a message is not available. If multiple receive() calls are concurrently active, the priority determines which call gets the message. Only one of the active receive() calls will receive the message. Returns a message or nothing.

If a filter filt is specified, only messages matching the filter trigger this behavior. A filter may be a message class or a function that takes the message as an argument and returns true to accept, false to reject.

Lower priority numbers indicate a higher priority.

Fjage.registerMethod
register(a::Agent, svc::String)

Register agent as providing a specied service.

Fjage.registerMethod
register(c::Container, aid::AgentID, svc::String)

Register agent aid as providing service svc.

Fjage.removelistenerMethod
removelistener(container::Container, listener)

Remove message listener from container.

Fjage.requestFunction
request(a::Agent, msg::Message)
request(a::Agent, msg::Message, timeout::Int)

Send a request and wait for a response. If a timeout is specified, the call blocks for at most timeout milliseconds. If no timeout is specified, a system default is used. Returns the response message or nothing if no response received.

Fjage.resetMethod
reset(b::Behavior)

Resets a behavior, removing it from an agent running it. Once a behavior is reset, it may be reused later by adding it to an agent.

Fjage.restartMethod
restart(b::Behavior)

Restart a blocked behavior, previous blocked by block(b).

Fjage.sendMethod
send(a::Agent, msg::Message)

Send a message from agent a.

Fjage.sendMethod
send(c::Container, msg)

Send message msg to recipient specified in the message. Return true if the message is accepted for delivery, false otherwise.

Fjage.servicesMethod
services(container::Container)

Get list of services running in the container.

Fjage.setupFunction
setup(a::Agent)

Unless an agent overrides its init(a) function, the default behavior for an agent is to call setup(a) during initialization, and startup(a) once the agent is running. Typically, the setup(a) function is used to register services, and the startup(a) function is used to lookup services from other agents. Behaviors may be added in either of the functions.

Examples:

using Fjage

@agent struct MyAgent end

function Fjage.setup(a::MyAgent)
  @info "MyAgent setting up"
end

function Fjage.startup(a::MyAgent)
  @info "MyAgent started"
end
Fjage.shutdownMethod
shutdown(a::Agent)

This function is called when an agent terminates. An agent may provide a method to handle termination, if desired.

Examples:

using Fjage

@agent struct MyAgent end

function Fjage.shutdown(a::MyAgent)
  @info "MyAgent shutting down"
end
Fjage.shutdownMethod
shutdown(platform::Platform)

Stop the platform and all containers running on the platform.

Fjage.shutdownMethod
shutdown(container::Container)

Stop a container and all agents running in it.

Fjage.startMethod
start(platform::Platform)

Start the platform and all containers running on the platform.

Fjage.startMethod
start(container::Container)

Start a container.

Fjage.startupFunction
setup(a::Agent)

Unless an agent overrides its init(a) function, the default behavior for an agent is to call setup(a) during initialization, and startup(a) once the agent is running. Typically, the setup(a) function is used to register services, and the startup(a) function is used to lookup services from other agents. Behaviors may be added in either of the functions.

Examples:

using Fjage

@agent struct MyAgent end

function Fjage.setup(a::MyAgent)
  @info "MyAgent setting up"
end

function Fjage.startup(a::MyAgent)
  @info "MyAgent started"
end
Fjage.stateMethod
state(container::Container)

Get a human-readable state of the container.

Fjage.stopMethod
stop(a::Agent)
stop(a::Agent, msg)

Terminates an agent, optionally with an error message to be logged, explaining the reason for termination.

Examples:

using Fjage

@agent struct MyAgent
  criticalagent::Union{AgentID,Nothing} = nothing
end

function Fjage.startup(a::MyAgent)
  a.criticalagent = agentforservice("CriticalService")
  a.criticalagent === nothing && return stop(a, "Could not find an agent providing CriticalService")
  @info "MyAgent up and running"
end
Fjage.stopMethod
stop(b::Behavior)

Terminates a behavior.

Fjage.storeMethod
store(a::Agent)

Return the persistent data store for agent. Currently unimplemented.

Fjage.subscribeMethod
subscribe(a::Agent, topic::AgentID)

Subscribe agent to specified topic.

Fjage.subscribeMethod
subscribe(c::Container, topic::AgentID, agent::Agent)

Subscribe agent running in container c to topic.

Fjage.tickcountMethod
tickcount(b::PoissonBehavior)

Get the number of times a PoissonBehavior has ticked (its action() has been called).

Fjage.tickcountMethod
tickcount(b::TickerBehavior)

Get the number of times a TickerBehavior has ticked (its action() has been called).

Fjage.unsubscribeMethod
unsubscribe(a::Agent, topic::AgentID)

Unsubscribe agent from specified topic.

Fjage.unsubscribeMethod
unsubscribe(c::Container, agent::Agent)

Unsubscribe agent running in container c from all topics.

Fjage.unsubscribeMethod
unsubscribe(c::Container, topic::AgentID, agent::Agent)

Unsubscribe agent running in container c from topic.

Fjage.@agentMacro

The @agent macro is used to define a Fjage agent. The macro takes in a struct definition and converts it into an agent definition. The fields in the struct are treated as agent attributes. Fjage agent types are subtypes of Fjage.Agent and are mutable.

The struct definition may include initialization, as supported by the Base.@kwdef macro.

Examples:

using Fjage

@agent struct MyAgent
  field1::Int = 1
  field2::String = "hello"
end

abstract type SpecialAgent <: Fjage.Agent end

@agent struct MySpecialAgent <: SpecialAgent
  agentnumber::Int = 007
  licensedtokill::Bool = true
end