Skip to content

fix(core): pass only MCP arguments as Codex tool-call input - #366

Merged
barryroodt merged 2 commits into
mainfrom
barryroodt/ai-1269-codex-mcp-judge-input
Oct 5, 2026
Merged

barryroodt merged 2 commits into
mainfrom
barryroodt/ai-1269-codex-mcp-judge-input

Conversation

@barryroodt

@barryroodt barryroodt commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

The Codex parser put the whole MCP item, tool result included, into tool_call.input. Judges that use serializeTranscript(ctx.transcript, { includeToolCallInputs: true }) therefore got full MCP outputs (e.g. search_docs results up to ~150k chars) as input, for Codex runs only. Input is now just the call arguments, matching the Claude Code, Grok and OpenCode parsers. The result stays on the tool result.

Changes

  • packages/core/src/agents/codex/parser.ts: input is item.arguments when it is an object, else {}. Result is item.result ?? item.output ?? item.error.message. The error fallback matters because when Codex fails a call itself it sends result: null with error: {message}, and that message used to reach judges only through the whole-item input. Normalized args (path/command/url) stay {} for MCP calls, so this only changes the input/result split.
  • packages/core/src/docs-results.ts: drop the body.arguments.graphql_query fallback, which only existed for the old shape.
  • 4 new parser tests. All 4 fail against the old parser.

Effects

  • Codex judge input shrinks, so Codex judge scores may shift.
  • resolve-performance-001-slow-query-cpu-spike executedSql now sees Codex execute_sql queries.
  • Saved files under results/ keep the old shape. Nothing re-derives from them.

The before/after judge-vs-agent spend measurement shows an 85–88% cut in Codex judge spend per full pass; see the measurement comment.

Testing

  • pnpm --filter @supabase-evals/core test: 170/170 pass
  • pnpm typecheck: pass
  • pnpm format:check: pass
  • pnpm check not run as a gate: its framework smoke test is already red on main.

Linear: AI-1269

@barryroodt
barryroodt requested a review from a team as a code owner October 5, 2026 10:02
@vercel

vercel Bot commented Oct 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
evals Ignored Ignored Oct 5, 2026 10:02am UTC

Request Review

@barryroodt

Copy link
Copy Markdown
Contributor Author

Measured: on main's own configuration, this fix cuts Codex judge spend per full benchmark pass by 85–88%, with 0 fix-related verdict regressions.

Experiment Judge $/pass before → after Judge share of pass spend
codex-gpt-6-luna $0.475 → $0.059 (−87.6%) 57.0% → 14.0%
codex-gpt-6-sol $0.406 → $0.063 (−84.6%) 5.65% → 0.96%

The drop comes from judges that read tool-call inputs: median input per call went from 48,919 → 2,453 tokens (luna) and 31,822 → 2,419 (sol). Judges that don't read tool inputs stayed flat. After the fix, 0 of 381 Codex MCP calls carry the old whole-item input; before, all 358 did. An offline replay of the before transcripts through the fixed parser agrees with the live run to within 0.2 points.

Verdicts: 19 checks flipped, none attributable to the fix. 16 were deterministic checks, 2 were on a judge that never sees tool inputs, and 1 was a FAIL → PASS on luna investigate-auth-001, where a deterministic check flipped the same way.

Caveats:

  • n=1, so agent cost and deterministic verdicts vary run to run. The replay backs the judge-side result. All 56 runs completed with 0 errors.
  • Luna's judge share stays at ~14% because a luna agent run costs about $0.01, while each judge call is still ~2k tokens on gpt-6-sol. That comes from the configuration, not leftover tool output: dependent judge inputs are now the same size as independent ones.
  • Full-pass figures sum all 23 evals' own agent and judge cost. 14 were measured (every judged eval). The 9 unjudged local evals have their agent cost filled from main's published 3-run means (luna $0.195, sol $3.040 per pass) identically on both sides, so the filled values change the denominator but not the before/after difference.
  • Codex reports usage per turn, so short-context pricing is assumed.

Base cc479f2 (origin/main) + local usage logging; before = base, after = base + this PR's two commits; judge gpt-6-sol; Codex CLI 0.154.0; experiments codex-gpt-6-luna / codex-gpt-6-sol; 14 evals (every judged eval included); n=1 per eval, side and experiment; prices from the OpenAI pricing page, 2026-10-05.

@barryroodt barryroodt self-assigned this Oct 5, 2026

@mattrossman mattrossman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for finding and fixing! Judge input looks better in my local testing.

@barryroodt
barryroodt merged commit e8f3c16 into main Oct 5, 2026
7 checks passed
mattrossman added a commit that referenced this pull request Oct 6, 2026
### Motivation

