aiomas – A library for multi-agent systems and RPC based on asyncio

aiomas is a new, asyncio based library that helps you with creating distributed multi-agent systems. It allows different clocks (real-time, coupled to simulations, …) and different codecs (MsgPack, JSON) to be used. You can also just use the lower level abstractions for RPC or message based request-reply channels.

I started the development in December 2014 and OFFIS, the institute I’m working at, now kindly allowed me to release it as OSS (licensed under the MIT license).

What? But why?

Multi-agent systems are a thing in many scientific institutes, including OFFIS. In particular, my colleagues and I want to create a multi-agent system for managing dynamic virtual power plants in our current project Dynamic VPP (German link).

A very common tool (at least in science) for writing multi-agent systems is JADE. However, most people I know don’t really like working with JADE (yes, that’s an euphemism).

I also didn’t find a modern, well documented and easy-to-use Python library for the job. So I asked my self: “How hard would it be to create one on my own?”

Being one of the maintainers of the discrete-event simulation (DES) library SimPy, I already have some experience with event-based and asynchronous programming. Multi-agent systems are relatively similar to DES, so I thought I should give it a try.

I also wanted to get to know asyncio. I’ve never worked with it before but its concepts looked very similar to the ones we’ve been using in SimPy for years, so building a library for multi-agent systems on top of asyncio was a perfect project to learn something new.

How do I use it?

The basic idea behind aiomas is that agents should be plain Python classes. Methods that perform asynchronous operations (like sending messages to other agents) should be coroutines. Agents should be able to call methods that other agents expose. It should also be easy to distribute agents over multiple CPU cores or machines. But you, as the user, shouldn’t bothered with setting up sockets or implementing transports, protocols and other low-level stuff.

aiomas is still in a relatively early stage of development. What’s already there seems to work quite nicely, though.

Here is a simple example how it currently looks like:

>>> import asyncio
>>> import aiomas
>>>
>>> class TestAgent(aiomas.Agent):
...     def __init__(self, container, name):
...         super().__init__(container, name)
...         print('Ohai, I am %s' % name)
...
...     @asyncio.coroutine
...     def run(self, addr):
...         """The agent's "main" function."""
...         remote_agent = yield from self.container.connect(addr)
...         ret = yield from remote_agent.service(42)
...         print('%s got %s from %s' % (self.name, ret, addr))
...
...     @aiomas.expose
...     def service(self, value):
...         """Exposed function that can be called by remote agents."""
...         return value
>>>
>>> # A Container is the home for a number of agents.
>>> c = aiomas.Container(('localhost', 5555))
>>> agents = [c.spawn(TestAgent) for i in range(2)]
Ohai, I am agent://localhost:5555/0
Ohai, I am agent://localhost:5555/1
>>>
>>> # Run agent 0 and let it call a method from agent 1
>>> loop = asyncio.get_event_loop()
>>> loop.run_until_complete(agents[0].run('agent://localhost:5555/1'))
agent://localhost:5555/0 got 42 from agent://localhost:5555/1
>>>
>>> c.shutdown()

What’s the list of features?

aiomas just puts three layers of abstraction around raw TCP / unix domain sockets provided by asyncio:

Request-reply channel:
The channel layer is the basis for the rpc layer. It sends JSON or MsgPack encoded byte strings over TCP or unix domain sockets. It also maps replies (of success or failure) to their corresponding request.
RPC:

The rpc layer implements remote procedure calls which let you call methods on remote objects nearly as if they were normal objects:

Instead of ret = obj.meth(arg) you write ret = yield from obj.meth(arg).

Agents and agent containers:

The top-layer provides a simple base class for your own agents. All agents live in a container.

Containers take care of creating agents and performing the communication between them.

The container provides a clock for the agents. This clock can either be synchronized with the real (wall-clock) time or be set by an external process (e.g., other simulators).

Although you usually want to use the agent layer, it is perfectly okay to only use the rpc or channel layer.

What’s comming next?

Some ideas for future releases:

  • SSL/TLS support for TCP sockets
  • Optional automatic re-connect after connection loss
  • Helper for binding a socket to a random free port
  • Implement a queue based transport that agents within a single container can use instead of a TCP connection.

Installation

aiomas requires Python >= 3.4, msgpack-python and arrow (it may also run on Python 3.3 with the asyncio package, but this is untested).

Install aiomas via pip by running:

$ pip install aiomas

The development happens on bitbucket. There’s no mailing list or IRC channel yet, so just create an issue for your feedback, send me an email, or leave me a tweet.