Skip to content

fix(oa): bind AutoResearch results to completed evaluations - #415

Open
sh-patterson wants to merge 9 commits into
gepa-ai:mainfrom
sh-patterson:codex/autoresearch-eval-integrity
Open

fix(oa): bind AutoResearch results to completed evaluations#415
sh-patterson wants to merge 9 commits into
gepa-ai:mainfrom
sh-patterson:codex/autoresearch-eval-integrity

Conversation

@sh-patterson

@sh-patterson sh-patterson commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Problem

AutoResearch can return after its agent subprocess exits while an eval.sh request is still running or has not reached EvalServer yet. The engine then trusted mutable best_candidate.txt, so the returned candidate could be unevaluated, or evaluation work could land after the result was selected.

Change

  • After each Claude subprocess exits, wait for admitted evals, stay accepting for a short quiet period so in-transit eval.sh can still land, then pause HTTP.
  • Bound that drain with drain_timeout_seconds (default 600s) so a hung eval_fn cannot block the run forever.
  • Pick the returned candidate and score from completed eval-server tracking, not best_candidate.txt.
  • Dataset tasks use the best full-pool /evaluate_examples average. Single-task runs use the best completed single eval.
  • Keep the agent's best_candidate.txt as agent_best_candidate.txt when it differs from the tracked winner.
  • Write the tracked winner back into the workspace between Ralph iterations when a completed checkpoint exists.
  • eval.sh treats HTTP 409 as a failure (EVAL_SERVER_PAUSED).
  • Tell the agent in program.md that only completed eval-server scores (full-pool on dataset tasks) select the returned winner.

Verification

  • In-transit HTTP request blocked before admit is accepted during the quiet period
  • In-flight HTTP /evaluate is waited for and selected
  • Drain timeout pauses without hanging
  • HTTP 409 after drain completes
  • Agent file preserved when the tracked winner is the seed
  • uv run pytest tests/test_optimize_anything_autoresearch_engine.py tests/test_optimize_anything_eval_server.py tests/test_optimize_anything_adaptive_sequential.py -q -> 40 passed
  • Ruff passed; Pyright reports 0 errors on the changed sources

Scope

This is an evaluation-integrity repair. It proves returned results come from completed eval-server state and that leftover eval.sh after process exit is waited out. It does not claim that an LLM causally used prior feedback when authoring its next candidate. The pre-existing concurrent budget-reservation race is out of scope.

@sh-patterson
sh-patterson marked this pull request as ready for review July 31, 2026 15:52
@LakshyAAAgrawal

Copy link
Copy Markdown
Contributor

@greptileai

@greptile-apps

greptile-apps Bot commented Aug 17, 2026

Copy link
Copy Markdown

Greptile Summary

The PR binds AutoResearch results to completed evaluation-server checkpoints rather than mutable workspace state.

  • Drains admitted and in-transit HTTP evaluations before selecting a winner, with a configurable timeout and quiet period.
  • Selects dataset winners from completed full-pool aggregates and single-task winners from completed individual evaluations.
  • Preserves differing agent-selected workspace candidates and documents the updated selection contract.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
src/gepa/oa/engines/autoresearch.py Selects winners from completed server tracking, drains HTTP work between agent iterations, and preserves differing workspace candidates.
src/gepa/oa/eval_server.py Adds admission and inflight accounting, bounded HTTP draining, paused-request handling, and selectable progress checkpoints.
tests/test_optimize_anything_autoresearch_engine.py Covers completed-work selection, dataset aggregation, drain timeout behavior, in-transit requests, and workspace preservation.
tests/test_optimize_anything_eval_server.py Exercises HTTP pause and drain behavior together with inflight request accounting.
.claude/skills/gepa-optimize-anything/references/api.md Documents drain configuration and the completed-checkpoint winner-selection contract.

Sequence Diagram

sequenceDiagram
    participant Agent as Claude / eval.sh
    participant Engine as AutoResearchEngine
    participant Server as EvalServer
    participant Workers as Evaluation workers
    Agent->>Server: POST evaluation
    Server->>Server: Admit request and increment inflight
    Server->>Workers: Execute evaluation work
    Agent-->>Engine: Claude subprocess exits
    Engine->>Server: drain_http(timeout, quiet)
    Server->>Server: Wait for inflight work and quiet period
    Workers-->>Server: Completed scores
    Server->>Server: Record selectable checkpoint
    Server-->>Engine: Pause HTTP and finish drain
    Engine->>Server: Read completed tracking
    Engine->>Engine: Select and persist tracked winner
    Engine->>Server: Resume HTTP
Loading

Reviews (8): Last reviewed commit: "fix(oa): admit AutoResearch evals before..." | Re-trigger Greptile

Comment thread src/gepa/oa/eval_server.py Outdated
Session close was zero-filling queued per-example workers, and Ralph
iterations still pointed agents at a closed eval.sh. Keep admitted
fan-out running after close, fail closed-session errors instead of
scoring them as 0, and bind later Ralph invocations to eval-N.sh.

Co-authored-by: Cursor <cursoragent@cursor.com>
@LakshyAAAgrawal

Copy link
Copy Markdown
Contributor

@greptileai

LakshyAAAgrawal and others added 2 commits August 16, 2026 21:57
… winner

Drain no longer borrows the 120s budget-exhaustion grace, so a slow in-flight
dataset eval cannot abort a completed run. The selected candidate is written
back to best_candidate.txt, Ralph updates program.md and retires prior eval
scripts, and dataset results require a full-pool checkpoint.

Co-authored-by: Cursor <cursoragent@cursor.com>
A drain timeout left the live session in EvalServer, so tokenless HTTP
and later engines on a shared server stayed blocked. Snapshot and drop
the session on timeout, fail-close cleanup without waiting, and record
full-pool aggregates from the Python evaluate_examples path.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Shangyint

Copy link
Copy Markdown
Collaborator

the bug is legit, but I am not sure if this is the best way to do it.

What about the following change: for autoresearch session, we only give it 95% of the budget to do the optimization, and use rest 5% of the budget to force the autoresearch agent to select the best candidate.

@Shangyint

Copy link
Copy Markdown
Collaborator

I hope the autoresearch backend can stay as simple as possible.

@LakshyAAAgrawal

Copy link
Copy Markdown
Contributor

Agree we should keep AutoResearch simple. A 95/5 split does not close this bug though.

The failure is that Claude can exit while an eval is still running, and we then trust best_candidate.txt. Asking the agent to pick the winner with leftover budget has the same problem. That process can also exit while an eval is still running, and we are still trusting the agent and the file.

5% is also often too small. On a 40-example pool with max_evals=100, 5% is 5 calls, which cannot score a candidate on the full pool. If the cap is max_token_cost, 5% of dollars does not buy a full-pool eval.

The agent can still write a candidate that was only spot-checked, or treat one high example score as the winner.

We would still have to wait for in-flight evals after that last phase, or the original race will still there at the end.

@Shangyint

Copy link
Copy Markdown
Collaborator

Ah what I meant was the 5%'s user message should prohibit any eval and ask the autoresearch agent to decide a best, based on current context (we can even close the eval server/endpoint). This basically sacrifices the last eval + candidate, which is fine to me.

The agent can still write a candidate that was only spot-checked

We can prompt to prevent this.

or treat one high example score as the winner.

This is the agent's behavior most of the time.

Another more modular solution might be: we track all the candidates from the eval server, and choose the best candidate post-hoc (do not rely on the agent's best_candidate.txt).

Current solution in this PR complicates autoresearch by a lot, and will be painful for us if it introduces more issues.

LakshyAAAgrawal and others added 3 commits August 17, 2026 10:23
After Claude exits, pause new HTTP evals, wait for in-flight work, and
select from server tracking instead of best_candidate.txt. Drop session
tokens, eval-N.sh rotation, and the admission protocol.

Co-authored-by: Cursor <cursoragent@cursor.com>
…g the agent file

Stay accepting for a quiet period after Claude exits so leftover eval.sh can still land, bound the wait, and keep the agent's best_candidate.txt when the tracked winner is the seed.

Co-authored-by: Cursor <cursoragent@cursor.com>
Drain could miss a last eval.sh still blocked in the POST body, and workspace
sync could wipe the agent's file before it was preserved. Count HTTP work at
handler entry, keep the agent pick beside the tracked winner, and ignore
val-only /validate checkpoints when selecting.

Co-authored-by: Cursor <cursoragent@cursor.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.

3 participants