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.AgentID — Method
AgentID(a::Agent)Get the AgentID of the agent.
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.
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)
endFjage.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
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)
endFjage.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)
endFjage.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)
endFjage.RealTimePlatform — Type
Real-time platform.
Fjage.SlaveContainer — Type
Slave container.
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.
Fjage.StandaloneContainer — Type
Standalone container.
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)
endFjage.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)
endBase.flush — Method
flush(a::Agent)Flush agent's incoming message queue.
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)
endFjage.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 + 27Fjage.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.
Fjage.addlistener — Method
addlistener(container::Container, listener)Add message listener to container. Unimplemented.
Fjage.agent — Method
agent(a::Agent, name::AbstractString)Generate an owned AgentID for an agent with the given name.
Fjage.agent — Method
agent(b::Behavior)Get the agent owning the behavior.
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.
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.
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.
Fjage.agents — Method
agents(container::Container)Get list of agents running in the container.
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.
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.
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.autoclone — Method
autoclone(container::Container)Check if the container is configured to automatically clone messages on send.
Fjage.backoff — Method
backoff(b::WakerBehavior, millis)Schedule the behavior to re-run in millis milliseconds.
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.
Fjage.buildversion — Method
buildversion(platform::Platform)Get build version of the platform.
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.
Fjage.container — Method
container(a::Agent)Get container in which the agent is running.
Fjage.containers — Method
containers(platform::Platform)Get list of containers running on the platform.
Fjage.containsagent — Method
containsagent(container::Container, aid::AgentID)
containsagent(container::Container, name::AbstractString)Check if an agent is running in the container.
Fjage.currenttimemillis — Method
currenttimemillis(a::Agent)Get current time in milliseconds for the agent.
Fjage.currenttimemillis — Method
currenttimemillis(platform::Platform)Get current time in milliseconds for the platform.
Fjage.delay — Method
delay(a::Agent, millis)Delay the execution of the agent by millis milliseconds.
Fjage.delay — Method
delay(platform::Platform, millis)Sleep for millis ms on the platform.
Fjage.deregister — Method
deregister(a::Agent, svc::AbstractString)Deregister agent from providing a specied service.
Fjage.deregister — Method
deregister(c::Container, aid::AgentID, svc::AbstractString)Deregister agent aid from providing service svc.
Fjage.deregister — Method
deregister(c::Container, aid::AgentID)Deregister agent aid from providing any services.
Fjage.done — Method
done(b::Behavior)Check if a behavior is completed.
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"
endFjage.isblocked — Method
isblocked(b::Behavior)Check if a behavior is currently blocked.
Fjage.isidle — Method
isidle(container::Container)Check if container is idle. Unimplemented. Currently always returns true.
Fjage.isidle — Method
isidle(platform::Platform)Check if platform is idle. Unimplemented. Currently always returns true.
Fjage.isrunning — Method
isrunning(container::Container)Check if the container is running.
Fjage.isrunning — Method
isrunning(platform::Platform)Check if platform is running.
Fjage.logerror — Function
logerror()
logerror(src)Log current exception with a stack trace.
Fjage.logerror — Function
logerror(f::Function)
logerror(f::Function, src)Run function f() and log any errors that occur.
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.name — Method
name(a::Agent)Get the name of the agent.
Fjage.name — Method
name(container::Container)Get name of the container.
Fjage.nanotime — Method
nanotime(a::Agent)Get current time in nanoseconds for the agent.
Fjage.nanotime — Method
nanotime(platform::Platform)Get current time in nanoseconds for the platform.
Fjage.platform — Method
platform(a::Agent)Get platform on which the agent's container is running.
Fjage.platform — Method
platform(container::Container)Get platform on which the container is running.
Fjage.platformsend — Method
platformsend(a::Agent, msg::Message)Send a message to agents running on all containers on a platform. Currently unimplemented.
Fjage.priority — Method
priority(b::Behavior)Get the priority associated with a behavior.
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
const MySpecialNtf = MessageClass(@__MODULE__, "MySpecialNtf")
@agent struct MyAgent end
function Fjage.processmessage(a::MyAgent, msg::MySpecialNtf)
# do something useful with the message here...
endFjage.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
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)
endFjage.queuesize! — Method
queuesize!(a::Agent, n)Set the incoming message queue size for an agent. Currently unimplemented.
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.
Fjage.register — Method
register(a::Agent, svc::AbstractString)Register agent as providing a specied service.
Fjage.register — Method
register(c::Container, aid::AgentID, svc::AbstractString)Register agent aid as providing service svc.
Fjage.removelistener — Method
removelistener(container::Container, listener)Remove message listener from container.
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.
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.
Fjage.restart — Method
restart(b::Behavior)Restart a blocked behavior, previous blocked by block(b).
Fjage.send — Method
send(a::Agent, msg::Message)Send a message from agent a.
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.
Fjage.services — Method
services(container::Container)Get list of services running in the container.
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"
endFjage.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"
endFjage.shutdown — Method
shutdown(platform::Platform)Stop the platform and all containers running on the platform.
Fjage.shutdown — Method
shutdown(container::Container)Stop a container and all agents running in it.
Fjage.start — Method
start(platform::Platform)Start the platform and all containers running on the platform.
Fjage.start — Method
start(container::Container)Start a container.
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"
endFjage.state — Method
state(container::Container)Get a human-readable state of the container.
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"
endFjage.stop — Method
stop(b::Behavior)Terminates a behavior.
Fjage.store — Method
store(a::Agent)Return the persistent data store for agent. Currently unimplemented.
Fjage.subscribe — Method
subscribe(a::Agent, topic::AgentID)Subscribe agent to specified topic.
Fjage.subscribe — Method
subscribe(c::Container, topic::AgentID, agent::Agent)Subscribe agent running in container c to topic.
Fjage.tickcount — Method
tickcount(b::PoissonBehavior)Get the number of times a PoissonBehavior has ticked (its action() has been called).
Fjage.tickcount — Method
tickcount(b::TickerBehavior)Get the number of times a TickerBehavior has ticked (its action() has been called).
Fjage.unsubscribe — Method
unsubscribe(a::Agent, topic::AgentID)Unsubscribe agent from specified topic.
Fjage.unsubscribe — Method
unsubscribe(c::Container, agent::Agent)Unsubscribe agent running in container c from all topics.
Fjage.unsubscribe — Method
unsubscribe(c::Container, topic::AgentID, agent::Agent)Unsubscribe agent running in container c from topic.
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