Quick Start

Creating your first dummy agent

It’s time for us to build our first SPADE agent. We’ll assume that we have a registered user in an XMPP server with a jid and a password. The jid contains the agent’s name (before the @) and the DNS or IP of the XMPP server (after the @). But remember! You should have your own jid and password in an XMPP server running in your own computer or in the Internet. In this example we will assume that our jid is your_jid@your_xmpp_server and the password is your_password.

Hint

To create a new XMPP account you can follow the steps of https://xmpp.org/getting-started/

Hint

To install an XMPP server visit https://xmpp.org/software/servers.html (we recommend Prosody IM)

A basic SPADE agent is really a python script that imports the spade module and that uses the constructs defined therein. For starters, fire up you favorite Python editor and create a file called dummyagent.py.

Warning

Remember to change the example’s jids and passwords by your own accounts. These accounts do not exist and are only for demonstration purposes.

To create an agent in a project you just need to:

import spade

class DummyAgent(spade.agent.Agent):
    async def setup(self):
        print("Hello World! I'm agent {}".format(str(self.jid)))

async def main():
    dummy = DummyAgent("your_jid@your_xmpp_server", "your_password")
    await dummy.start()

if __name__ == "__main__":
    spade.run(main())

This agent is only printing on screen a message during its setup and stopping. If you run this script you get the following output:

$ python dummyagent.py
Hello World! I'm agent your_jid@your_xmpp_server
$

And that’s it! We have built our first SPADE Agent in 6 lines of code. Easy, isn’t it? Of course, this is a very very dumb agent that does nothing, but it serves well as a starting point to understand the logics behind SPADE.

Note

A SPADE agent is an asyncronous agent. That means that all the code to run an agent must be executed in an asyncronous loop. This is done by the spade.run() function. This function receives a coroutine as a parameter and runs it in an async loop. In our example, the main() coroutine is the one that is run in the loop.

An agent with a behaviour

Let’s build a more functional agent, one that uses an actual behaviour to perform a task. As we stated earlier, the real programming of the SPADE agents is done mostly in the behaviours. Let’s see how.

Let’s create a cyclic behaviour that performs a task. In this case, a simple counter. We can modify our existing dummyagent.py script.

Warning

Remember to change the example’s jids and passwords by your own accounts. These accounts do not exist and are only for demonstration purposes.

Example:

import asyncio
import spade
from spade import wait_until_finished
from spade.agent import Agent
from spade.behaviour import CyclicBehaviour

class DummyAgent(Agent):
    class MyBehav(CyclicBehaviour):
        async def on_start(self):
            print("Starting behaviour . . .")
            self.counter = 0

        async def run(self):
            print("Counter: {}".format(self.counter))
            self.counter += 1
            await asyncio.sleep(1)

    async def setup(self):
        print("Agent starting . . .")
        b = self.MyBehav()
        self.add_behaviour(b)

async def main():
    dummy = DummyAgent("your_jid@your_xmpp_server", "your_password")
    await dummy.start()
    print("DummyAgent started. Check its console to see the output.")

    print("Wait until user interrupts with ctrl+C")
    await wait_until_finished(dummy)

if __name__ == "__main__":
    spade.run(main())

As you can see, we have defined a custom behaviour called MyBehav that inherits from the spade.behaviour.CyclicBehaviour class, the default class for all behaviours. This class represents a cyclic behaviour with no specific period, that is, a loop-like behaviour.

You can see that there is a coroutine called on_start() in the behaviour. This method is similar to the setup() method of the agent class but it is run in the async loop. It is executed just before the main iteration of the behaviour begins and it is used for initialization code. In this case, we print a line and initialize the variable for the counter. There is also an on_end() coroutine that is executed when a behaviour is done or killed.

Also, there is the run() method, which is very important. In all behaviours, this is the method in which the core of the programming is done, because this method is called on each iteration of the behaviour loop. It acts as the body of the loop, sort of. In our example, the run() method prints the current value of the counter, increases it and then waits for a second (to iterate again).

