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
— MethodAgentID(a::Agent)
Get the AgentID
of the agent.
Fjage.Container
— TypeContainer()
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
— MethodCyclicBehavior(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.MessageBehavior
— MethodMessageBehavior(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.OneShotBehavior
— MethodOneShotBehavior(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.PoissonBehavior
— MethodPoissonBehavior(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.RealTimePlatform
— TypeReal-time platform.
Fjage.SlaveContainer
— TypeSlave container.
Fjage.SlaveContainer
— MethodSlaveContainer(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
— TypeStandalone container.
Fjage.TickerBehavior
— MethodTickerBehavior(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.WakerBehavior
— MethodWakerBehavior(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.flush
— Methodflush(a::Agent)
Flush agent's incoming message queue.
Base.kill
— Methodkill(container::Container, aid::AgentID)
kill(container::Container, name::String)
kill(container::Container, agent::Agent)
Stop an agent running in a container.
Base.wait
— Methodwait(platform::Platform)
Wait for platform to finish running. Blocks until all containers running on the platform have shutdown.
Fjage.BackoffBehavior
— MethodBackoffBehavior(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.ParameterMessageBehavior
— MethodParameterMessageBehavior()
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.action
— Functionaction(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.add
— Methodadd(a::Agent, b::Behavior)
Add a behavior to an agent.
Fjage.add
— Methodadd(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.add
— Methodadd(platform::Platform, container::Container)
Run a container on a platform.
Fjage.addlistener
— Methodaddlistener(container::Container, listener)
Add message listener to container. Unimplemented.
Fjage.agent
— Methodagent(a::Agent, name::String)
Generate an owned AgentID
for an agent with the given name.
Fjage.agent
— Methodagent(b::Behavior)
Get the agent owning the behavior.
Fjage.agent
— Methodagent(container::Container, aid::AgentID)
agent(container::Container, name::String)
Get the agent ID of an agent specified by name or its agent ID.
Fjage.agentforservice
— Methodagentforservice(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.agentforservice
— Methodagentforservice(c::Container, svc::String, 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
— Methodagents(container::Container)
Get list of agents running in the container.
Fjage.agentsforservice
— Methodagentsforservice(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.agentsforservice
— Methodagentsforservice(c::Container, svc::String, 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!
— Methodautoclone!(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
— Methodautoclone(container::Container)
Check if the container is configured to automatically clone messages on send.
Fjage.backoff
— Methodbackoff(b::WakerBehavior, millis)
Schedule the behavior to re-run in millis
milliseconds.
Fjage.block
— Methodblock(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
— Methodbuildversion(platform::Platform)
Get build version of the platform.
Fjage.canlocateagent
— Methodcanlocateagent(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.container
— Methodcontainer(a::Agent)
Get container in which the agent is running.
Fjage.containers
— Methodcontainers(platform::Platform)
Get list of containers running on the platform.
Fjage.containsagent
— Methodcontainsagent(container::Container, aid::AgentID)
containsagent(container::Container, name::String)
Check if an agent is running in the container.
Fjage.currenttimemillis
— Methodcurrenttimemillis(a::Agent)
Get current time in milliseconds for the agent.
Fjage.currenttimemillis
— Methodcurrenttimemillis(platform::Platform)
Get current time in milliseconds for the platform.
Fjage.delay
— Methoddelay(a::Agent, millis)
Delay the execution of the agent by millis
milliseconds.
Fjage.delay
— Methoddelay(platform::Platform, millis)
Sleep for millis ms on the platform.
Fjage.deregister
— Methodderegister(a::Agent, svc::String)
Deregister agent from providing a specied service.
Fjage.deregister
— Methodderegister(c::Container, aid::AgentID, svc::String)
Deregister agent aid
from providing service svc
.
Fjage.deregister
— Methodderegister(c::Container, aid::AgentID)
Deregister agent aid
from providing any services.
Fjage.done
— Methoddone(b::Behavior)
Check if a behavior is completed.
Fjage.init
— Methodinit(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.isblocked
— Methodisblocked(b::Behavior)
Check if a behavior is currently blocked.
Fjage.isidle
— Methodisidle(container::Container)
Check if container is idle. Unimplemented. Currently always returns true.
Fjage.isidle
— Methodisidle(platform::Platform)
Check if platform is idle. Unimplemented. Currently always returns true.
Fjage.isrunning
— Methodisrunning(container::Container)
Check if the container is running.
Fjage.isrunning
— Methodisrunning(platform::Platform)
Check if platform is running.
Fjage.logerror
— Functionlogerror()
logerror(src)
Log current exception with a stack trace.
Fjage.logerror
— Functionlogerror(f::Function)
logerror(f::Function, src)
Run function f()
and log any errors that occur.
Fjage.loglevel!
— Methodloglevel!(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!
— Methodname!(container::Container, s)
Set name of the container.
Fjage.name
— Methodname(a::Agent)
Get the name of the agent.
Fjage.name
— Methodname(container::Container)
Get name of the container.
Fjage.nanotime
— Methodnanotime(a::Agent)
Get current time in nanoseconds for the agent.
Fjage.nanotime
— Methodnanotime(platform::Platform)
Get current time in nanoseconds for the platform.
Fjage.platform
— Methodplatform(a::Agent)
Get platform on which the agent's container is running.
Fjage.platform
— Methodplatform(container::Container)
Get platform on which the container is running.
Fjage.platformsend
— Methodplatformsend(a::Agent, msg::Message)
Send a message to agents running on all containers on a platform. Currently unimplemented.
Fjage.priority
— Methodpriority(b::Behavior)
Get the priority associated with a behavior.
Fjage.processmessage
— Methodprocessmessage(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.processrequest
— Methodprocessrequest(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.ps
— Methodps(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!
— Methodqueuesize!(a::Agent, n)
Set the incoming message queue size for an agent. Currently unimplemented.
Fjage.receive
— Functionreceive(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
— Methodregister(a::Agent, svc::String)
Register agent as providing a specied service.
Fjage.register
— Methodregister(c::Container, aid::AgentID, svc::String)
Register agent aid
as providing service svc
.
Fjage.removelistener
— Methodremovelistener(container::Container, listener)
Remove message listener from container.
Fjage.request
— Functionrequest(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
— Methodreset(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
— Methodrestart(b::Behavior)
Restart a blocked behavior, previous blocked by block(b)
.
Fjage.send
— Methodsend(a::Agent, msg::Message)
Send a message from agent a
.
Fjage.send
— Methodsend(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
— Methodservices(container::Container)
Get list of services running in the container.
Fjage.setup
— Functionsetup(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.shutdown
— Methodshutdown(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.shutdown
— Methodshutdown(platform::Platform)
Stop the platform and all containers running on the platform.
Fjage.shutdown
— Methodshutdown(container::Container)
Stop a container and all agents running in it.
Fjage.start
— Methodstart(platform::Platform)
Start the platform and all containers running on the platform.
Fjage.start
— Methodstart(container::Container)
Start a container.
Fjage.startup
— Functionsetup(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.state
— Methodstate(container::Container)
Get a human-readable state of the container.
Fjage.stop
— Methodstop(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.stop
— Methodstop(b::Behavior)
Terminates a behavior.
Fjage.store
— Methodstore(a::Agent)
Return the persistent data store for agent. Currently unimplemented.
Fjage.subscribe
— Methodsubscribe(a::Agent, topic::AgentID)
Subscribe agent to specified topic.
Fjage.subscribe
— Methodsubscribe(c::Container, topic::AgentID, agent::Agent)
Subscribe agent
running in container c
to topic
.
Fjage.tickcount
— Methodtickcount(b::PoissonBehavior)
Get the number of times a PoissonBehavior
has ticked (its action()
has been called).
Fjage.tickcount
— Methodtickcount(b::TickerBehavior)
Get the number of times a TickerBehavior
has ticked (its action()
has been called).
Fjage.unsubscribe
— Methodunsubscribe(a::Agent, topic::AgentID)
Unsubscribe agent from specified topic.
Fjage.unsubscribe
— Methodunsubscribe(c::Container, agent::Agent)
Unsubscribe agent
running in container c
from all topics.
Fjage.unsubscribe
— Methodunsubscribe(c::Container, topic::AgentID, agent::Agent)
Unsubscribe agent
running in container c
from topic
.
Fjage.@agent
— MacroThe @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