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.Container — Type
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.

source
Fjage.CyclicBehavior — Method
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
source
Fjage.MessageBehavior — Method
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

@message "org.arl.fjage.demo.MySpecialNtf" struct MySpecialNtf end

@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
source
Fjage.OneShotBehavior — Method
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
source
Fjage.PoissonBehavior — Method
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
source
Fjage.SlaveContainer — Method
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.

source
Fjage.TickerBehavior — Method
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
source
Fjage.WakerBehavior — Method
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
source
Base.kill — Method
kill(container::Container, aid::AgentID)
kill(container::Container, name::AbstractString)
kill(container::Container, agent::Agent)

Stop an agent running in a container.

source
Base.wait — Method
wait(platform::Platform)

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

source
Fjage.BackoffBehavior — Method
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
source
Fjage.ParameterMessageBehavior — Method
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
source
Fjage.action — Function
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.

source
Fjage.add — Method
add(a::Agent, b::Behavior)

Add a behavior to an agent.

source
Fjage.add — Method
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.

source
Fjage.add — Method
add(platform::Platform, container::Container)

Run a container on a platform.

source
Fjage.addlistener — Method
addlistener(container::Container, listener)

Add message listener to container. Unimplemented.

source
Fjage.agent — Method
agent(a::Agent, name::AbstractString)

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

source
Fjage.agent — Method
agent(container::Container, aid::AgentID)
agent(container::Container, name::AbstractString)

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

source
Fjage.agentforservice — Method
agentforservice(a::Agent, svc::AbstractString)

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

source
Fjage.agentforservice — Method
agentforservice(c::Container, svc::AbstractString, owner::Agent)

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

source
Fjage.agents — Method
agents(container::Container)

Get list of agents running in the container.

source
Fjage.agentsforservice — Method
agentsforservice(a::Agent, svc::AbstractString)

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.

source
Fjage.agentsforservice — Method
agentsforservice(c::Container, svc::AbstractString, owner::Agent)

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.

source
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.

source
Fjage.autoclone — Method
autoclone(container::Container)

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

source
Fjage.backoff — Method
backoff(b::WakerBehavior, millis)

Schedule the behavior to re-run in millis milliseconds.

source
Fjage.block — Method
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.

source
Fjage.canlocateagent — Method
canlocateagent(container::Container, aid::AgentID)
canlocateagent(container::Container, name::AbstractString)

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

source
Fjage.containers — Method
containers(platform::Platform)

Get list of containers running on the platform.

source
Fjage.containsagent — Method
containsagent(container::Container, aid::AgentID)
containsagent(container::Container, name::AbstractString)

Check if an agent is running in the container.

source
Fjage.delay — Method
delay(a::Agent, millis)

Delay the execution of the agent by millis milliseconds.

source
Fjage.delay — Method
delay(platform::Platform, millis)

Sleep for millis ms on the platform.

source
Fjage.deregister — Method
deregister(a::Agent, svc::AbstractString)

Deregister agent from providing a specied service.

source
Fjage.deregister — Method
deregister(c::Container, aid::AgentID, svc::AbstractString)

Deregister agent aid from providing service svc.

source
Fjage.deregister — Method
deregister(c::Container, aid::AgentID)

Deregister agent aid from providing any services.

source
Fjage.init — Method
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
source
Fjage.isidle — Method
isidle(container::Container)

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

source
Fjage.isidle — Method
isidle(platform::Platform)

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

source
Fjage.logerror — Function
logerror(f::Function)
logerror(f::Function, src)

Run function f() and log any errors that occur.

source
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.

source
Fjage.nanotime — Method
nanotime(platform::Platform)

Get current time in nanoseconds for the platform.

source
Fjage.platform — Method
platform(a::Agent)

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

source
Fjage.platform — Method
platform(container::Container)

Get platform on which the container is running.

source
Fjage.platformsend — Method
platformsend(a::Agent, msg::Message)

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

source
Fjage.processmessage — Method
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

@message "org.arl.fjage.demo.MySpecialNtf" struct MySpecialNtf end

@agent struct MyAgent end

function Fjage.processmessage(a::MyAgent, msg::MySpecialNtf)
  # do something useful with the message here...
end
source
Fjage.processrequest — Method
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

@message "org.arl.fjage.demo.MySpecialReq" struct MySpecialReq end

@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
source
Fjage.ps — Method
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.

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

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

source
Fjage.receive — Function
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.

source
Fjage.register — Method
register(a::Agent, svc::AbstractString)

Register agent as providing a specied service.

source
Fjage.register — Method
register(c::Container, aid::AgentID, svc::AbstractString)

Register agent aid as providing service svc.

source
Fjage.request — Function
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.

source
Fjage.reset — Method
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.

source
Fjage.restart — Method
restart(b::Behavior)

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

source
Fjage.send — Method
send(a::Agent, msg::Message)

Send a message from agent a.

source
Fjage.send — Method
send(c::Container, msg)

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

source
Fjage.services — Method
services(container::Container)

Get list of services running in the container.

source
Fjage.setup — Function
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
source
Fjage.shutdown — Method
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
source
Fjage.shutdown — Method
shutdown(platform::Platform)

Stop the platform and all containers running on the platform.

source
Fjage.shutdown — Method
shutdown(container::Container)

Stop a container and all agents running in it.

source
Fjage.start — Method
start(platform::Platform)

Start the platform and all containers running on the platform.

source
Fjage.startup — Function
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
source
Fjage.state — Method
state(container::Container)

Get a human-readable state of the container.

source
Fjage.stop — Method
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
source
Fjage.store — Method
store(a::Agent)

Return the persistent data store for agent. Currently unimplemented.

source
Fjage.subscribe — Method
subscribe(c::Container, topic::AgentID, agent::Agent)

Subscribe agent running in container c to topic.

source
Fjage.tickcount — Method
tickcount(b::PoissonBehavior)

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

source
Fjage.tickcount — Method
tickcount(b::TickerBehavior)

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

source
Fjage.unsubscribe — Method
unsubscribe(c::Container, agent::Agent)

Unsubscribe agent running in container c from all topics.

source
Fjage.unsubscribe — Method
unsubscribe(c::Container, topic::AgentID, agent::Agent)

Unsubscribe agent running in container c from topic.

source
Fjage.@agent — Macro

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
source