Skip to content

feat: warn when max_metric_calls is below the budget floor (closes #375 partially)#376

Open
LakshyAAAgrawal wants to merge 4 commits into
mainfrom
feat/budget-floor-warning
Open

feat: warn when max_metric_calls is below the budget floor (closes #375 partially)#376
LakshyAAAgrawal wants to merge 4 commits into
mainfrom
feat/budget-floor-warning

Conversation

@LakshyAAAgrawal

Copy link
Copy Markdown
Contributor

Summary

Implements most of the proposals in #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 "done" because the single accepted candidate happened to 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 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 of UserWarning) — exported at the top level so callers can silence with the standard warnings.filterwarnings mechanism.
  • Pre-flight check at the 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 again if fewer than 3 proposals were accepted — that's 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.

Docs

  • New docs/docs/guides/budget.md — Choosing max_metric_calls. Formula, a worked-example floor table for common configurations, how to read the warnings, how to silence them, how to diagnose retrospectively.
  • New 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.
  • Quickstart admonition next to max_metric_calls=50 explaining why that value is on the low side and linking to the budget guide.
  • New FAQ entry: "My GEPA run finished with only one proposal — what happened?"
  • mkdocs nav entries for both new guides.

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 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. 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_proposals parameter on optimize. A meaningful API addition (separate from max_metric_calls) that lets callers say "don't return until you've tried at least N proposals." Needs an interaction analysis with the existing stop_callbacks machinery and with composite stoppers — better as a focused PR.
  • Making max_metric_calls non-default. Invasive backwards-compatibility change; the warning is the much lower-risk way to get the same effect.
  • Standalone agent skill on agentskills.io. Yes, this is worth doing and you raised it explicitly — but it lives outside this repo. Happy to follow up with a separate skill manifest that wraps the prompt block from coding-agents.md and 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 pass
  • uv run pytest tests/test_data_loader.py — no regression
  • uv run ruff check src/gepa/api.py src/gepa/__init__.py tests/test_budget_warnings.py — clean
  • uv run pyright src/gepa/api.py — 0 errors
  • mkdocs serve and confirm the budget and coding-agents pages render in the Guides nav
  • One manual gepa.optimize smoke run with max_metric_calls=8, valset_size=6 to confirm both warnings fire and the post-run summary line is logged

Linked issue

Closes #375 (partially — see "Deferred" section above for what's left for follow-ups).

🤖 Generated with Claude Code

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>
@semanticdiff-com

semanticdiff-com Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review changes with  SemanticDiff

Changed Files
File Status
  src/gepa/__init__.py  6% smaller
  src/gepa/api.py  3% smaller
  AGENTS.md Unsupported file format
  docs/docs/guides/budget.md Unsupported file format
  docs/docs/guides/coding-agents.md Unsupported file format
  docs/docs/guides/faq.md Unsupported file format
  docs/docs/guides/quickstart.md Unsupported file format
  docs/mkdocs.yml  0% smaller
  tests/test_budget_warnings.py  0% smaller

gepa-bot and others added 3 commits June 13, 2026 16:37
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Coding agents running GEPA via /goal don't know to set a high-enough max_metric_calls — proposed API + docs fixes

2 participants