1import random2import time3import logging45log = logging.getLogger(__name__)617MAX_ATTEMPTS = 58BASE_DELAY = 1.0 # seconds29310def 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 None7 delay = 2 ** attempt8 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.3838 redis.zadd("agentflow:retries", {agent_id: time.time() + delay})3939 log.info("enqueued", agent_id=agent_id, delay=delay)4040 return delay4142def 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.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.