Warning

Note that the run() method is an async coroutine!. This is very important since SPADE is an async library based on python’s asyncio. That’s why we can call async methods inside the run() method, like the await asyncio.sleep(1), which sleeps during one second without blocking the event loop.

Now look at the setup() coroutine of the agent. There, we make an instance of MyBehav and add it to the current agent by means of the add_behaviour() method. The first parameter of this method is the behaviour we want to add, and there is also a second optional parameter which is the template associated to that behaviour, but we will talk later about templates.

Let’s test our new agent:

$ python dummyagent.py
Agent starting . . .
DummyAgent started. Check its console to see the output.
Wait until user interrupts with ctrl+C
Starting behaviour . . .
Counter: 0
Counter: 1
Counter: 2
Counter: 3
Counter: 4
Counter: 5
Counter: 6
Counter: 7

… and so on. As we have not set any end condition, this agent would go on counting forever until we press ctrl+C.

Finishing a behaviour

If you want to finish a behaviour you can kill it by using the self.kill(exit_code) method. This method marks the behaviour to be killed at the next loop iteration and stores the exit_code to be queried later.

An example of how to kill a behaviour:

import asyncio
import spade
from spade.agent import Agent
from spade.behaviour import CyclicBehaviour

class DummyAgent(Agent):
    class MyBehav(CyclicBehaviour):
        async def on_start(self):
            print("Starting behaviour . . .")
            self.counter = 0

        async def run(self):
            print("Counter: {}".format(self.counter))
            self.counter += 1
            if self.counter > 3:
                self.kill(exit_code=10)
                return
            await asyncio.sleep(1)

        async def on_end(self):
            print("Behaviour finished with exit code {}.".format(self.exit_code))

    async def setup(self):
        print("Agent starting . . .")
        self.my_behav = self.MyBehav()
        self.add_behaviour(self.my_behav)

async def main():
    dummy = DummyAgent("your_jid@your_xmpp_server", "your_password")
    await dummy.start()

    # wait until user interrupts with ctrl+C
    while not dummy.my_behav.is_killed():
        try:
            await asyncio.sleep(1)
        except KeyboardInterrupt:
            break

    assert dummy.my_behav.exit_code == 10

    await dummy.stop()


if __name__ == "__main__":
        spade.run(main())

And the output of this example would be:

$ python killbehav.py
Agent starting . . .
Starting behaviour . . .
Counter: 0
Counter: 1
Counter: 2
Counter: 3
Behaviour finished with exit code 10.

Note

An exit code may be of any type you need: int, dict, string, exception, etc.

Warning

Remember that killing a behaviour does not cancel its current run loop, if you need to finish the current iteration you’ll have to call return.

Hint

If a exception occurs inside an on_start, run or on_end coroutines, the behaviour will be automatically killed and the exception will be stored as its exit_code.

Finishing SPADE

A SPADE script will be running until all agents are stopped. If you want to stop all agents and finish the script you may send a SIGINT (ctrl+C) signal. This signal will stop all agents.

Warning

The quit_spade() method has been deprecated since the current version of SPADE (3.3).

Creating an agent from within another agent

There is a common use case where you may need to create an agent from within another agent, that is, from within another agent’s behaviour. This is a common case where `start must be called with an await statement in order to work properly. Example:

import spade
from spade.agent import Agent
from spade.behaviour import OneShotBehaviour


class AgentExample(Agent):
    async def setup(self):
        print(f"{self.jid} created.")


class CreateBehav(OneShotBehaviour):
    async def run(self):
        agent2 = AgentExample("agent2_example@your_xmpp_server", "fake_password")
        await agent2.start(auto_register=True)

async def main():
    agent1 = AgentExample("agent1_example@your_xmpp_server", "fake_password")
    behav = CreateBehav()
    agent1.add_behaviour(behav)
    await agent1.start(auto_register=True)

    # wait until the behaviour is finished to quit spade.
    await behav.join()

if __name__ == "__main__":
    spade.run(main())