Skip to content

Retry loop runs max_attempts + 1 times — one extra LLM round-trip on every failure #39

Description

@enJcOde

RetryPolicy.max_attempts is documented as the total attempt count including the first, aag/error_recovery/policies.py:9:

    max_attempts: int = 3          # Total attempts (including the first)

But the block-level retry loop iterates range(max_attempts + 1), aag/error_recovery/error_manager.py:75:

        for attempt in range(max_attempts+1):
            try:
                result = await operation(error_history)
                if attempt > 1:
                    logger.info("✅ %s succeeded on attempt %d/%d", name, attempt, max_attempts)
                return result
            except Exception as e:
                ...
                if attempt < max_attempts:
                    logger.warning("⚠️ %s failed on attempt %d/%d: %s", name, attempt, max_attempts, err["error"])
                else:
                    logger.error("❌ %s failed after %d attempts", name, max_attempts)

With max_attempts = 3, range(3 + 1) yields attempt = 0, 1, 2, 3 — the operation runs four times, not three.

Failure scenario. Every hard failure in an operation wrapped by run() (LLM-driven code generation, parameter extraction, dependency resolution) performs one more attempt than configured. Since each attempt is typically an LLM call plus an exec, this is a real cost and latency regression on exactly the slow, expensive path. The error log "failed after %d attempts" also prints max_attempts (3) while 4 calls actually happened, so the logs disagree with reality.

Two smaller correctness slips in the same block:

  • if attempt > 1 means a success on the second attempt (attempt == 1) never logs the "succeeded on attempt" line.
  • The final failure branch logs max_attempts rather than attempt + 1, undercounting.

Fix. Use for attempt in range(max_attempts): and gate the success log on attempt >= 1 (or attempt > 0). Then the "including the first" contract in policies.py holds.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions