diff --git a/.env.example b/.env.example index 6aff565..db1e1b0 100644 --- a/.env.example +++ b/.env.example @@ -34,3 +34,9 @@ GH_TOKEN= # PR_AF_NO_BUDGET=false # PR_AF_MAX_DURATION_SECONDS=300 # PR_AF_MAX_COST_USD=2.0 + +# Concurrency for review_dimension fan-out. Default 10 lets all 6–8 +# dimensions run in parallel; raise/lower based on your provider's +# per-key rate limits. Was 3 before — caused 3× wall-clock multiplier +# in production. +# PR_AF_MAX_CONCURRENT_REVIEWERS=10 diff --git a/src/pr_af/config.py b/src/pr_af/config.py index 04bdc2d..5215e8b 100644 --- a/src/pr_af/config.py +++ b/src/pr_af/config.py @@ -45,9 +45,17 @@ class BudgetConfig(BaseModel): } ) - # Concurrency — kept low to avoid cascading rate-limit backoff - # when using OpenRouter or other rate-limited providers. - max_concurrent_reviewers: int = 3 + # Concurrency for review_dimension fan-out. + # + # Production data showed 8 review_dimensions throttled by this semaphore at + # the previous default of 3, turning per-dimension cost (~25 min) into a + # 3× wall-clock multiplier (≥75 min for the review phase alone). Bumped to + # 10 — well within OpenRouter's per-key rate limits on Kimi K2.5 and the + # other models we run through opencode. Override via PR_AF_MAX_CONCURRENT_REVIEWERS + # if a deployment needs to dial it back for a stricter rate-limit ceiling. + max_concurrent_reviewers: int = Field( + default_factory=lambda: int(os.getenv("PR_AF_MAX_CONCURRENT_REVIEWERS", "10")) + ) # Stagger delay (seconds) between launching parallel tasks to avoid # burst rate-limit hits. Set to 0 to disable staggering. diff --git a/tests/test_staggered_gather.py b/tests/test_staggered_gather.py index d4e45d8..a67c7ea 100644 --- a/tests/test_staggered_gather.py +++ b/tests/test_staggered_gather.py @@ -90,7 +90,21 @@ async def fail() -> str: def test_budget_config_defaults(): - """Verify the updated concurrency and stagger defaults.""" - config = BudgetConfig() - assert config.max_concurrent_reviewers == 3 - assert config.stagger_delay_seconds == 2.0 + """Verify the updated concurrency and stagger defaults. + + Concurrency was raised from 3 → 10 after production data showed 8 + review_dimensions throttled by the old semaphore, turning ~25-min + per-dimension cost into a 3× wall-clock multiplier. The default is + overridable via PR_AF_MAX_CONCURRENT_REVIEWERS — clear that env var + so the test pins the in-code default rather than the runtime override. + """ + import os + + prior = os.environ.pop("PR_AF_MAX_CONCURRENT_REVIEWERS", None) + try: + config = BudgetConfig() + assert config.max_concurrent_reviewers == 10 + assert config.stagger_delay_seconds == 2.0 + finally: + if prior is not None: + os.environ["PR_AF_MAX_CONCURRENT_REVIEWERS"] = prior