Files
deer-flow/backend/packages/harness/deerflow/actor/actor.py
T
greatmengqi 3e17417122 feat: asyncio-native Actor framework with supervision, middleware, and pluggable mailbox
Lightweight actor library built on asyncio primitives (~800 lines):

- Actor base class with lifecycle hooks (on_started/on_stopped/on_restart)
- ActorRef with tell (fire-and-forget) and ask (request-response)
- Supervision: OneForOne/AllForOne strategies with restart limits
- Middleware pipeline for cross-cutting concerns
- Pluggable Mailbox interface (MemoryMailbox default, RedisMailbox optional)
- ReplyRegistry + ReplyChannel: ask() works across any mailbox backend
- System-level thread pool for blocking I/O (run_in_executor)
- Dead letter handling, poison message quarantine, parallel shutdown
- 22 tests + benchmark suite
2026-03-30 23:50:54 +08:00

110 lines
3.2 KiB
Python

"""Actor base class and per-actor context."""
from __future__ import annotations
from collections.abc import Callable
from typing import TYPE_CHECKING, Any, Generic, TypeVar
from .supervision import OneForOneStrategy, SupervisorStrategy
if TYPE_CHECKING:
from .ref import ActorRef
# Message type variable — use Actor[MyMsg] for typed actors
M = TypeVar("M")
R = TypeVar("R")
class ActorContext:
"""Per-actor runtime context, injected before ``on_started``.
Provides access to the actor's identity, parent, children,
and the ability to spawn child actors.
"""
__slots__ = ("_cell",)
def __init__(self, cell: Any) -> None:
self._cell = cell
@property
def self_ref(self) -> ActorRef:
return self._cell.ref
@property
def parent(self) -> ActorRef | None:
p = self._cell.parent
return p.ref if p is not None else None
@property
def children(self) -> dict[str, ActorRef]:
return {name: c.ref for name, c in self._cell.children.items()}
@property
def system(self) -> Any:
return self._cell.system
async def spawn(
self,
actor_cls: type[Actor],
name: str,
*,
mailbox_size: int = 256,
middlewares: list | None = None,
) -> ActorRef:
"""Spawn a child actor supervised by this actor."""
return await self._cell.spawn_child(actor_cls, name, mailbox_size=mailbox_size, middlewares=middlewares)
async def run_in_executor(self, fn: Callable[..., Any], *args: Any) -> Any:
"""Run a blocking function in the system's thread pool.
Usage::
result = await self.context.run_in_executor(requests.get, url)
"""
import asyncio
executor = self._cell.system._executor
return await asyncio.get_running_loop().run_in_executor(executor, fn, *args)
class Actor(Generic[M]):
"""Base class for all actors.
Type parameter ``M`` constrains the message type::
class Greeter(Actor[str]):
async def on_receive(self, message: str) -> str:
return f"Hello, {message}!"
class Calculator(Actor[int | tuple[str, int, int]]):
async def on_receive(self, message: int | tuple[str, int, int]) -> int:
...
Unparameterized ``Actor`` accepts ``Any`` (backward-compatible).
"""
context: ActorContext
async def on_receive(self, message: M) -> Any:
"""Handle an incoming message.
Return value is sent back as reply for ``ask`` calls.
For ``tell`` calls, the return value is discarded.
"""
async def on_started(self) -> None:
"""Called after creation, before receiving messages."""
async def on_stopped(self) -> None:
"""Called on graceful shutdown. Release resources here."""
async def on_restart(self, error: Exception) -> None:
"""Called on the *new* instance before resuming after a crash."""
def supervisor_strategy(self) -> SupervisorStrategy:
"""Override to customize how this actor supervises its children.
Default: OneForOne, up to 3 restarts per 60 seconds, always restart.
"""
return OneForOneStrategy()