v1.6.0
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):