PR #47 — AgentFlow: Sistema de reintentos con backoff exponencial

agents/retry_engine.py@1 → @1
1import random
2import time
3import logging
4
5log = logging.getLogger(__name__)
6
17MAX_ATTEMPTS = 5
8BASE_DELAY = 1.0 # seconds
29
310def schedule_retry(agent_id: str, attempt: int) -> float | None:
411 """Devuelve el delay en segundos, o None si se agotaron los intentos."""
512 if attempt >= MAX_ATTEMPTS:
13 log.warning("max_attempts_reached", agent_id=agent_id, attempt=attempt)
614 return None
7 delay = 2 ** attempt
8 return _enqueue(agent_id, delay)
15 delay = BASE_DELAY * (2 ** attempt)
16 jitter = random.uniform(0, delay * 0.1)
17 log.info("retry_scheduled", agent_id=agent_id, attempt=attempt, delay=delay + jitter)
18 return _enqueue(agent_id, delay + jitter)
random.uniform() no está sembrado. En los tests de integración produce delays no deterministas y hace fallar los snapshots de tiempo. Necesita un `random.Random(seed)` configurable o un parámetro `_jitter_fn` inyectable para test.
BASE_DELAY debería ser configurable por entorno (`AGENTFLOW_RETRY_BASE_DELAY_S`). En producción querremos valores distintos según la presión de la cola. Hardcodear 1.0 lo hace inmodificable sin código.
agents/retry_queue.py@38 → @38def _enqueue(agent_id: str, delay: float) -> float:
3838 redis.zadd("agentflow:retries", {agent_id: time.time() + delay})
3939 log.info("enqueued", agent_id=agent_id, delay=delay)
4040 return delay
41
42def cancel_retries(agent_id: str) -> int:
43 """Elimina todos los reintentos pendientes de un agente. Devuelve el nº eliminados."""
44 removed = redis.zrem("agentflow:retries", agent_id)
45 if removed:
46 log.info("retries_cancelled", agent_id=agent_id, removed=removed)
47 return removed
redis.zrem devuelve un `int` (número de elementos eliminados), pero si el agente tiene varias entradas con el mismo ID no las eliminaría todas. Considera `redis.zremrangebyscore` o un sorted set con score compuesto. No es urgente si el diseño garantiza unicidad de IDs.
Convención de nombres: usar retries.cancelled (con punto) en lugar de `retries_cancelled` para mantener la jerarquía de eventos del resto del módulo (`retry_scheduled`, `max_attempts_reached` ya usan snake_case — es inconsistente). Unificar a punto-separado o guión-bajo en todos.