We want to understand how our scorers are judging eval outputs. Today we
capture the final output of our judge, but not the input / usage
details. We don't know how much $ we spend on judge inference or where
inefficiencies are in our judge context (e.g. #366)

### Changes

Scorers now ask for verdicts through `ctx.judge()` instead of plain
`judge()`, which records each judge call's model, full prompt, output,
tokens and timing in `result.json`. The uploader logs each call as an
LLM span under the score span:

```
passed (score, purpose: scorer)
├─ gpt-6-sol (llm, purpose: scorer)
└─ gpt-6-sol (llm, purpose: scorer)
```

> [!NOTE]
> 23 of the changed eval files are just swapping `judge()` for
`ctx.judge()`


`purpose: "scorer"` is how Braintrust [keeps judge cost out of preset
cost
charts](https://braintrust.dev/docs/kb/total-llm-cost-preset-requirements#what-is-happening).
Core no longer exports `judge()` so folks are encouraged to use
`ctx.judge` as the preferred way to access the judge in scorers similar
to `ctx.query`.

As an incidental fix, the root eval span no longer logs agent tokens,
since its LLM spans carry them. Braintrust [sums metrics across every
span in a
trace](https://braintrust.dev/docs/reference/sql/query-structure#summary),
so with the initial approach in #351 the experiment table double counts
tokens
([example](https://www.braintrust.dev/app/supabase.io/p/Evals/experiments/claude-code-sonnet-5%400a50211-20261002T1454Z)).
Note `aiSdkAgent` has no per-request usage so its rows now show no
tokens, but it's unused and slated for removal anyway.

### Verification

See Braintrust
[experiment](https://www.braintrust.dev/app/supabase.io/p/Evals/experiments/codex-gpt-6-luna%408c23101-20261006T1429Z)
of the regression suite and this specific sample
[trace](https://www.braintrust.dev/app/supabase.io/p/Evals/experiments/codex-gpt-6-luna%408c23101-20261006T1429Z?r=6450ec155246034da058a5e621834383)
with LLM judge calls now visible under the scorer span.

| Timeline | Messages | Span |
|--------|--------|-----|
| <img width="2056" height="1704" alt="CleanShot 2026-10-06 at 10 33 09
AM@2x"
src="https://github.com/user-attachments/assets/e1f7a100-45ca-4716-8427-3427a133af86"
/> | <img width="1196" height="1668" alt="CleanShot 2026-10-06 at 10 33
33 AM@2x"
src="https://github.com/user-attachments/assets/e5d711db-d7da-4132-bf51-b982ba160067"
/> | <img width="874" height="320" alt="CleanShot 2026-10-06 at 10 33 46
AM@2x"
src="https://github.com/user-attachments/assets/fbe862b2-186c-4ce5-b0f3-8f678750f6b4"
/>

> [!TIP]
> Braintrust hides scorer spans by default in the Timeline view, to see
them enable "Include score spans in timeline" from the overflow menu:
> <img width="200" alt="CleanShot 2026-10-06 at 9 53 33 AM@2x"
src="https://github.com/user-attachments/assets/c21fd180-8df1-4da8-ba99-b29e820efcfb"
/>

### Future improvements

I'd like to more formally pair checks with `ctx.judge()` calls so we can
attach nice labels to these spans, e.g. `judge: wrote safe RLS policy`.
Today, we don't have that structural pairing so I've left it out of this
PR.

Closes AI-1271

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
mattrossman added a commit that referenced this pull request Oct 8, 2026
## Motivation

We have unit / e2e tests across `apps/*` and `packages/*` but don't run
them or type checks in CI, so they quietly regress (e.g. #351 broke a
Codex parser test that #366 added the same day).

## Changes

- Code Quality gets a `typecheck` job next to `format`
- A new Tests workflow runs `test`, `test-platform-lite` (which is slow
enough for its own parallel job), and `test-docker`. The Docker tests
take ~2.5 min, so they only run when a PR touches relevant files
([skipped
example](https://github.com/supabase/evals/actions/runs/37680288402/job/112994246780)).
- Refresh results commits drop `[skip ci]` (added in #332) and push with
our GH App token instead, so required checks run on them without
approval. A `run-evals-changed` refresh's [results
commit](b57f997)
ran CI as the app with no approval prompt and didn't start another
refresh.
- Upgraded to Vitest v5 so we get per-package labels on the job summary
(compare
[before](https://github.com/supabase/evals/actions/runs/37533147821) and
[after](https://github.com/supabase/evals/actions/runs/37630783292)).

| [Tests](https://github.com/supabase/evals/actions/runs/37679563185) |
[Code
Quality](https://github.com/supabase/evals/actions/runs/37679562944) |
| --- | --- |
| <img alt="CleanShot 2026-10-07 at 4 16 00 PM@2x"
src="https://github.com/user-attachments/assets/8593f06b-d45a-4576-b346-163caec4689f"
/> | <img alt="CleanShot 2026-10-07 at 3 37 37 PM@2x"
src="https://github.com/user-attachments/assets/5600cda5-208d-432c-8b18-534c6f06f7f5"
/> |

Example with tests failing
([run](https://github.com/supabase/evals/actions/runs/37666155427)):

<img alt="CleanShot 2026-10-07 at 2 24 36 PM@2x"
src="https://github.com/user-attachments/assets/47b7d635-616c-47bc-806a-428fe851c78d"
/>

I added `OPENAI_API_KEY` to [Dependabot
secrets](https://github.com/supabase/evals/settings/secrets/dependabot)
so Tests runs on Dependabot PRs. Fork PRs don't get secrets as is the
case on [supabase/mcp](https://github.com/supabase/mcp).

## Follow-ups

- After merge, require `format`, `typecheck`, `test`,
`test-platform-lite`, and `test-docker` on the [`main: review
required`](https://github.com/supabase/evals/settings/rules/18775100)
ruleset.
- AI-934: replace the framework [smoke
tests](https://github.com/supabase/evals/blob/5f8de50a5e8c8ef40d983d9aff9030dd25080a89/apps/framework/scripts/smoke-framework.ts)
with per-scorer Vitest files.
- AI-1295: cut platform-lite setup time with PGlite snapshots.

Closes AI-1121

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.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.

2 participants