simbiot-harness/venv/lib/python3.12/site-packages/strands/models/routing/fallback_strategy.py
Simbiot Harness b60439152b Simbiot Harness v0.1: тактовая лестница на Strands + CQRS/ES-спина
- harness/es.py: append-only event store + проекции (образец — кооп-портал)
- harness/levels.py: уровни 1с/1м/1ч/1д, контекст ∝ масштабу
- harness/scheduler.py: уровневые потоки, идемпотентные тики, absorbed-стабы
- harness/isolated_runner.py + _llm_worker.py: LLM-тик в сабпроцессе
  (глобальный asyncio-рантайм Strands не переживает 2+ create_harness)
- cli.py: init/task/tasks/status/events + requeue (написан самим минутным агентом)
- 9router: отдельный ключ sk-simbiot, модель auto (glm-5.3 первым)
2026-09-29 15:28:50 +00:00

40 lines
1.9 KiB
Python

"""The default routing strategy: ordered failover that prefers the candidates failing least."""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from .strategy import RoutingContext, _attempts_since_success
if TYPE_CHECKING:
from .router import RoutingCandidate
class FallbackStrategy:
"""Picks the healthiest candidate not yet tried since the last success.
Candidates already tried since the last success are excluded, then the fewest recorded failures
wins, ties going to the earlier declaration. So an invocation with no failures behind it is plain
declaration order, and a model that keeps failing sinks below healthier ones rather than being
re-tried in its declared slot.
A success re-arms every candidate, since exclusion looks only at attempts since the last success, and
clears the succeeding candidate's own failure count. Every other candidate keeps its failure history,
so a model that fails repeatedly stays below healthier ones across successes. Returns ``None`` once
every candidate has been tried since the last success.
"""
async def select(self, context: RoutingContext, **kwargs: Any) -> RoutingCandidate | None:
"""Return the least-failed candidate not yet tried since the last success, else ``None``."""
failures: dict[int, int] = {}
for attempt in context.attempts:
if attempt.exception is None:
failures.pop(id(attempt.candidate), None)
else:
failures[id(attempt.candidate)] = failures.get(id(attempt.candidate), 0) + 1
tried_now = {id(attempt.candidate) for attempt in _attempts_since_success(context.attempts)}
available = [candidate for candidate in context.candidates if id(candidate) not in tried_now]
if not available:
return None
return min(available, key=lambda candidate: failures.get(id(candidate), 0))