Skip to content

v1.6.0

Compare
Choose a tag to compare
@operand operand released this 26 Sep 02:36
· 29 commits to main since this release
de32dcb

I'm proud to announce Agency v1.6.0. Technically, it represents a big step forward and I believe is a milestone in terms of clarity for the project.

It took some iterating and lots of thoughtful feedback, but I think the learnings from the last few versions have come together to create a clearer picture for the API and architecture. I believe there is a good foundation now to focus on the next steps: js client, code execution, and multimodal support.

Special thanks to @wwj718 and @hidaris for their reviews and feedback that helped shape this release!

As with any major release, please do not hesitate to open issues or discussions if you have any questions or feedback. 🙇‍♂️

Summary of Changes

Space types now support mixed threading/multiprocessing for agents

Version 1.4 introduced multiprocessing but did not allow threading and multiprocessing based agents to be used together. Additionally the API prevented some use cases that were previously possible, due to not allowing direct access to agents from the main thread.

In 1.6.0, all agents are now add'ed using multiprocessing based subprocesses by default. Agents added this way may not be directly accessed, similar to 1.4.

With 1.6.0 you now have the option to run agents in the "foreground" using Space.add_foreground. When adding an agent this way, an instance of the agent is returned in the current thread. For example:

my_agent = my_space.add_foreground(MyAgent, "my_agent")

The tradeoff for adding an agent in the foreground is performance. Due to the python GIL, agents running in the foreground may not be fully parallel with other agents, and may block the main thread.

It's recommended to use Space.add_foreground only when needed.

MultiprocessSpace and ThreadSpace classes replaced with LocalSpace

Since all Space types now support mixed threading/multiprocessing, LocalSpace combines these older types into a single class for in-application communication.

Context manager support and Space.destroy method added

These two additions allow for improved resource management. Creating and cleaning up a Space can now be done using the context manager syntax:

with AMQPSpace() as space:
    space.add(Host, "Host")
    ...

This form will automatically clean up Space related resources upon exit of the with block.

Direct control of cleanup is also possible using the Space.destroy method.

space = AMQPSpace()
space.add(Host, "Host")
...
space.destroy()

Agent.respond_with and Agent.raise_with methods added

These methods are intended for use with Agent.request and the response callbacks: Agent.handle_action_value and Agent.handle_action_error.

Automatic response behavior has changed from v1.5. Actions will no longer automatically return their return value to the sender.

To explicitly return a value to the sender associated with the current message, use Agent.respond_with. For example:

@action
def ping(self):
    self.respond_with("pong")

The above will invoke the handle_action_value callback on the sender with the value "pong", or return the value "pong" directly when used with the request method.

Similarly you may return an error with:

@action
def ping(self):
    self.raise_with(Exception("oops"))

Actions that raise exceptions will automatically call the raise_with method.

Note that respond_with and raise_with may be called multiple times in a single action. Each call will invoke the appropriate callback on the sender.

When using request the first call to respond_with will return the value to the calling thread. Subsequent responses will invoke the handle_action_value callback.

Performance Improvements

All busy waiting has been replaced with event based processing. The test suite completes about 80% faster than in 1.5 and CPU overhead should now be minimized.

Agent.__init__ signature updated

The Agent constructor has been reverted to its older signature which does not include a Queue parameter. Make sure to update your classes.

def __init__(self, id: str, receive_own_broadcasts: bool = True):