Skip to main content

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
tip

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.

info

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.

With the Prolog incarnation

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.