Connect agents to an external environment
JaKtA has no environment class: the external environment is your Kotlin model of the world. Agents are connected to it in two directions:
- perceiving: the external environment publishes perceptions on the node, and each agent turns them into beliefs;
- acting: a skill exposes the operations agents can perform on it.
The snippets come from the thermostat example,
walked through in the thermostat tutorial.
Define a perception
Any class implementing AgentEvent.External.Perception (from it.unibo.jakta.event):
data class RoomReading(val temperature: Double, val target: Double, val mode: Mode) : Perception
Keep perceptions separate from the belief type: each agent decides what a perception means to it.
Publish it
Anything holding the node can publish, e.g. the external environment's own loop:
node.publishEvent(RoomReading(state.temperature, state.target, state.mode))
To get the node from outside the DSL, build it on its own and add it to the MAS with withNodes:
val home = node(NodeBuilders.baseNode()) {
context(Hvac(room)) {
withAgents(thermostat())
}
}
launch { room.simulate(home, stepTime) }
mas(NodeBuilders.baseNode()) { withNodes(home) }.run(CoroutineNodeRunner(SharedMemoryNetwork()))
Inside the DSL, node is in scope in node { } blocks, agent builders and plan bodies.
Deliver it to some agents only
publishEvent takes an optional filter on agent bodies:
node.publishEvent(reading) { body -> body === node.agents[agentId] }
This is most useful with custom bodies, e.g. to deliver a sensor reading only to the robot that made it.
Perceptions only reach the agents of the node that publishes them; use messages to reach other nodes.
Turn perceptions into beliefs
Return an AgentUpdate.Belief(additions, removals) from handlesPerceptionEvents, or null to ignore a perception:
handlesPerceptionEvents { perception ->
when (perception) {
is RoomReading -> {
val belief = RoomBelief(perception.temperature, perception.target, perception.mode)
if (belief in beliefs) null else AgentUpdate.Belief(setOf(belief), beliefs.toSet())
}
else -> null
}
}
Replacing the old belief with the new one keeps the belief base up to date, and ignoring a reading equal to the current belief generates events only when something changed.
React to them
A belief addition triggers the plans whose trigger accepts the new belief and whose guard holds:
adding.belief {
takeIf { it.temperature < it.target - TOLERANCE }
} onlyWhen {
context.takeIf { it.mode != Mode.HEATING }
} triggers {
hvac.heat()
}
Act through a skill
A skill is an ordinary object wrapping the operations on the external environment:
class Hvac(private val room: Room) {
fun heat() = room.switch(Mode.HEATING)
fun off() = room.switch(Mode.OFF)
}
The node provides it to its agents with context(Hvac(room)) { ... }, and the agent states that it needs it as a
context parameter, context(hvac: Hvac) fun thermostat() = agent { ... }, so that its plans can call hvac.heat().
See Skills.
The pattern is the same with Prolog beliefs: the perception handler builds Prolog facts and removes the stale ones. See Turn perceptions into Prolog facts.