Skip to content

evals: judge behavior instead of matching source text - #97826

Merged
gaojude merged 3 commits into
canaryfrom
evals/judge-behavior-not-style
Aug 25, 2026
Merged

evals: judge behavior instead of matching source text#97826
gaojude merged 3 commits into
canaryfrom
evals/judge-behavior-not-style

Conversation

@gaojude

@gaojude gaojude commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Makes a few of the evals judge what the code actually does rather than how it happens to be written, so correct solutions stop failing on style. One of them was already being vetoed by leftover assertions the judge was meant to replace.

@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Tests Passed

Commit: d116816

@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Stats from current PR

🔴 2 regressions, 1 improvement

Metric Canary PR Change Trend
Webpack Build Time 25.563s 26.123s 🔴 +560ms (+2%) ▁▁▂▁▁
Webpack Build Time (cached) 25.464s 26.330s 🔴 +866ms (+3%) ▁▁▂▂▂
Webpack Warm (First Request) 3.687s 3.512s 🟢 175ms (-5%) ▁▁▂▁▁
📊 All Metrics
📖 Metrics Glossary

Dev Server Metrics:

  • Listen = TCP port starts accepting connections
  • First Request = HTTP server returns successful response
  • Cold = Fresh build (no cache)
  • Warm = With cached build artifacts

Build Metrics:

  • Fresh = Clean build (no .next directory)
  • Cached = With existing .next directory

Change Thresholds:

  • Time: Changes < 50ms AND < 10%, OR < 2% are insignificant
  • Size: Changes < 1KB AND < 1% are insignificant
  • All other changes are flagged to catch regressions

⚡ Dev Server

Metric Canary PR Change Trend
Cold (Listen) 812ms 812ms █████
Cold (Ready in log) 781ms 781ms ▄▁▃█▇
Cold (First Request) 1.223s 1.222s ▂▁▁█▆
Warm (Listen) 813ms 812ms █████
Warm (Ready in log) 783ms 784ms ▅▆▅▆█
Warm (First Request) 1.217s 1.222s ▃▆▁▇▇
📦 Dev Server (Webpack) (Legacy)

📦 Dev Server (Webpack)

Metric Canary PR Change Trend
Cold (Listen) 815ms 863ms ▁▂▃▂▂
Cold (Ready in log) 818ms 832ms ▁▁▂▁▁
Cold (First Request) 3.481s 3.566s ▁▁▂▁▁
Warm (Listen) 864ms 863ms ▁▂▃▂▂
Warm (Ready in log) 835ms 825ms ▁▁▂▂▁
Warm (First Request) 3.687s 3.512s 🟢 175ms (-5%) ▁▁▂▁▁

⚡ Production Builds

Metric Canary PR Change Trend
Fresh Build 5.488s 5.541s ▃▂▁▅█
Cached Build 2.824s 2.831s ▂▁▁▄█
📦 Production Builds (Webpack) (Legacy)

📦 Production Builds (Webpack)

Metric Canary PR Change Trend
Fresh Build 25.563s 26.123s 🔴 +560ms (+2%) ▁▁▂▁▁
Cached Build 25.464s 26.330s 🔴 +866ms (+3%) ▁▁▂▂▂
node_modules Size 553 MB 553 MB █████
📦 Bundle Sizes

Bundle Sizes

⚡ Turbopack

Client

Main Bundles
Canary PR Change
017_4moyv5m34.js gzip 117 B 117 B
05jocc85-b73_.js gzip 341 B N/A -
096twxhe86hwd.js gzip 7.53 kB N/A -
0cz1d0mv5g_q7.js gzip 39.4 kB 39.4 kB
0e5lgz-xcf6ln.js gzip 273 B 273 B
0enhddgqs2dl-.js gzip 265 B 265 B
0h34mejwa_7pd.js gzip 354 B 354 B
0mg5-nyl2xcjy.js gzip 65.5 kB N/A -
0w853zxni3ro0.js gzip 1.36 kB N/A -
13lq2aw3_0bgw.js gzip 3.52 kB N/A -
17wjm_wc16a_w.js gzip 71.5 kB N/A -
1drifx6-oqf3q.js gzip 5.7 kB N/A -
1elt1qium-r2m.css gzip 115 B 115 B
1m5vdnlf0deiv.js gzip 411 B 411 B
1nigpy_mr0xvj.js gzip 339 B N/A -
1p0l8mn2wz5f2.js gzip 1.97 kB N/A -
1sqoyy39jpgkq.js gzip 121 B 121 B
1tf1phijqlx9j.js gzip 220 B 220 B
2-54v5vj7orwp.js gzip 46.8 kB N/A -
26upjmii0ixt5.js gzip 153 B N/A -
2b05_424u1zq-.js gzip 120 B N/A -
2ca40xittx95r.js gzip 8.75 kB N/A -
2kci0vnt-49o-.js gzip 113 B 113 B
2rtriq9lc6zj-.js gzip 116 B N/A -
2sxgl_rcrrn_p.js gzip 399 B N/A -
2vt62i17bhbdu.js gzip 2.35 kB N/A -
342di-crrg9kq.js gzip 119 B N/A -
36hfc2_979aw2.js gzip 119 B N/A -
38wfp6jjrrdfm.js gzip 131 B 131 B
3dl1ilk37-30g.js gzip 117 B N/A -
3h0us7yy7bd_o.js gzip 13.1 kB N/A -
3kvxagoobts7h.js gzip 117 B N/A -
3p99hd9vwe6ae.js gzip 13 kB N/A -
3r0475panhk6m.js gzip 123 B N/A -
3s0i-weykgzrt.js gzip 125 B N/A -
3vjosdbddykpw.js gzip 5.88 kB N/A -
3wijk2ywqgsv9.js gzip 2.52 kB N/A -
41u5s3oe2-erp.js gzip 2.29 kB N/A -
turbopack-0d..9ejy.js gzip 3.74 kB 3.74 kB
02zc7sqlpqxn3.js gzip N/A 119 B -
06pzxqp5qiozm.js gzip N/A 5.7 kB -
0dug3edczk4gl.js gzip N/A 71.5 kB -
0e28eh3gbwmp0.js gzip N/A 3.51 kB -
0e53csrecof4e.js gzip N/A 65.5 kB -
0jwho9fkrb_t3.js gzip N/A 2.29 kB -
0longconn5mlm.js gzip N/A 123 B -
0p4l3ozbez2cz.js gzip N/A 2.35 kB -
0sbh_15ejop4o.js gzip N/A 13 kB -
0skdm-9rpid8g.js gzip N/A 117 B -
0wv-h56sz83wn.js gzip N/A 120 B -
137xup2t69ovc.js gzip N/A 1.97 kB -
1ezuqvubf86h1.js gzip N/A 5.88 kB -
1fk10s3m1oecx.js gzip N/A 400 B -
1hut5u-lz2yif.js gzip N/A 117 B -
20qhkyyj2_tla.js gzip N/A 116 B -
2imyufe18bsb7.js gzip N/A 1.36 kB -
2p4n3-8-xixh_.js gzip N/A 341 B -
2vqdfieehz5do.js gzip N/A 8.75 kB -
2x99jdcpr5n8x.js gzip N/A 7.53 kB -
2xn85ykzw2v1e.js gzip N/A 13.1 kB -
34knadd6wu9qg.js gzip N/A 119 B -
39jq2b6t6z6up.js gzip N/A 339 B -
3a9a9tvkag_-j.js gzip N/A 2.52 kB -
3b660b4ak0547.js gzip N/A 153 B -
3d61tgick8g6b.js gzip N/A 46.8 kB -
3t121tj-h0kbm.js gzip N/A 125 B -
Total 299 kB 299 kB ⚠️ +3 B

Server

Middleware
Canary PR Change
middleware-b..fest.js gzip 1.02 kB 1.03 kB
Total 1.02 kB 1.03 kB ⚠️ +1 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 846 B 848 B
Total 846 B 848 B ⚠️ +2 B
Build Cache
Canary PR Change
00000001.sst gzip 14.7 MB 10.1 MB 🟢 4.64 MB (-31%)
00000002.sst gzip 12.4 MB 12.2 MB 🟢 234 kB (-2%)
00000003.sst gzip 13.9 MB 13.8 MB 🟢 103 kB (-1%)
00000004.sst gzip 13.8 MB 13.8 MB 🟢 48 kB (0%)
00000005.sst gzip 10.1 MB 15.1 MB 🔴 +5.08 MB (+50%)
00000006.sst gzip 2.75 MB 2.75 MB
00000007.sst gzip 59 B 59 B
00000008.meta gzip 89 B 89 B
00000009.meta gzip 297 kB 297 kB
00000010.meta gzip 297 kB 297 kB
00000011.meta gzip 297 kB 297 kB
00000012.sst gzip 52.6 kB 52.7 kB
00000013.sst gzip 1.78 MB 1.81 MB 🔴 +30.1 kB (+2%)
00000014.sst gzip 59 B 59 B
00000015.meta gzip 116 B 116 B
00000016.meta gzip 327 kB 327 kB
00000017.meta gzip 403 kB 404 kB
00000018.sst gzip 53.4 kB 53.5 kB
00000019.sst gzip 1.36 MB 1.38 MB 🔴 +19.7 kB (+1%)
00000020.sst gzip 59 B 59 B
00000021.meta gzip 116 B 116 B
00000022.meta gzip 327 kB 327 kB
00000023.meta gzip 371 kB 372 kB
00000024.sst gzip 53.5 kB 53.6 kB
00000025.sst gzip 1.36 MB 1.38 MB 🔴 +19.4 kB (+1%)
00000026.sst gzip 59 B 59 B
00000027.meta gzip 116 B 116 B
00000028.meta gzip 327 kB 327 kB
00000029.meta gzip 371 kB 372 kB
00000030.sst gzip 52.7 kB 52.7 kB
00000031.sst gzip 1.36 MB 1.38 MB 🔴 +19.4 kB (+1%)
00000032.sst gzip 59 B 59 B
00000033.meta gzip 116 B 116 B
00000034.meta gzip 327 kB 327 kB
00000035.meta gzip 371 kB 372 kB
00000036.sst gzip 53.4 kB 53.4 kB
00000037.sst gzip 1.36 MB 1.38 MB 🔴 +19.4 kB (+1%)
00000038.sst gzip 59 B 59 B
00000039.meta gzip 116 B 116 B
00000040.meta gzip 327 kB 327 kB
00000041.meta gzip 371 kB 372 kB
CURRENT gzip 93 B 93 B
LOG gzip 665 B 667 B
Total 79.5 MB 79.7 MB ⚠️ +163 kB

📦 Webpack

Client

Main Bundles
Canary PR Change
3322-HASH.js gzip 65.7 kB N/A -
4191.HASH.js gzip 169 B N/A -
9784-HASH.js gzip 5.6 kB N/A -
b1ad9f4c-HASH.js gzip 63.5 kB N/A -
framework-HASH.js gzip 59.7 kB 59.7 kB
main-app-HASH.js gzip 253 B 253 B
main-HASH.js gzip 39.8 kB 39.7 kB
webpack-HASH.js gzip 1.68 kB 1.68 kB
3577.HASH.js gzip N/A 168 B -
578-HASH.js gzip N/A 65.8 kB -
8590-HASH.js gzip N/A 5.57 kB -
a8984546-HASH.js gzip N/A 63.5 kB -
Total 236 kB 236 kB ⚠️ +106 B
Polyfills
Canary PR Change
polyfills-HASH.js gzip 39.4 kB 39.4 kB
Total 39.4 kB 39.4 kB
Pages
Canary PR Change
_app-HASH.js gzip 194 B 193 B
_error-HASH.js gzip 181 B 182 B
css-HASH.js gzip 334 B 331 B
dynamic-HASH.js gzip 1.81 kB 1.81 kB
edge-ssr-HASH.js gzip 255 B 253 B
head-HASH.js gzip 349 B 351 B
hooks-HASH.js gzip 382 B 384 B
image-HASH.js gzip 5.05 kB 5.05 kB
index-HASH.js gzip 260 B 259 B
link-HASH.js gzip 2.48 kB 2.48 kB
routerDirect..HASH.js gzip 317 B 318 B
script-HASH.js gzip 384 B 386 B
withRouter-HASH.js gzip 316 B 315 B
1afbb74e6ecf..834.css gzip 106 B 106 B
Total 12.4 kB 12.4 kB ✅ -6 B

Server

Edge SSR
Canary PR Change
edge-ssr.js gzip 128 kB 128 kB
page.js gzip 293 kB 293 kB
Total 421 kB 421 kB ✅ -426 B
Middleware
Canary PR Change
middleware-b..fest.js gzip 601 B 603 B
middleware-r..fest.js gzip 156 B 156 B
middleware.js gzip 45.5 kB 45.2 kB
edge-runtime..pack.js gzip 842 B 842 B
Total 47.1 kB 46.8 kB ✅ -282 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 699 B 699 B
Total 699 B 699 B
Build Cache
Canary PR Change
0.pack gzip 4.8 MB 4.79 MB 🟢 11.3 kB (0%)
index.pack gzip 124 kB 123 kB
index.pack.old gzip 123 kB 125 kB
Total 5.05 MB 5.04 MB ✅ -10.8 kB

🔄 Shared (bundler-independent)

Runtimes
Canary PR Change
app-page-exp...dev.js gzip 375 kB 375 kB
app-page-exp..prod.js gzip 206 kB 206 kB
app-page-tur...dev.js gzip 374 kB 374 kB
app-page-tur..prod.js gzip 206 kB 206 kB
app-page-tur...dev.js gzip 370 kB 370 kB
app-page-tur..prod.js gzip 204 kB 204 kB
app-page.run...dev.js gzip 371 kB 371 kB
app-page.run..prod.js gzip 204 kB 204 kB
app-route-ex...dev.js gzip 82.6 kB 82.6 kB
app-route-ex..prod.js gzip 55.7 kB 55.7 kB
app-route-tu...dev.js gzip 82.6 kB 82.6 kB
app-route-tu..prod.js gzip 55.7 kB 55.7 kB
app-route-tu...dev.js gzip 82.2 kB 82.2 kB
app-route-tu..prod.js gzip 55.4 kB 55.4 kB
app-route.ru...dev.js gzip 82.2 kB 82.2 kB
app-route.ru..prod.js gzip 55.4 kB 55.4 kB
dev-validati...dev.js gzip 134 kB 134 kB
dev-validati...dev.js gzip 134 kB 134 kB
dev-validati...dev.js gzip 132 kB 132 kB
dev-validati...dev.js gzip 132 kB 132 kB
dist_client_...dev.js gzip 324 B 324 B
dist_client_...dev.js gzip 326 B 326 B
dist_client_...dev.js gzip 318 B 318 B
dist_client_...dev.js gzip 317 B 317 B
pages-api-tu...dev.js gzip 46.3 kB 46.3 kB
pages-api-tu..prod.js gzip 34.3 kB 34.3 kB
pages-api.ru...dev.js gzip 46.2 kB 46.2 kB
pages-api.ru..prod.js gzip 34.3 kB 34.3 kB
pages-turbo....dev.js gzip 54.7 kB 54.7 kB
pages-turbo...prod.js gzip 39.7 kB 39.7 kB
pages.runtim...dev.js gzip 54.7 kB 54.7 kB
pages.runtim..prod.js gzip 39.6 kB 39.6 kB
server.runti..prod.js gzip 65.9 kB 65.9 kB
use-cache-pr...dev.js gzip 73.3 kB 73.3 kB
use-cache-pr...dev.js gzip 73.3 kB 73.3 kB
use-cache-pr...dev.js gzip 71.5 kB 71.5 kB
use-cache-pr...dev.js gzip 71.5 kB 71.5 kB
Total 4.1 MB 4.1 MB
📎 Tarball URL
https://vercel-packages.vercel.app/next/commits/d11681687458d20ae07cb07e9709b746dca5585b/next

Commit: d116816

@gaojude
gaojude force-pushed the evals/judge-behavior-not-style branch from f8e34ce to 447e949 Compare August 24, 2026 19:28
@gaojude

gaojude commented Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

One dependency worth knowing before this merges: 041 and 031 now rest on a judge call, and a judge call that runs longer than 60s trips vitest's worker RPC timeout (Timeout calling "onTaskUpdate"), which fails the run even though every test passed.

Measured across 141 judge calls in my runs: all 5 calls over 60s tripped it, versus 3 of 136 under. The harness sets testTimeout/hookTimeout to 900s but nothing raises the RPC timeout, so this is an agent-eval fix rather than an eval one. Filing separately; happy to hold this in draft until that lands.

Comment thread evals/evals/agent-029-use-cache-directive/EVAL.ts
@gaojude
gaojude force-pushed the evals/judge-behavior-not-style branch from 447e949 to 693a71d Compare August 25, 2026 02:44
@gaojude

gaojude commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

Updated after digging into the timeout above. Root cause is birpc's DEFAULT_TIMEOUT = 6e4, which vitest does not override for worker→main RPC and does not expose in config, so it cannot be raised from an eval or from the generated vitest config.

Measured judge durations: 029 lands at 23–55s and 041 at 11–59s, both comfortably under. 031 measured ~65s twice, so it would have failed reliably. 031 therefore keeps deterministic checks, just tolerant ones (any export form, any NextResponse.next(...) call shape, generated-not-hardcoded id), which fixes the original false negatives without depending on a judge. Only 029 and 041 are judged.

Still worth fixing upstream in agent-eval, since it hits existing evals too — agent-045 tripped it at 63s in my runs.

Both judged criteria now also point at node_modules/next/dist/docs/ for the exact version under test, so the judge can read use-cache.md/cacheTag.md/revalidateTag.md rather than infer APIs that postdate its training data.

@gaojude
gaojude force-pushed the evals/judge-behavior-not-style branch 2 times, most recently from b776e6d to ba65514 Compare August 25, 2026 03:27
@gaojude

gaojude commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

Retested the RPC ceiling on Vercel Sandbox with a fresh OIDC token, since everything before this was local Docker and that was a fair confound.

It is not a local artifact. 041's judge ran 69.7s on Vercel Sandbox and tripped the same Timeout calling "onTaskUpdate"; the harness classifier diagnosed it correctly on its own ("the EVAL test actually passed... test framework infrastructure issue, not a model or code issue"). The ceiling is total EVAL.ts runtime, hard at 60s — across 20 observations everything at or above 63.0s tripped and everything at or below 59.5s passed, no overlap.

Two things follow. 029 now makes a single judge call covering caching, tagging and invalidation together instead of two sequential ones: 76s down to 33.5s on Vercel Sandbox, comfortably clear. 041 is judge-only by nature and its judge has measured 11s to 110s, so roughly 15% of runs will land over the line until agent-eval raises the timeout — upstream has not, createVitestConfig at v2.2.0 is unchanged.

Worth noting the blast radius is small: without --ack-failures the classifier marks these infra and housekeeping deletes them, so it costs a re-run rather than recording a false model failure. I was passing --ack-failures during debugging, which is why they kept surfacing.

@gaojude

gaojude commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

Unblocked — vercel-labs/agent-eval#196 fixes the RPC ceiling, and I built it locally to verify against it.

The fix is the right one: the judge ran via spawnSync, which froze the vitest worker's event loop for the whole model run, so in-flight onTaskUpdate calls blew birpc's hardcoded 60s. Async spawn keeps the loop servicing RPC while the judge works.

Verified on Vercel Sandbox with it applied. 041's judge ran 101.6s — well past the 69.7s that failed before — and passed clean with zero onTaskUpdate hits. So 041 being judge-only is no longer a flake risk, and the ~15% failure rate I quoted earlier goes to zero once #196 lands.

With the ceiling gone I could also settle the false-negative question directly, by replaying each model's actual saved output against the new evals rather than inferring:

  • 041 against opus-5's co-located solution (0 <Suspense> in page.tsx, 4 across the section components — exactly the shape the deleted regexes rejected): passes
  • 029 against Melon's exact output (tag hoisted to a constant, getAllProducts imported relatively as './db.js' — the two things the old literals rejected): passes
  • 031 against Melon's export const proxy: NextProxy = ...: passes

The one true negative is Fable's base 031 run, which created neither proxy.ts nor middleware.ts — a real miss of the rename, correctly still failing.

This PR does not depend on #196 to be correct, but 041 will be flaky until #196 ships.

Three evals were failing correct solutions because their assertions encoded one
particular way of writing the answer rather than what the code does.

agent-041-optimize-ppr-shell already had an LLM judge, and the judge was passing
solutions that the two remaining regexes then failed: they grep app/page.tsx for
literal <Suspense> tags, so a model that co-locates each boundary inside its
section component builds fine, produces a correctly partially prerendered route,
is accepted by the judge, and still fails. Those two vetoed the judge they were
meant to replace, so they are gone and the per-section granularity they encoded
moved into the criterion.

agent-029-use-cache-directive required the literal strings cacheTag('products')
and revalidateTag('products', ...), so hoisting the key into a shared constant
failed, and looked for "lib/db" as a substring of an import, so a cached wrapper
living in lib/ and importing './db' relatively failed. Both are correct. Those
checks are judged now, in a single judge call — two sequential ones pushed the
file past the 60s worker-RPC ceiling.

Preferring revalidateTag over updateTag here is still required, since this
scenario is stale-while-revalidate, but it is judged rather than grepped. The
old source-text ban failed a solution that used revalidateTag correctly and
merely named updateTag in a comment explaining why it was the wrong API — the
exact reasoning the eval wants to see.

agent-031-proxy-middleware is scoped back to the thing it exists to measure —
that the agent creates proxy, not the deprecated middleware. It had grown checks
for the response header and for logging, which failed correct work: it demanded
a `function` declaration, so `export const proxy: NextProxy = (request) => ...`
failed, and the literal `NextResponse.next()`, so
`NextResponse.next({ request: { headers } })` failed even though it also
forwards the id onto the request. What is left is the file and handler rename,
tolerant of any export form and of src/ and .js placement per the shipped doc.
Its behaviour checks were also wrapped in `if (existsSync(proxyPath))` and
passed vacuously when proxy.ts was missing entirely.

Judged criteria state the requirement, give a correct/incorrect example pair,
and point at the docs for the exact Next.js version under test, which ship at
node_modules/next/dist/docs. These APIs postdate most training data.

Verified by replaying six recorded model solutions against the rewritten evals:
every previously-rejected correct solution now passes, and the one true negative
(a run that wrote middleware.ts) still fails.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gaojude
gaojude force-pushed the evals/judge-behavior-not-style branch from 89819b7 to 7acd798 Compare August 25, 2026 15:15
@gaojude
gaojude marked this pull request as ready for review August 25, 2026 15:58
Both were fixed on the previous branch head and reverted incidentally by
the rebuild: prettier 3.6.2 (CI's version) reflows the long RegExp line
in agent-031, and fileWith in agent-029 is unused (flagged by review).
@gaojude
gaojude enabled auto-merge (squash) August 25, 2026 19:43
gaojude added a commit that referenced this pull request Aug 25, 2026
Harness bump so the eval grader rewrite in #97826 can run against the
fixed judge.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@gaojude
gaojude merged commit f5f4ab6 into canary Aug 25, 2026
231 of 233 checks passed
@gaojude
gaojude deleted the evals/judge-behavior-not-style branch August 25, 2026 20:11
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.

2 participants