feat: warn when max_metric_calls is below the budget floor (closes #375 partially)#376
Open
LakshyAAAgrawal wants to merge 4 commits into
Open
feat: warn when max_metric_calls is below the budget floor (closes #375 partially)#376LakshyAAAgrawal wants to merge 4 commits into
LakshyAAAgrawal wants to merge 4 commits into
Conversation
Closes (partially) #375 — the failure where a coding agent ran GEPA via /goal with max_metric_calls=8 against a 6-row valset, got exactly one proposal step, and reported the run as "done" because the single accepted candidate beat the baseline on external validation. The root cause is that max_metric_calls is the *only* knob controlling iteration count, but the relationship between budget, valset size, and actual proposal depth is implicit and not surfaced. This change makes both ends of the run loud about the problem without breaking any existing API. What's added - GEPABudgetWarning (subclass of UserWarning) exported as `gepa.GEPABudgetWarning` so callers can silence with the standard warnings.filterwarnings mechanism. - Pre-flight check at start of `gepa.optimize`: if `max_metric_calls < len(valset) + 3 * (reflection_minibatch_size + len(valset))`, warn with the recommended floor, the implied proposal count under the current budget, and a link to the new budget guide. Silent when no budget is set (custom stop_callbacks path) or when the valset loader doesn't expose __len__. - Post-run summary: logs "GEPA finished: N proposal(s) accepted over M metric call(s); final candidate pool size = K" and warns if fewer than 3 proposals were accepted (the exact `num_candidates=2` symptom the reporting agent missed in its post-mortem). - Docstring rewrite on `max_metric_calls` calling out that it's the only iteration knob and referencing the budget guide. Docs - New `docs/docs/guides/budget.md` — Choosing max_metric_calls (formula, worked-example floor table, how to read the new warnings, how to silence). - New `docs/docs/guides/coding-agents.md` — Running GEPA inside autonomous coding agents, with a copy-paste prompt block. - Quickstart admonition next to `max_metric_calls=50` explaining why that value is on the low side, with a link to the budget guide. - New FAQ entry: "My GEPA run finished with only one proposal — what happened?" - mkdocs nav entries for the two new guides. AGENTS.md - New "Running GEPA from inside an autonomous loop" section. AGENTS.md is the first thing Claude Code / Codex / Cursor read, so the budget-formula + verify-num_candidates + treat-GEPABudgetWarning-as-failure rules live there directly. Issue #375 was caused by an agent that didn't have this context — putting it in AGENTS.md fixes it at the source for any agent that operates inside this repo. Tests - tests/test_budget_warnings.py — 18 tests covering the floor formula, loader-length probing, pre-flight warning conditions (including silence cases for None budget / streaming loader / empty valset), post-run summary behavior (including the exact incident config of num_candidates=2), logger interaction, public re-export, and the warnings.filterwarnings silence/error round-trip. Deferred from the issue (worth their own discussion) - `min_proposals` parameter on `optimize` — meaningful API addition, deserves separate PR + interaction analysis with stop_callbacks. - Making `max_metric_calls` non-default — invasive backwards-compatibility change. - Standalone agent skill on agentskills.io — distribution lives outside this repo; happy to follow up with a separate skill manifest that wraps the prompt block from coding-agents.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Changed Files
|
Per project recommendation, GEPA should get at least 15 proposal attempts for meaningful evolutionary search — not 3 as I'd initially implemented. This brings the constant and all user-facing copy in line. - _MIN_RECOMMENDED_PROPOSALS: 3 -> 15 (drives the pre-flight floor and the recommended max_metric_calls in all warning messages and docs). - Introduce _MIN_ACCEPTED_PROPOSALS_NO_WARN=3 as a separate threshold for the post-run "almost nothing happened" check, since not every proposal attempt gets accepted into the pool. The pre-flight is what enforces the full 15-attempt budget; the post-run only flags the degenerate "1-2 accepted" smoke test failure mode. - Add the simple rule of thumb (`~16 x len(valset)`) to docs alongside the exact formula; emphasize it in the warning text and prompt blocks. - Update budget guide's recommended-floor table, coding-agents.md prompt block, AGENTS.md, FAQ entry, quickstart admonition. - Update tests to assert the new 141 floor for the incident config (valset=6, minibatch=3, N=15) instead of the previous 33. 19 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three corrections to the previous commit: 1. Drop the "max_metric_calls is the only knob controlling iteration count" claim. That's wrong — StopperProtocol has several implementations (NoImprovementStopper, TimeoutStopCondition, ScoreThresholdStopper, SignalStopper, FileStopper, CompositeStopper) that can stop GEPA earlier than max_metric_calls would. The correct framing is that max_metric_calls is the *ceiling* on the budget — other stoppers can lower the effective iteration count but none of them can raise it above max_metric_calls, so undersizing it still caps the optimizer regardless of what else you configure. Updated this framing in: - src/gepa/api.py docstring on max_metric_calls - docs/docs/guides/budget.md (+ new "Other stop conditions" section) - docs/docs/guides/coding-agents.md (intro + copy-paste prompt block) - docs/docs/guides/quickstart.md admonition - docs/docs/guides/faq.md - AGENTS.md 2. Rule of thumb: ">15 * len(valset)" not "~16 * len(valset)" — matches the project's actual recommendation phrasing. The exact 15-proposals formula (len(valset) + 15 * (minibatch + valset)) stays as the precise version. 3. Reframe "Do not present score graphs while either warning is active" as "Do not consider a GEPA run as valid if either warning is active" — captures the real intent better (the run is invalid, not just unfit for visualization). Also removed the trailing "These rules exist because of a real incident" sentence from AGENTS.md as requested — that pointer belongs in the docs page, not the agent-facing instructions. All 19 tests still pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Missed the more important point in the previous corrections: you don't have
to set max_metric_calls at all. Passing stop_callbacks (e.g.
NoImprovementStopper(max_iterations_without_improvement=10)) and skipping
max_metric_calls lets GEPA run until the optimization actually converges,
without anyone guessing a budget upfront. For unattended agent runs this
is usually the better choice.
This commit reframes all the user-facing copy around two equal-status paths
("stop_callbacks only" or "max_metric_calls"), recommends the stopper-only
path as the preferred default for autonomous loops, and rewrites the
post-run warning text so it suggests either remedy (raise max_metric_calls
OR loosen the stopper that fired), since the user may not have set
max_metric_calls at all.
Files touched:
- src/gepa/api.py: docstring + post-run warning text
- docs/docs/guides/budget.md: opens with the two-path framing; the
NoImprovementStopper example is the first concrete recommendation
- docs/docs/guides/coding-agents.md: ditto; prompt block now leads with
the stopper-only path
- docs/docs/guides/quickstart.md: admonition mentions both paths
- docs/docs/guides/faq.md: "why only one proposal" answer covers both
causes (undersized budget OR aggressive stopper)
- AGENTS.md: two paths up front; stopper-only flagged as preferred default
- tests/test_budget_warnings.py: incident-config assertion updated to
match the new warning text (mentions both max_metric_calls and stopper
remedies)
19/19 tests pass, lint clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements most of the proposals in #375 — the failure where a coding agent ran GEPA via
/goalwithmax_metric_calls=8against a 6-row valset, got exactly one proposal step, and reported "done" because the single accepted candidate happened to beat the baseline on external validation.The root cause is that
max_metric_callsis the only knob controlling iteration count, but the relationship between budget, valset size, and actual proposal depth is implicit and not surfaced. This PR makes both ends of the run loud about the problem without breaking any existing API.What's added
API (no breaking changes)
gepa.GEPABudgetWarning(subclass ofUserWarning) — exported at the top level so callers can silence with the standardwarnings.filterwarningsmechanism.gepa.optimize: ifmax_metric_calls < len(valset) + 3 * (reflection_minibatch_size + len(valset)),warn with the recommended floor, the implied proposal count under the current budget, and a link to the new budget guide. Silent when no budget is set (custom
stop_callbackspath) or when the valset loader doesn't expose__len__.GEPA finished: N proposal(s) accepted over M metric call(s); final candidate pool size = Kand warns again if fewer than 3 proposals were accepted — that's the exactnum_candidates=2symptom the reporting agent missed in its post-mortem.max_metric_callscalling out that it's the only iteration knob.Docs
docs/docs/guides/budget.md— Choosingmax_metric_calls. Formula, a worked-example floor table for common configurations, how to read the warnings, how to silence them, how to diagnose retrospectively.docs/docs/guides/coding-agents.md— Running GEPA inside autonomous coding agents. Includes a copy-paste prompt block users can drop into Claude Code / Codex/goal/ Cursor agent mode.max_metric_calls=50explaining why that value is on the low side and linking to the budget guide.AGENTS.md
A new "Running GEPA from inside an autonomous loop" section. AGENTS.md is the file Claude Code / Codex / Cursor read first when they enter the repo, so the budget formula + verify-
num_candidates+ treat-GEPABudgetWarning-as-failure rules live there directly. Issue #375 was caused by an agent that didn't have this context — putting it in AGENTS.md fixes it at the source for any agent that operates inside this repo (and is exactly the kind of thing you asked about).Tests
tests/test_budget_warnings.py— 18 tests covering: the floor formula, loader-length probing, pre-flight warning conditions (including silence cases forNonebudget / streaming loader / empty valset), post-run summary behavior (including the exact incident config ofnum_candidates=2), logger interaction, public re-export, and thewarnings.filterwarningssilence/error round-trip. All pass locally.Deferred from #375 (worth their own discussion)
You asked whether to do "all of these changes" — I picked the highest-leverage subset and left these out for follow-ups so this PR isn't trying to make too many design decisions at once:
min_proposalsparameter onoptimize. A meaningful API addition (separate frommax_metric_calls) that lets callers say "don't return until you've tried at least N proposals." Needs an interaction analysis with the existingstop_callbacksmachinery and with composite stoppers — better as a focused PR.max_metric_callsnon-default. Invasive backwards-compatibility change; the warning is the much lower-risk way to get the same effect.coding-agents.mdand the budget formula. The AGENTS.md section in this PR handles the in-repo coverage; a skill would extend it to any agent invoking GEPA from outside the repo.Test plan
uv run pytest tests/test_budget_warnings.py— 18/18 passuv run pytest tests/test_data_loader.py— no regressionuv run ruff check src/gepa/api.py src/gepa/__init__.py tests/test_budget_warnings.py— cleanuv run pyright src/gepa/api.py— 0 errorsmkdocs serveand confirm the budget and coding-agents pages render in the Guides navgepa.optimizesmoke run withmax_metric_calls=8, valset_size=6to confirm both warnings fire and the post-run summary line is loggedLinked issue
Closes #375 (partially — see "Deferred" section above for what's left for follow-ups).
🤖 Generated with Claude Code