Asyncio-native Actor framework for Python agent systems
Project description
everything-is-an-actor
Asyncio-native Actor framework for Python agent systems — supervision trees, event streaming, and pluggable mailbox.
Inspired by Erlang/Akka. Built for AI agent orchestration.
Documentation · Getting Started · Agent Layer · API Reference
Install
pip install everything-is-an-actor
# With Redis mailbox support
pip install everything-is-an-actor[redis]
Agent Quick Start
import asyncio
from actor_for_agents.agents import AgentSystem, AgentActor, Task
class SummaryAgent(AgentActor[str, str]):
async def execute(self, input: str) -> str:
await self.emit_progress("summarizing...")
return f"Summary: {input[:80]}..."
async def main():
system = AgentSystem("app")
# Stream every event from the entire agent tree
async for event in system.run(SummaryAgent, "Long document..."):
print(event.type, event.agent_path, event.data)
asyncio.run(main())
Agent Layer
AgentActor — implement execute(), not on_receive()
class ResearchAgent(AgentActor[str, list]):
async def on_started(self):
self.client = aiohttp.ClientSession()
async def execute(self, query: str) -> list:
await self.emit_progress("searching...")
results = await self.client.get(f"/search?q={query}")
return results
async def on_stopped(self):
await self.client.close()
Streaming output via yield
Each yield inside execute() emits a task_chunk event immediately — ideal for LLM tokens or file chunks.
class LLMAgent(AgentActor[str, list]):
async def execute(self, prompt: str):
async for token in openai.stream(prompt):
yield token # → task_chunk event
Orchestration
Six concurrency primitives for composing child agents — all ephemeral actors are cleaned up automatically.
class OrchestratorAgent(AgentActor[str, Any]):
async def execute(self, query: str):
# ask — single child, one result
r = await self.context.ask(SearchAgent, Task(input=query))
result = r.output
# sequence — fan-out, results in order, fail-fast
a, b = await self.context.sequence([
(SearchAgent, Task(input=query)),
(FactCheckAgent, Task(input=query)),
])
combined = {"search": a.output, "facts": b.output}
# traverse — map a list through one agent
summaries = await self.context.traverse(["doc1", "doc2", "doc3"], SummaryAgent)
texts = [r.output for r in summaries]
# race — first wins, cancel the rest
fastest = await self.context.race([
(FastAgent, Task(input=query)),
(SlowAgent, Task(input=query)),
])
winner = fastest.output
# zip — two tasks, typed pair
search, facts = await self.context.zip(
(SearchAgent, Task(input=query)),
(FactCheckAgent, Task(input=query)),
)
# stream — forward child chunks upstream
async for item in self.context.stream(LLMAgent, Task(input=query)):
match item:
case StreamEvent(event=e) if e.type == "task_chunk":
yield e.data
case StreamResult():
pass
Event streaming
system = AgentSystem("app")
# Stream all events from a fresh agent run
async for event in system.run(OrchestratorAgent, user_query):
if event.type == "task_progress":
print(event.data)
# Or stream from an existing ref
ref = await system.spawn(SummaryAgent, "summarizer")
async for item in ref.ask_stream(Task(input="document...")):
match item:
case StreamEvent(event=e):
print(e.type, e.data)
case StreamResult(result=r):
print(r.output)
Span linking
Every TaskEvent carries parent_task_id and parent_agent_path — reconstruct the full call tree from a flat event stream (OpenTelemetry-style spans).
Core Actor API
| Class | Description |
|---|---|
Actor |
Base class. Override on_receive, on_started, on_stopped, on_restart |
ActorRef |
Lightweight handle. tell(msg) / ask(msg) / ask_stream(task) |
ActorSystem |
Container. spawn(cls, name) / shutdown() |
AgentSystem |
Drop-in replacement with event streaming. run(cls, input) / abort(run_id) |
Mailbox |
Interface. MemoryMailbox (default) or RedisMailbox |
Middleware |
Interceptor chain for all lifecycle events |
OneForOneStrategy |
Restart only the failing child |
AllForOneStrategy |
Restart all siblings when one fails |
Supervision
class ParentActor(Actor):
def supervisor_strategy(self):
return OneForOneStrategy(max_restarts=3, within_seconds=60)
async def on_started(self):
self.child = await self.context.spawn(WorkerActor, "worker")
Directives: resume | restart | stop | escalate
Middleware
class LogMiddleware(Middleware):
async def on_receive(self, ctx, message, next_fn):
print(f"[{ctx.recipient.path}] ← {message}")
result = await next_fn(ctx, message)
print(f"[{ctx.recipient.path}] → {result}")
return result
ref = await system.spawn(MyActor, "worker", middlewares=[LogMiddleware()])
Redis Mailbox
from actor_for_agents.plugins.redis import RedisMailbox
ref = await system.spawn(
MyActor, "worker",
mailbox=RedisMailbox(pool, "actor:inbox:worker", maxlen=1000),
)
Benchmarks
Apple M-series, Python 3.12, asyncio:
Actor core
| Metric | Value |
|---|---|
tell throughput |
945K msg/s |
ask throughput |
29K msg/s |
ask latency p50 |
32 µs |
ask latency p99 |
46 µs |
| 100-hop actor chain | 2.0 ms, 20 µs/hop |
| 1000 actors × 100 msgs | 879K msg/s, 0 loss |
| Middleware overhead | +0% (~1 middleware) |
| Spawn 5000 actors | 27 ms |
Agent layer
| Metric | Value |
|---|---|
AgentActor ask throughput |
27K tasks/s |
AgentActor ask latency p50 |
36 µs |
AgentActor ask latency p99 |
50 µs |
sequence(50) fan-out |
32K child tasks/s |
traverse(100) map |
28K items/s |
ask_stream chunk throughput |
227K chunks/s |
AgentSystem.run() latency p50 |
0.2 ms (spawn+run+stream) |
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file everything_is_an_actor-0.1.6.tar.gz.
File metadata
- Download URL: everything_is_an_actor-0.1.6.tar.gz
- Upload date:
- Size: 61.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
75c5b63992f68e5f994ab84af754e936ed4e7a52918230eb737d558cb8e80c3e
|
|
| MD5 |
29503e3efa2bd080c46ff35e3df6bd87
|
|
| BLAKE2b-256 |
65b483e5f091880b56d86beefab050ba8bc9b4fe273a74f25e81f8b0627b2006
|
Provenance
The following attestation bundles were made for everything_is_an_actor-0.1.6.tar.gz:
Publisher:
release.yml on greatmengqi/everything-is-an-actor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
everything_is_an_actor-0.1.6.tar.gz -
Subject digest:
75c5b63992f68e5f994ab84af754e936ed4e7a52918230eb737d558cb8e80c3e - Sigstore transparency entry: 1205992837
- Sigstore integration time:
-
Permalink:
greatmengqi/everything-is-an-actor@7fa70a85ec3bb6bdc13c07c0deca2e4c3e1b205d -
Branch / Tag:
refs/tags/v0.1.8 - Owner: https://github.com/greatmengqi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7fa70a85ec3bb6bdc13c07c0deca2e4c3e1b205d -
Trigger Event:
push
-
Statement type:
File details
Details for the file everything_is_an_actor-0.1.6-py3-none-any.whl.
File metadata
- Download URL: everything_is_an_actor-0.1.6-py3-none-any.whl
- Upload date:
- Size: 33.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0bfc23d52233ef970166e3714dde87ddeb94d857b7995c968a3157a319f9a313
|
|
| MD5 |
1cd99915eccfd032ae631f991d9e7490
|
|
| BLAKE2b-256 |
cd54b8891f269dad76fdf547a404d9121574019fc4d8d8ccb8ca6a83c596eb6d
|
Provenance
The following attestation bundles were made for everything_is_an_actor-0.1.6-py3-none-any.whl:
Publisher:
release.yml on greatmengqi/everything-is-an-actor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
everything_is_an_actor-0.1.6-py3-none-any.whl -
Subject digest:
0bfc23d52233ef970166e3714dde87ddeb94d857b7995c968a3157a319f9a313 - Sigstore transparency entry: 1205992838
- Sigstore integration time:
-
Permalink:
greatmengqi/everything-is-an-actor@7fa70a85ec3bb6bdc13c07c0deca2e4c3e1b205d -
Branch / Tag:
refs/tags/v0.1.8 - Owner: https://github.com/greatmengqi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7fa70a85ec3bb6bdc13c07c0deca2e4c3e1b205d -
Trigger Event:
push
-
Statement type: