fix(oa): bind AutoResearch results to completed evaluations - #415
fix(oa): bind AutoResearch results to completed evaluations#415sh-patterson wants to merge 9 commits into
Conversation
Greptile SummaryThe PR binds AutoResearch results to completed evaluation-server checkpoints rather than mutable workspace state.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| 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
Reviews (8): Last reviewed commit: "fix(oa): admit AutoResearch evals before..." | Re-trigger Greptile
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>
… 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>
|
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. |
|
I hope the autoresearch backend can stay as simple as possible. |
|
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. |
|
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.
We can prompt to prevent this.
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. |
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>
Problem
AutoResearch can return after its agent subprocess exits while an
eval.shrequest is still running or has not reached EvalServer yet. The engine then trusted mutablebest_candidate.txt, so the returned candidate could be unevaluated, or evaluation work could land after the result was selected.Change
eval.shcan still land, then pause HTTP.drain_timeout_seconds(default 600s) so a hungeval_fncannot block the run forever.best_candidate.txt./evaluate_examplesaverage. Single-task runs use the best completed single eval.best_candidate.txtasagent_best_candidate.txtwhen it differs from the tracked winner.eval.shtreats HTTP 409 as a failure (EVAL_SERVER_PAUSED).program.mdthat only completed eval-server scores (full-pool on dataset tasks) select the returned winner.Verification
/evaluateis waited for and selecteduv 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 passedScope
This is an evaluation-integrity repair. It proves returned results come from completed eval-server state and that leftover
eval.shafter 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.