operand/agency: an actor-model runtime for Python agent systems
A fast and minimal framework for building agentic systems
At a glance
- What is it?
- Agency treats every participant in an agent application, including humans and plain software interfaces, as an Agent that exposes discoverable actions over a shared Space. The design is small and explicit, but the release history and the demo's experimental status matter more than the feature list.
- Who is it for?
- Adopt Agency if you want agent-to-agent messaging in Python and you are willing to treat the library as a foundation you extend rather than a finished product. Do not adopt it if you need a maintained release cadence: the newest release is v1.6.3 from 2024-04-24, while the last push to main is 2026-06-10, so check the commit history between those two dates before committing.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 97 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
What Agency is for, and who it leaves out
Agency is a Python library for building agent-integrated systems, and its own summary frames the goal narrowly: a minimal foundation to experiment with and build upon. Every entity in the system is an Agent instance. That includes AI-driven agents, software interfaces, and human users, which is the part that distinguishes it from a wrapper around a chat completion call. A human is not a special case bolted on at the edge; a human is addressable the same way a calculator is.
The audience is developers who already know what they want their architecture to look like and need plumbing for it. The README says the library enables connecting agents with traditional software systems in a flexible and scalable way, allowing any architecture you need. That is a claim about primitives, not about outcomes. If you want a prebuilt agent loop with planning, memory, and tool selection decided for you, this repository does not appear to provide one. The demo's OpenAI and HuggingFace agents are examples, and the README describes the demo as an experimental development environment, not as a supported product.
Agents, actions, and the Space that connects them
The mechanism is the actor model. An Agent subclass declares methods decorated with @action, and other agents invoke those methods by sending a message. The README's example is a CalculatorAgent with an add action, called through a dict shaped like {'to': 'CalcAgent', 'action': {'name': 'add', 'args': {'a': 1, 'b': 2}}}. The message names a recipient, an action, and a dictionary of arguments. Nothing in the documented surface suggests a return channel beyond the action's return value.
Agents cannot talk to each other until they share a Space. Two implementations ship: LocalSpace for agents in the same application, and AMQPSpace for agents across a network through an AMQP server such as RabbitMQ. That split is the whole topology story. Local in-process messaging and brokered network messaging are the same abstraction with a different transport, which is a clean boundary and an honest one.
Callbacks give you hooks at defined points. before_action and after_action fire around an attempted action, with after_action receiving the return value and any error. after_add fires once the agent is in a space and may begin communicating; before_remove fires before it leaves. For observability, the README also lists action and lifecycle callbacks, access policies and permission callbacks, and detailed logging. The permission layer is where the design gets interesting, and it is covered next.
Access policies as a safety primitive, not a sandbox
An action can carry an access policy. ACCESS_PERMITTED lets the action run at any time. ACCESS_REQUESTED requires review before the action executes. The README presents this as a way to control access for safety, and the demo includes an agent with operating system access, which is exactly the case where you would want a policy in front of a call.
Be precise about what this is. A policy attached to an action is an approval gate inside your process. It is not a process boundary, not a filesystem jail, and not a capability system enforced by the operating system. If an action can run shell commands, ACCESS_REQUESTED changes when the command runs and who signs off, not what the command is able to touch. The README does not describe a sandbox, so do not read one into it.
The practical consequence is that the access policy belongs alongside process isolation, not instead of it. The demo's Docker configuration is described as being for reference and development, which is a hint about intended use rather than a security guarantee.
Getting it running: install, define, connect
Installation is a single command, either pip install agency or poetry add agency. There is no documented configuration step before that.
A minimal program has three parts. Define an agent class with at least one @action method. Create a space with space = LocalSpace(). Add instances with space.add(CalculatorAgent, "CalcAgent") and space.add(MyAgent, "MyAgent"). After the second add call, the README states the agents can communicate. The name passed to add is the address other agents use in the 'to' field, so the string in space.add and the string in the message must match.
For a networked deployment you would use AMQPSpace in place of LocalSpace and point it at an AMQP server. The README names RabbitMQ as an example and does not document connection parameters, so treat broker configuration as something to confirm against the source or the help site at createwith.agency rather than something the README settles. The README also states that multiprocessing and multithreading are supported for concurrency, without describing how a given space chooses between them.
Where the release history undercuts the pitch
The feature list reads as a stable library. The release list does not. The three most recent releases are v1.6.3 on 2024-04-24, v1.6.1 on 2023-09-26, and v1.6.0 on 2023-09-26. The last push to the default branch is 2026-06-10. That gap is the single most important fact in this review, and it cuts both ways.
A long gap between the newest release and the newest commit means the project is not dormant, but it also means the released artifact is not the current state of the code. If you install from PyPI you get whatever v1.6.3 contains. If you install from main you get two years of unreleased changes with no version number attached. Neither is wrong, but they are different decisions, and the README does not tell you which one the documentation describes.
There is also a version-number oddity: v1.6.1 and v1.6.0 share the same date, and there is no v1.6.2 in the list. That is not evidence of a problem on its own, but it is the kind of detail worth checking in the changelog before you pin a version.
The comparison that matters: Agency versus a direct tool-calling loop
The obvious alternative is not another agent framework. It is skipping the framework and calling the model's tool API directly, dispatching to functions you wrote. That approach has fewer moving parts and no abstraction to learn.
The difference is in what you get when the system grows past one model and one set of tools. A direct loop assumes a single caller. Agency assumes many participants that discover and invoke each other's actions at run time, including participants that are not models. The moment you want a human operator to be addressable by name, or a plain software service to be callable the same way an LLM agent is, the direct loop needs you to invent addressing, dispatch, and permission checks. Agency already has them as Agent, Space, and access policy.
The trade is real in the other direction. A direct loop has no message schema to get wrong and no space to configure. If your application is one model with a fixed tool list, Agency's actor layer is overhead you will pay for and not use. The README's own framing supports this: the library is a foundation to build upon, and foundations are only worth their cost when you are actually building on them.
Maintenance, licence, and what to check before adopting
The licence is MIT, which permits commercial use, modification, and redistribution provided the copyright notice and permission notice are included. That is the standard permissive arrangement. This is not legal advice; read the LICENSE file in the repository and get your own review if the distinction matters to you.
Maintenance cost is the harder question. Because the library is minimal by design, most of your ongoing work will be in code you wrote on top of it rather than in the library itself. The risk is the opposite of a heavy framework: you are not waiting on upstream to fix your bug, but you are also not getting upstream fixes on a predictable schedule. The release dates suggest a project that has slowed down, and the commit date suggests it has not stopped. Verify by reading the commit log between 2024-04-24 and 2026-06-10 to see whether those changes are fixes, features, or experiments.
Before adopting, check three things. Confirm whether the documentation at createwith.agency describes the released version or main. Confirm that AMQPSpace works against your broker version, since compatibility is not documented. And confirm that ACCESS_REQUESTED produces the review flow you expect, because the README shows the decorator and not the review mechanism behind it.
Editorial conclusion
Adopt Agency if you want agent-to-agent messaging in Python and you are willing to treat the library as a foundation you extend rather than a finished product. Do not adopt it if you need a maintained release cadence: the newest release is v1.6.3 from 2024-04-24, while the last push to main is 2026-06-10, so check the commit history between those two dates before committing. Verify first that the access policy model matches your threat model, and that AMQPSpace behaves as you expect against your RabbitMQ version, since the README does not document broker compatibility.
Community notes