Files
deer-flow/backend/packages/harness/deerflow/actor/supervision.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

76 lines
2.5 KiB
Python

"""Supervision strategies — Erlang/Akka-inspired fault tolerance."""
from __future__ import annotations
import enum
import time
from collections import deque
from collections.abc import Callable
from typing import Any
class Directive(enum.Enum):
"""What a supervisor should do when a child fails."""
resume = "resume" # ignore error, keep processing
restart = "restart" # discard state, create fresh instance
stop = "stop" # terminate the child permanently
escalate = "escalate" # propagate to grandparent
class SupervisorStrategy:
"""Base class for supervision strategies.
Args:
max_restarts: Maximum restarts allowed within *within_seconds*.
Exceeding this limit stops the child permanently.
within_seconds: Time window for restart counting.
decider: Maps exception → Directive. Default: always restart.
"""
def __init__(
self,
*,
max_restarts: int = 3,
within_seconds: float = 60.0,
decider: Callable[[Exception], Directive] | None = None,
) -> None:
self.max_restarts = max_restarts
self.within_seconds = within_seconds
self.decider = decider or (lambda _: Directive.restart)
self._restart_timestamps: dict[str, deque[float]] = {}
def decide(self, error: Exception) -> Directive:
return self.decider(error)
def record_restart(self, child_name: str) -> bool:
"""Record a restart and return True if within limits."""
now = time.monotonic()
if child_name not in self._restart_timestamps:
self._restart_timestamps[child_name] = deque()
ts = self._restart_timestamps[child_name]
# Purge old entries outside the window
cutoff = now - self.within_seconds
while ts and ts[0] < cutoff:
ts.popleft()
ts.append(now)
return len(ts) <= self.max_restarts
def apply_to_children(self, failed_child: str, all_children: list[str]) -> list[str]:
"""Return which children should be affected by the directive."""
raise NotImplementedError
class OneForOneStrategy(SupervisorStrategy):
"""Only the failed child is affected."""
def apply_to_children(self, failed_child: str, all_children: list[str]) -> list[str]:
return [failed_child]
class AllForOneStrategy(SupervisorStrategy):
"""All children are affected when any one fails."""
def apply_to_children(self, failed_child: str, all_children: list[str]) -> list[str]:
return list(all_children)