From 92cea6ba3acfd1db44d248c06ab8b52f53e85410 Mon Sep 17 00:00:00 2001
From: Matt Rossman <22670878+mattrossman@users.noreply.github.com>
Date: Mon, 26 Oct 2026 10:49:25 -0400
Subject: [PATCH 01/18] chore(evals): align Claude Code benchmark presets with
current defaults
---
README.md | 6 +++---
.../README.md | 2 +-
...de-code-sonnet-5.5-no-skills.experiment.ts} | 4 ++--
...ts => claude-code-sonnet-5.5.experiment.ts} | 4 ++--
experiments/presets.ts | 8 ++++----
.../core/src/agents/claude-code/runner.test.ts | 12 ++++++------
packages/core/src/agents/claude-code/runner.ts | 2 +-
.../core/src/agents/opencode/runner.test.ts | 18 +++++++++---------
packages/core/src/index.test.ts | 4 ++--
9 files changed, 30 insertions(+), 30 deletions(-)
rename experiments/ai/{claude-code-sonnet-5-no-skills.experiment.ts => claude-code-sonnet-5.5-no-skills.experiment.ts} (63%)
rename experiments/ai/{claude-code-sonnet-5.experiment.ts => claude-code-sonnet-5.5.experiment.ts} (60%)
diff --git a/README.md b/README.md
index 521c0f499..f6994a5e9 100644
--- a/README.md
+++ b/README.md
@@ -42,7 +42,7 @@ Running evals executes experiment x eval pairs and writes local result files und
Run a single eval with one experiment:
```bash
-pnpm eval -- --eval resolve-dataapi-001-empty-results --experiment claude-code-sonnet-5
+pnpm eval -- --eval resolve-dataapi-001-empty-results --experiment claude-code-sonnet-5.5
```
@@ -50,7 +50,7 @@ Run selected evals across multiple experiments:
```bash
pnpm eval -- \
- --experiment claude-code-sonnet-5 \
+ --experiment claude-code-sonnet-5.5 \
--experiment claude-code-opus-5.5 \
--eval resolve-dataapi-001-empty-results \
--eval investigate-auth-001-deleted-user-access
@@ -64,7 +64,7 @@ Each run writes to `results///run-/result.json`. Add
```bash
pnpm eval -- --eval resolve-dataapi-001-empty-results \
- --experiment claude-code-sonnet-5 \
+ --experiment claude-code-sonnet-5.5 \
--runs 3 \
--skip-existing
```
diff --git a/evals/docs/build-docs-001-homepage-quickstart/README.md b/evals/docs/build-docs-001-homepage-quickstart/README.md
index 67210b556..0622e31e7 100644
--- a/evals/docs/build-docs-001-homepage-quickstart/README.md
+++ b/evals/docs/build-docs-001-homepage-quickstart/README.md
@@ -6,6 +6,6 @@ This guide explains how to add an eval for another AI setup prompt from the docs
2. Copy the target prompt verbatim into `PROMPT.md`. Don't paraphrase it or add detail that the prompt doesn't already give. Keep `skills: []` and `skipCliInstall: true` in the frontmatter if the prompt has the agent install its own tooling.
3. Seed `local/` with a minimal, unmodified starter that matches the guide's context. Don't include a `supabase/` directory, so the "is Supabase already initialized" step has something real to check.
4. Reuse this eval's `EVAL.ts` checks as a starting point. Adjust the checks to match what the new guide's prompt requires, including the judge rubric's wording.
-5. Validate the eval before you open a PR. Run `pnpm eval:dry -- --eval --experiment claude-code-sonnet-5` to check the setup, and then run `pnpm eval -- --eval --experiment claude-code-sonnet-5` for a full pass.
+5. Validate the eval before you open a PR. Run `pnpm eval:dry -- --eval --experiment claude-code-sonnet-5.5` to check the setup, and then run `pnpm eval -- --eval --experiment claude-code-sonnet-5.5` for a full pass.
For the full eval-authoring workflow, including frontmatter fields, suite selection, and submitting for review, see `CONTRIBUTING.md`.
diff --git a/experiments/ai/claude-code-sonnet-5-no-skills.experiment.ts b/experiments/ai/claude-code-sonnet-5.5-no-skills.experiment.ts
similarity index 63%
rename from experiments/ai/claude-code-sonnet-5-no-skills.experiment.ts
rename to experiments/ai/claude-code-sonnet-5.5-no-skills.experiment.ts
index df5f5b70a..3e4f69924 100644
--- a/experiments/ai/claude-code-sonnet-5-no-skills.experiment.ts
+++ b/experiments/ai/claude-code-sonnet-5.5-no-skills.experiment.ts
@@ -1,8 +1,8 @@
import { defineExperiment } from '@supabase-evals/core';
-import { claudeCodeSonnet5 } from '../presets.js';
+import { claudeCodeSonnet55 } from '../presets.js';
export default defineExperiment({
- ...claudeCodeSonnet5,
+ ...claudeCodeSonnet55,
suite: ['no-skills'],
skills: [],
});
diff --git a/experiments/ai/claude-code-sonnet-5.experiment.ts b/experiments/ai/claude-code-sonnet-5.5.experiment.ts
similarity index 60%
rename from experiments/ai/claude-code-sonnet-5.experiment.ts
rename to experiments/ai/claude-code-sonnet-5.5.experiment.ts
index da35fac1f..6eee1d6f3 100644
--- a/experiments/ai/claude-code-sonnet-5.experiment.ts
+++ b/experiments/ai/claude-code-sonnet-5.5.experiment.ts
@@ -1,7 +1,7 @@
import { defineExperiment } from '@supabase-evals/core';
-import { claudeCodeSonnet5 } from '../presets.js';
+import { claudeCodeSonnet55 } from '../presets.js';
export default defineExperiment({
- ...claudeCodeSonnet5,
+ ...claudeCodeSonnet55,
suite: ['benchmark'],
});
diff --git a/experiments/presets.ts b/experiments/presets.ts
index 0e62f846c..991c68d93 100644
--- a/experiments/presets.ts
+++ b/experiments/presets.ts
@@ -25,17 +25,17 @@ function defaultRuntime() {
export const claudeCodeOpus55 = {
agent: claudeCodeAgent({
model: 'claude-opus-5-5',
- reasoningEffort: 'high',
+ reasoningEffort: 'medium',
}),
runtime: defaultRuntime(),
localStack: localStackRuntime(),
skills,
} satisfies ExperimentPreset;
-export const claudeCodeSonnet5 = {
+export const claudeCodeSonnet55 = {
agent: claudeCodeAgent({
- model: 'claude-sonnet-5',
- reasoningEffort: 'high',
+ model: 'claude-sonnet-5-5',
+ reasoningEffort: 'medium',
}),
runtime: defaultRuntime(),
localStack: localStackRuntime(),
diff --git a/packages/core/src/agents/claude-code/runner.test.ts b/packages/core/src/agents/claude-code/runner.test.ts
index 075f0f19d..a8ba68ced 100644
--- a/packages/core/src/agents/claude-code/runner.test.ts
+++ b/packages/core/src/agents/claude-code/runner.test.ts
@@ -97,7 +97,7 @@ describe('claudeCodeRunner.extractUsage', () => {
subtype: 'success',
usage: { input_tokens: 10, output_tokens: 40 },
modelUsage: {
- 'claude-sonnet-5': {
+ 'claude-sonnet-5-5': {
inputTokens: 10,
cacheCreationInputTokens: 200,
cacheReadInputTokens: 3000,
@@ -114,9 +114,9 @@ describe('claudeCodeRunner.extractUsage', () => {
},
}),
].join('\n');
- expect(extract(raw, 'claude-sonnet-5')).toEqual([
+ expect(extract(raw, 'claude-sonnet-5-5')).toEqual([
{
- model: 'claude-sonnet-5',
+ model: 'claude-sonnet-5-5',
inputTokens: 3210,
cacheReadInputTokens: 3000,
cacheWriteInputTokens: 200,
@@ -133,12 +133,12 @@ describe('claudeCodeRunner.extractUsage', () => {
});
it('returns undefined without modelUsage', () => {
- expect(extract(undefined, 'claude-sonnet-5')).toBeUndefined();
- expect(extract('not json\n', 'claude-sonnet-5')).toBeUndefined();
+ expect(extract(undefined, 'claude-sonnet-5-5')).toBeUndefined();
+ expect(extract('not json\n', 'claude-sonnet-5-5')).toBeUndefined();
expect(
extract(
JSON.stringify({ type: 'result', subtype: 'success' }),
- 'claude-sonnet-5'
+ 'claude-sonnet-5-5'
)
).toBeUndefined();
});
diff --git a/packages/core/src/agents/claude-code/runner.ts b/packages/core/src/agents/claude-code/runner.ts
index dd37dbf60..cba2928ea 100644
--- a/packages/core/src/agents/claude-code/runner.ts
+++ b/packages/core/src/agents/claude-code/runner.ts
@@ -26,7 +26,7 @@ export const claudeCodeRunner: AgentRunner = {
cliPackage: '@anthropic-ai/claude-code',
// Pinned: Claude Code's transcript format evolves; bump deliberately and
// re-check the parser. See ./parser.ts.
- defaultCliVersion: '2.1.280',
+ defaultCliVersion: '2.1.284',
defaultModel: 'claude-sonnet-4-6',
sessionDir: '"$HOME/.claude/projects"',
diff --git a/packages/core/src/agents/opencode/runner.test.ts b/packages/core/src/agents/opencode/runner.test.ts
index a40bfef53..f2fff3781 100644
--- a/packages/core/src/agents/opencode/runner.test.ts
+++ b/packages/core/src/agents/opencode/runner.test.ts
@@ -21,7 +21,7 @@ const SESSION = [
describe('opencode runner', () => {
it("resolves the results-metadata provider from the slug's vendor prefix", () => {
- expect(providerForModel('anthropic/claude-sonnet-5')).toBe('anthropic');
+ expect(providerForModel('anthropic/claude-sonnet-5-5')).toBe('anthropic');
expect(providerForModel('openai/gpt-5.4')).toBe('openai');
expect(providerForModel('moonshotai/kimi-k3')).toBe('moonshotai');
});
@@ -40,7 +40,7 @@ describe('opencode runner', () => {
});
it('deriveStopReason reads the terminal step_finish reason', () => {
- const runner = createOpencodeRunner('anthropic/claude-sonnet-5');
+ const runner = createOpencodeRunner('anthropic/claude-sonnet-5-5');
const ok = { ok: true, exitCode: 0, stdout: '', stderr: '' };
expect(runner.deriveStopReason!(SESSION, ok)).toBe('stop');
// A non-stop terminal reason is surfaced verbatim.
@@ -84,7 +84,7 @@ describe('opencode runner', () => {
});
describe('opencode runner extractUsage', () => {
- const extract = createOpencodeRunner('anthropic/claude-sonnet-5')
+ const extract = createOpencodeRunner('anthropic/claude-sonnet-5-5')
.extractUsage!;
it('adds cache into input and reasoning into output', () => {
@@ -117,9 +117,9 @@ describe('opencode runner extractUsage', () => {
},
}),
].join('\n');
- expect(extract(raw, 'anthropic/claude-sonnet-5')).toEqual([
+ expect(extract(raw, 'anthropic/claude-sonnet-5-5')).toEqual([
{
- model: 'anthropic/claude-sonnet-5',
+ model: 'anthropic/claude-sonnet-5-5',
inputTokens: 440,
cacheReadInputTokens: 130,
cacheWriteInputTokens: 10,
@@ -129,15 +129,15 @@ describe('opencode runner extractUsage', () => {
});
it('returns undefined without usage', () => {
- expect(extract(undefined, 'anthropic/claude-sonnet-5')).toBeUndefined();
- expect(extract('not json\n', 'anthropic/claude-sonnet-5')).toBeUndefined();
+ expect(extract(undefined, 'anthropic/claude-sonnet-5-5')).toBeUndefined();
+ expect(extract('not json\n', 'anthropic/claude-sonnet-5-5')).toBeUndefined();
expect(
extract(
JSON.stringify({
type: 'step_finish',
part: { type: 'step-finish', reason: 'stop' },
}),
- 'anthropic/claude-sonnet-5'
+ 'anthropic/claude-sonnet-5-5'
)
).toBeUndefined();
});
@@ -213,7 +213,7 @@ describe('opencode runner exec routing', () => {
});
describe('opencode runner extractStepCount', () => {
- const extract = createOpencodeRunner('anthropic/claude-sonnet-5')
+ const extract = createOpencodeRunner('anthropic/claude-sonnet-5-5')
.extractStepCount!;
it('counts step_finish records', () => {
diff --git a/packages/core/src/index.test.ts b/packages/core/src/index.test.ts
index 64e05d3a8..c7a8ca89d 100644
--- a/packages/core/src/index.test.ts
+++ b/packages/core/src/index.test.ts
@@ -9,7 +9,7 @@ describe('aiSdkAgent usage', () => {
vi.stubEnv('ANTHROPIC_API_KEY', 'test'); // to satisfy `assertProviderReady()`
const model = new MockLanguageModelV3({
provider: 'anthropic.messages',
- modelId: 'claude-sonnet-5',
+ modelId: 'claude-sonnet-5-5',
doGenerate: async () => ({
content: [{ type: 'text', text: 'Done.' }],
finishReason: { unified: 'stop', raw: 'stop' },
@@ -34,7 +34,7 @@ describe('aiSdkAgent usage', () => {
expect(run.usage).toEqual([
{
- model: 'claude-sonnet-5',
+ model: 'claude-sonnet-5-5',
inputTokens: 3210,
cacheReadInputTokens: 3000,
cacheWriteInputTokens: 200,
From 24491af7fedbbfe2f984c31c7a708e51222487a2 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Mon, 26 Oct 2026 14:57:23 +0000
Subject: [PATCH 02/18] chore: refresh eval results
---
apps/web/src/data/eval-results.json | 309 ++++++++++++----------------
1 file changed, 127 insertions(+), 182 deletions(-)
diff --git a/apps/web/src/data/eval-results.json b/apps/web/src/data/eval-results.json
index ce7cde21e..8b233cbff 100644
--- a/apps/web/src/data/eval-results.json
+++ b/apps/web/src/data/eval-results.json
@@ -5697,7 +5697,7 @@
"agent": "claude-code",
"modelProvider": "anthropic",
"modelId": "claude-opus-5-5",
- "reasoningEffort": "high"
+ "reasoningEffort": "medium"
},
"eval": "resolve-dataapi-001-empty-results",
"stage": "resolve",
@@ -5741,7 +5741,7 @@
{
"name": "diagnosed RLS and added owner-scoped policies",
"passed": true,
- "judgeNotes": "Diagnosed RLS deny-all, kept RLS enabled, and created authenticated SELECT and INSERT policies scoped to each user's bookmarks."
+ "judgeNotes": "Correctly diagnoses RLS enabled without policies as deny-all, creates authenticated-only owner-scoped SELECT and INSERT policies using auth.uid(), and leaves RLS enabled."
}
],
"skills": {
@@ -5749,9 +5749,7 @@
"supabase",
"supabase-postgres-best-practices"
],
- "loaded": [
- "supabase"
- ]
+ "loaded": []
},
"docs": {
"calls": []
@@ -5759,193 +5757,29 @@
"usage": [
{
"model": "claude-opus-5-5",
- "inputTokens": 291639,
- "cacheReadInputTokens": 270261,
- "cacheWriteInputTokens": 21356,
- "outputTokens": 5474
+ "inputTokens": 296229,
+ "cacheReadInputTokens": 279491,
+ "cacheWriteInputTokens": 16712,
+ "outputTokens": 4205
}
],
- "stepCount": 17,
+ "stepCount": 16,
"toolCallCount": 15,
- "agentRunDurationMs": 60110,
+ "agentRunDurationMs": 49585,
"sandboxUsage": {
- "memory": 8192
+ "activeCpuDurationMs": 155711,
+ "duration": 169764,
+ "memory": 8192,
+ "networkTransfer": {
+ "ingress": 721169174,
+ "egress": 2570080
+ }
},
"prompt": "Our app lets signed-in users save bookmarks and view them on their dashboard. Bookmarks are stored in the `bookmarks` table and are private — a user must only ever see their own. \nUsers also need to be able to save new bookmarks from the app.\n\nI can see the rows when I query the table directly, but the dashboard shows an empty list for every user.\n\nFind out why the Data API returns nothing and fix it.",
"promptSourcePath": "evals/benchmark/resolve-dataapi-001-empty-results/PROMPT.md",
"run": 1,
"sourcePath": "claude-code-opus-5.5/resolve-dataapi-001-empty-results/run-1/result.json"
},
- {
- "experiment": "claude-code-opus-5.5",
- "experimentSuite": "benchmark",
- "experimentDisplay": {
- "agent": "claude-code",
- "modelProvider": "anthropic",
- "modelId": "claude-opus-5-5",
- "reasoningEffort": "high"
- },
- "eval": "resolve-dataapi-001-empty-results",
- "stage": "resolve",
- "product": [
- "data-api",
- "database",
- "auth"
- ],
- "topic": [
- "rls",
- "sdk"
- ],
- "suite": "benchmark",
- "interface": "mcp",
- "passed": true,
- "checks": [
- {
- "name": "RLS still enabled on bookmarks",
- "passed": true
- },
- {
- "name": "user A reads own bookmarks",
- "passed": true
- },
- {
- "name": "user B cannot read user A bookmarks",
- "passed": true
- },
- {
- "name": "anon reads no bookmarks",
- "passed": true
- },
- {
- "name": "user A can save a new bookmark",
- "passed": true
- },
- {
- "name": "user B cannot insert a bookmark as user A",
- "passed": true
- },
- {
- "name": "diagnosed RLS and added owner-scoped policies",
- "passed": true,
- "judgeNotes": "Diagnosed RLS with no policies as the cause of empty Data API results, created owner-scoped SELECT and INSERT policies for authenticated users, kept RLS enabled, and verified access."
- }
- ],
- "skills": {
- "available": [
- "supabase",
- "supabase-postgres-best-practices"
- ],
- "loaded": [
- "supabase"
- ]
- },
- "docs": {
- "calls": []
- },
- "usage": [
- {
- "model": "claude-opus-5-5",
- "inputTokens": 315506,
- "cacheReadInputTokens": 293993,
- "cacheWriteInputTokens": 21489,
- "outputTokens": 5299
- }
- ],
- "stepCount": 18,
- "toolCallCount": 16,
- "agentRunDurationMs": 66314,
- "sandboxUsage": {
- "memory": 8192
- },
- "prompt": "Our app lets signed-in users save bookmarks and view them on their dashboard. Bookmarks are stored in the `bookmarks` table and are private — a user must only ever see their own. \nUsers also need to be able to save new bookmarks from the app.\n\nI can see the rows when I query the table directly, but the dashboard shows an empty list for every user.\n\nFind out why the Data API returns nothing and fix it.",
- "promptSourcePath": "evals/benchmark/resolve-dataapi-001-empty-results/PROMPT.md",
- "run": 2,
- "sourcePath": "claude-code-opus-5.5/resolve-dataapi-001-empty-results/run-2/result.json"
- },
- {
- "experiment": "claude-code-opus-5.5",
- "experimentSuite": "benchmark",
- "experimentDisplay": {
- "agent": "claude-code",
- "modelProvider": "anthropic",
- "modelId": "claude-opus-5-5",
- "reasoningEffort": "high"
- },
- "eval": "resolve-dataapi-001-empty-results",
- "stage": "resolve",
- "product": [
- "data-api",
- "database",
- "auth"
- ],
- "topic": [
- "rls",
- "sdk"
- ],
- "suite": "benchmark",
- "interface": "mcp",
- "passed": true,
- "checks": [
- {
- "name": "RLS still enabled on bookmarks",
- "passed": true
- },
- {
- "name": "user A reads own bookmarks",
- "passed": true
- },
- {
- "name": "user B cannot read user A bookmarks",
- "passed": true
- },
- {
- "name": "anon reads no bookmarks",
- "passed": true
- },
- {
- "name": "user A can save a new bookmark",
- "passed": true
- },
- {
- "name": "user B cannot insert a bookmark as user A",
- "passed": true
- },
- {
- "name": "diagnosed RLS and added owner-scoped policies",
- "passed": true,
- "judgeNotes": "Correctly diagnosed deny-all RLS, created authenticated-only owner-scoped SELECT and INSERT policies, and kept RLS enabled."
- }
- ],
- "skills": {
- "available": [
- "supabase",
- "supabase-postgres-best-practices"
- ],
- "loaded": []
- },
- "docs": {
- "calls": []
- },
- "usage": [
- {
- "model": "claude-opus-5-5",
- "inputTokens": 323324,
- "cacheReadInputTokens": 305485,
- "cacheWriteInputTokens": 17811,
- "outputTokens": 5414
- }
- ],
- "stepCount": 17,
- "toolCallCount": 16,
- "agentRunDurationMs": 59898,
- "sandboxUsage": {
- "memory": 8192
- },
- "prompt": "Our app lets signed-in users save bookmarks and view them on their dashboard. Bookmarks are stored in the `bookmarks` table and are private — a user must only ever see their own. \nUsers also need to be able to save new bookmarks from the app.\n\nI can see the rows when I query the table directly, but the dashboard shows an empty list for every user.\n\nFind out why the Data API returns nothing and fix it.",
- "promptSourcePath": "evals/benchmark/resolve-dataapi-001-empty-results/PROMPT.md",
- "run": 3,
- "sourcePath": "claude-code-opus-5.5/resolve-dataapi-001-empty-results/run-3/result.json"
- },
{
"experiment": "claude-code-opus-5.5",
"experimentSuite": "benchmark",
@@ -24644,6 +24478,117 @@
"run": 3,
"sourcePath": "claude-code-sonnet-5-no-skills/resolve-security-002-rls-cross-tenant-leak/run-3/result.json"
},
+ {
+ "experiment": "claude-code-sonnet-5.5",
+ "experimentSuite": "benchmark",
+ "experimentDisplay": {
+ "agent": "claude-code",
+ "modelProvider": "anthropic",
+ "modelId": "claude-sonnet-5-5",
+ "reasoningEffort": "medium"
+ },
+ "eval": "resolve-dataapi-001-empty-results",
+ "stage": "resolve",
+ "product": [
+ "data-api",
+ "database",
+ "auth"
+ ],
+ "topic": [
+ "rls",
+ "sdk"
+ ],
+ "suite": "benchmark",
+ "interface": "mcp",
+ "passed": true,
+ "checks": [
+ {
+ "name": "RLS still enabled on bookmarks",
+ "passed": true
+ },
+ {
+ "name": "user A reads own bookmarks",
+ "passed": true
+ },
+ {
+ "name": "user B cannot read user A bookmarks",
+ "passed": true
+ },
+ {
+ "name": "anon reads no bookmarks",
+ "passed": true
+ },
+ {
+ "name": "user A can save a new bookmark",
+ "passed": true
+ },
+ {
+ "name": "user B cannot insert a bookmark as user A",
+ "passed": true
+ },
+ {
+ "name": "diagnosed RLS and added owner-scoped policies",
+ "passed": true,
+ "judgeNotes": "Diagnosed RLS enabled without policies as deny-all, created authenticated-only owner-scoped SELECT and INSERT policies, and kept RLS enabled. Also tested ownership isolation and rejection of inserts for another user."
+ }
+ ],
+ "skills": {
+ "available": [
+ "supabase",
+ "supabase-postgres-best-practices"
+ ],
+ "loaded": [
+ "supabase"
+ ]
+ },
+ "docs": {
+ "calls": [
+ {
+ "source": "web_fetch",
+ "query": "Summarize debugging guidance for empty results / RLS / permissions",
+ "hasContent": true,
+ "pages": [
+ {
+ "url": "https://supabase.com/docs/guides/monitoring-and-debugging.md"
+ }
+ ],
+ "resultChars": 1392
+ }
+ ]
+ },
+ "usage": [
+ {
+ "model": "claude-sonnet-5-5",
+ "inputTokens": 164108,
+ "cacheReadInputTokens": 147998,
+ "cacheWriteInputTokens": 16096,
+ "outputTokens": 2430
+ },
+ {
+ "model": "claude-haiku-4-5-20251001",
+ "inputTokens": 1091,
+ "cacheReadInputTokens": 0,
+ "cacheWriteInputTokens": 0,
+ "outputTokens": 292
+ }
+ ],
+ "stepCount": 12,
+ "toolCallCount": 10,
+ "agentRunDurationMs": 23457,
+ "sandboxUsage": {
+ "activeCpuDurationMs": 117336,
+ "duration": 121806,
+ "memory": 8192,
+ "networkTransfer": {
+ "ingress": 723349609,
+ "egress": 3870800
+ }
+ },
+ "prompt": "Our app lets signed-in users save bookmarks and view them on their dashboard. Bookmarks are stored in the `bookmarks` table and are private — a user must only ever see their own. \nUsers also need to be able to save new bookmarks from the app.\n\nI can see the rows when I query the table directly, but the dashboard shows an empty list for every user.\n\nFind out why the Data API returns nothing and fix it.",
+ "promptSourcePath": "evals/benchmark/resolve-dataapi-001-empty-results/PROMPT.md",
+ "run": 1,
+ "sourcePath": "claude-code-sonnet-5.5/resolve-dataapi-001-empty-results/run-1/result.json"
+ },
{
"experiment": "codex-gpt-6-luna",
"experimentSuite": "benchmark",
From 6d1d4126c6d215a6d2587835caa65ba789b9f4b6 Mon Sep 17 00:00:00 2001
From: Matt Rossman <22670878+mattrossman@users.noreply.github.com>
Date: Mon, 26 Oct 2026 10:58:10 -0400
Subject: [PATCH 03/18] Revert "chore: refresh eval results"
This reverts commit 24491af7fedbbfe2f984c31c7a708e51222487a2.
---
apps/web/src/data/eval-results.json | 309 ++++++++++++++++------------
1 file changed, 182 insertions(+), 127 deletions(-)
diff --git a/apps/web/src/data/eval-results.json b/apps/web/src/data/eval-results.json
index 8b233cbff..ce7cde21e 100644
--- a/apps/web/src/data/eval-results.json
+++ b/apps/web/src/data/eval-results.json
@@ -5697,7 +5697,7 @@
"agent": "claude-code",
"modelProvider": "anthropic",
"modelId": "claude-opus-5-5",
- "reasoningEffort": "medium"
+ "reasoningEffort": "high"
},
"eval": "resolve-dataapi-001-empty-results",
"stage": "resolve",
@@ -5741,7 +5741,7 @@
{
"name": "diagnosed RLS and added owner-scoped policies",
"passed": true,
- "judgeNotes": "Correctly diagnoses RLS enabled without policies as deny-all, creates authenticated-only owner-scoped SELECT and INSERT policies using auth.uid(), and leaves RLS enabled."
+ "judgeNotes": "Diagnosed RLS deny-all, kept RLS enabled, and created authenticated SELECT and INSERT policies scoped to each user's bookmarks."
}
],
"skills": {
@@ -5749,7 +5749,9 @@
"supabase",
"supabase-postgres-best-practices"
],
- "loaded": []
+ "loaded": [
+ "supabase"
+ ]
},
"docs": {
"calls": []
@@ -5757,29 +5759,193 @@
"usage": [
{
"model": "claude-opus-5-5",
- "inputTokens": 296229,
- "cacheReadInputTokens": 279491,
- "cacheWriteInputTokens": 16712,
- "outputTokens": 4205
+ "inputTokens": 291639,
+ "cacheReadInputTokens": 270261,
+ "cacheWriteInputTokens": 21356,
+ "outputTokens": 5474
}
],
- "stepCount": 16,
+ "stepCount": 17,
"toolCallCount": 15,
- "agentRunDurationMs": 49585,
+ "agentRunDurationMs": 60110,
"sandboxUsage": {
- "activeCpuDurationMs": 155711,
- "duration": 169764,
- "memory": 8192,
- "networkTransfer": {
- "ingress": 721169174,
- "egress": 2570080
- }
+ "memory": 8192
},
"prompt": "Our app lets signed-in users save bookmarks and view them on their dashboard. Bookmarks are stored in the `bookmarks` table and are private — a user must only ever see their own. \nUsers also need to be able to save new bookmarks from the app.\n\nI can see the rows when I query the table directly, but the dashboard shows an empty list for every user.\n\nFind out why the Data API returns nothing and fix it.",
"promptSourcePath": "evals/benchmark/resolve-dataapi-001-empty-results/PROMPT.md",
"run": 1,
"sourcePath": "claude-code-opus-5.5/resolve-dataapi-001-empty-results/run-1/result.json"
},
+ {
+ "experiment": "claude-code-opus-5.5",
+ "experimentSuite": "benchmark",
+ "experimentDisplay": {
+ "agent": "claude-code",
+ "modelProvider": "anthropic",
+ "modelId": "claude-opus-5-5",
+ "reasoningEffort": "high"
+ },
+ "eval": "resolve-dataapi-001-empty-results",
+ "stage": "resolve",
+ "product": [
+ "data-api",
+ "database",
+ "auth"
+ ],
+ "topic": [
+ "rls",
+ "sdk"
+ ],
+ "suite": "benchmark",
+ "interface": "mcp",
+ "passed": true,
+ "checks": [
+ {
+ "name": "RLS still enabled on bookmarks",
+ "passed": true
+ },
+ {
+ "name": "user A reads own bookmarks",
+ "passed": true
+ },
+ {
+ "name": "user B cannot read user A bookmarks",
+ "passed": true
+ },
+ {
+ "name": "anon reads no bookmarks",
+ "passed": true
+ },
+ {
+ "name": "user A can save a new bookmark",
+ "passed": true
+ },
+ {
+ "name": "user B cannot insert a bookmark as user A",
+ "passed": true
+ },
+ {
+ "name": "diagnosed RLS and added owner-scoped policies",
+ "passed": true,
+ "judgeNotes": "Diagnosed RLS with no policies as the cause of empty Data API results, created owner-scoped SELECT and INSERT policies for authenticated users, kept RLS enabled, and verified access."
+ }
+ ],
+ "skills": {
+ "available": [
+ "supabase",
+ "supabase-postgres-best-practices"
+ ],
+ "loaded": [
+ "supabase"
+ ]
+ },
+ "docs": {
+ "calls": []
+ },
+ "usage": [
+ {
+ "model": "claude-opus-5-5",
+ "inputTokens": 315506,
+ "cacheReadInputTokens": 293993,
+ "cacheWriteInputTokens": 21489,
+ "outputTokens": 5299
+ }
+ ],
+ "stepCount": 18,
+ "toolCallCount": 16,
+ "agentRunDurationMs": 66314,
+ "sandboxUsage": {
+ "memory": 8192
+ },
+ "prompt": "Our app lets signed-in users save bookmarks and view them on their dashboard. Bookmarks are stored in the `bookmarks` table and are private — a user must only ever see their own. \nUsers also need to be able to save new bookmarks from the app.\n\nI can see the rows when I query the table directly, but the dashboard shows an empty list for every user.\n\nFind out why the Data API returns nothing and fix it.",
+ "promptSourcePath": "evals/benchmark/resolve-dataapi-001-empty-results/PROMPT.md",
+ "run": 2,
+ "sourcePath": "claude-code-opus-5.5/resolve-dataapi-001-empty-results/run-2/result.json"
+ },
+ {
+ "experiment": "claude-code-opus-5.5",
+ "experimentSuite": "benchmark",
+ "experimentDisplay": {
+ "agent": "claude-code",
+ "modelProvider": "anthropic",
+ "modelId": "claude-opus-5-5",
+ "reasoningEffort": "high"
+ },
+ "eval": "resolve-dataapi-001-empty-results",
+ "stage": "resolve",
+ "product": [
+ "data-api",
+ "database",
+ "auth"
+ ],
+ "topic": [
+ "rls",
+ "sdk"
+ ],
+ "suite": "benchmark",
+ "interface": "mcp",
+ "passed": true,
+ "checks": [
+ {
+ "name": "RLS still enabled on bookmarks",
+ "passed": true
+ },
+ {
+ "name": "user A reads own bookmarks",
+ "passed": true
+ },
+ {
+ "name": "user B cannot read user A bookmarks",
+ "passed": true
+ },
+ {
+ "name": "anon reads no bookmarks",
+ "passed": true
+ },
+ {
+ "name": "user A can save a new bookmark",
+ "passed": true
+ },
+ {
+ "name": "user B cannot insert a bookmark as user A",
+ "passed": true
+ },
+ {
+ "name": "diagnosed RLS and added owner-scoped policies",
+ "passed": true,
+ "judgeNotes": "Correctly diagnosed deny-all RLS, created authenticated-only owner-scoped SELECT and INSERT policies, and kept RLS enabled."
+ }
+ ],
+ "skills": {
+ "available": [
+ "supabase",
+ "supabase-postgres-best-practices"
+ ],
+ "loaded": []
+ },
+ "docs": {
+ "calls": []
+ },
+ "usage": [
+ {
+ "model": "claude-opus-5-5",
+ "inputTokens": 323324,
+ "cacheReadInputTokens": 305485,
+ "cacheWriteInputTokens": 17811,
+ "outputTokens": 5414
+ }
+ ],
+ "stepCount": 17,
+ "toolCallCount": 16,
+ "agentRunDurationMs": 59898,
+ "sandboxUsage": {
+ "memory": 8192
+ },
+ "prompt": "Our app lets signed-in users save bookmarks and view them on their dashboard. Bookmarks are stored in the `bookmarks` table and are private — a user must only ever see their own. \nUsers also need to be able to save new bookmarks from the app.\n\nI can see the rows when I query the table directly, but the dashboard shows an empty list for every user.\n\nFind out why the Data API returns nothing and fix it.",
+ "promptSourcePath": "evals/benchmark/resolve-dataapi-001-empty-results/PROMPT.md",
+ "run": 3,
+ "sourcePath": "claude-code-opus-5.5/resolve-dataapi-001-empty-results/run-3/result.json"
+ },
{
"experiment": "claude-code-opus-5.5",
"experimentSuite": "benchmark",
@@ -24478,117 +24644,6 @@
"run": 3,
"sourcePath": "claude-code-sonnet-5-no-skills/resolve-security-002-rls-cross-tenant-leak/run-3/result.json"
},
- {
- "experiment": "claude-code-sonnet-5.5",
- "experimentSuite": "benchmark",
- "experimentDisplay": {
- "agent": "claude-code",
- "modelProvider": "anthropic",
- "modelId": "claude-sonnet-5-5",
- "reasoningEffort": "medium"
- },
- "eval": "resolve-dataapi-001-empty-results",
- "stage": "resolve",
- "product": [
- "data-api",
- "database",
- "auth"
- ],
- "topic": [
- "rls",
- "sdk"
- ],
- "suite": "benchmark",
- "interface": "mcp",
- "passed": true,
- "checks": [
- {
- "name": "RLS still enabled on bookmarks",
- "passed": true
- },
- {
- "name": "user A reads own bookmarks",
- "passed": true
- },
- {
- "name": "user B cannot read user A bookmarks",
- "passed": true
- },
- {
- "name": "anon reads no bookmarks",
- "passed": true
- },
- {
- "name": "user A can save a new bookmark",
- "passed": true
- },
- {
- "name": "user B cannot insert a bookmark as user A",
- "passed": true
- },
- {
- "name": "diagnosed RLS and added owner-scoped policies",
- "passed": true,
- "judgeNotes": "Diagnosed RLS enabled without policies as deny-all, created authenticated-only owner-scoped SELECT and INSERT policies, and kept RLS enabled. Also tested ownership isolation and rejection of inserts for another user."
- }
- ],
- "skills": {
- "available": [
- "supabase",
- "supabase-postgres-best-practices"
- ],
- "loaded": [
- "supabase"
- ]
- },
- "docs": {
- "calls": [
- {
- "source": "web_fetch",
- "query": "Summarize debugging guidance for empty results / RLS / permissions",
- "hasContent": true,
- "pages": [
- {
- "url": "https://supabase.com/docs/guides/monitoring-and-debugging.md"
- }
- ],
- "resultChars": 1392
- }
- ]
- },
- "usage": [
- {
- "model": "claude-sonnet-5-5",
- "inputTokens": 164108,
- "cacheReadInputTokens": 147998,
- "cacheWriteInputTokens": 16096,
- "outputTokens": 2430
- },
- {
- "model": "claude-haiku-4-5-20251001",
- "inputTokens": 1091,
- "cacheReadInputTokens": 0,
- "cacheWriteInputTokens": 0,
- "outputTokens": 292
- }
- ],
- "stepCount": 12,
- "toolCallCount": 10,
- "agentRunDurationMs": 23457,
- "sandboxUsage": {
- "activeCpuDurationMs": 117336,
- "duration": 121806,
- "memory": 8192,
- "networkTransfer": {
- "ingress": 723349609,
- "egress": 3870800
- }
- },
- "prompt": "Our app lets signed-in users save bookmarks and view them on their dashboard. Bookmarks are stored in the `bookmarks` table and are private — a user must only ever see their own. \nUsers also need to be able to save new bookmarks from the app.\n\nI can see the rows when I query the table directly, but the dashboard shows an empty list for every user.\n\nFind out why the Data API returns nothing and fix it.",
- "promptSourcePath": "evals/benchmark/resolve-dataapi-001-empty-results/PROMPT.md",
- "run": 1,
- "sourcePath": "claude-code-sonnet-5.5/resolve-dataapi-001-empty-results/run-1/result.json"
- },
{
"experiment": "codex-gpt-6-luna",
"experimentSuite": "benchmark",
From 47cb1b7611bc6b44cab71319529e204813d410d3 Mon Sep 17 00:00:00 2001
From: Matt Rossman <22670878+mattrossman@users.noreply.github.com>
Date: Mon, 26 Oct 2026 11:16:30 -0400
Subject: [PATCH 04/18] chore(evals): bump pinned Claude Code CLI to 2.1.310
---
packages/core/src/agents/claude-code/runner.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/core/src/agents/claude-code/runner.ts b/packages/core/src/agents/claude-code/runner.ts
index cba2928ea..ed031e932 100644
--- a/packages/core/src/agents/claude-code/runner.ts
+++ b/packages/core/src/agents/claude-code/runner.ts
@@ -26,7 +26,7 @@ export const claudeCodeRunner: AgentRunner = {
cliPackage: '@anthropic-ai/claude-code',
// Pinned: Claude Code's transcript format evolves; bump deliberately and
// re-check the parser. See ./parser.ts.
- defaultCliVersion: '2.1.284',
+ defaultCliVersion: '2.1.310',
defaultModel: 'claude-sonnet-4-6',
sessionDir: '"$HOME/.claude/projects"',
From 1d09dd8b74f75d88c0ef40c7c96cc9f2b4dc9fc6 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Mon, 26 Oct 2026 15:20:55 +0000
Subject: [PATCH 05/18] chore: refresh eval results
---
apps/web/src/data/eval-results.json | 268 +++++++++-------------------
1 file changed, 85 insertions(+), 183 deletions(-)
diff --git a/apps/web/src/data/eval-results.json b/apps/web/src/data/eval-results.json
index ce7cde21e..d8f3c5df7 100644
--- a/apps/web/src/data/eval-results.json
+++ b/apps/web/src/data/eval-results.json
@@ -5697,7 +5697,7 @@
"agent": "claude-code",
"modelProvider": "anthropic",
"modelId": "claude-opus-5-5",
- "reasoningEffort": "high"
+ "reasoningEffort": "medium"
},
"eval": "resolve-dataapi-001-empty-results",
"stage": "resolve",
@@ -5741,7 +5741,7 @@
{
"name": "diagnosed RLS and added owner-scoped policies",
"passed": true,
- "judgeNotes": "Diagnosed RLS deny-all, kept RLS enabled, and created authenticated SELECT and INSERT policies scoped to each user's bookmarks."
+ "judgeNotes": "Diagnosed RLS enabled with no policies as deny-all. Created authenticated-only SELECT USING and INSERT WITH CHECK policies matching user_id to auth.uid(), and kept RLS enabled."
}
],
"skills": {
@@ -5749,9 +5749,7 @@
"supabase",
"supabase-postgres-best-practices"
],
- "loaded": [
- "supabase"
- ]
+ "loaded": []
},
"docs": {
"calls": []
@@ -5759,193 +5757,29 @@
"usage": [
{
"model": "claude-opus-5-5",
- "inputTokens": 291639,
- "cacheReadInputTokens": 270261,
- "cacheWriteInputTokens": 21356,
- "outputTokens": 5474
+ "inputTokens": 279324,
+ "cacheReadInputTokens": 263102,
+ "cacheWriteInputTokens": 16198,
+ "outputTokens": 4214
}
],
- "stepCount": 17,
- "toolCallCount": 15,
- "agentRunDurationMs": 60110,
+ "stepCount": 15,
+ "toolCallCount": 14,
+ "agentRunDurationMs": 47003,
"sandboxUsage": {
- "memory": 8192
+ "activeCpuDurationMs": 145124,
+ "duration": 161920,
+ "memory": 8192,
+ "networkTransfer": {
+ "ingress": 746108858,
+ "egress": 3825012
+ }
},
"prompt": "Our app lets signed-in users save bookmarks and view them on their dashboard. Bookmarks are stored in the `bookmarks` table and are private — a user must only ever see their own. \nUsers also need to be able to save new bookmarks from the app.\n\nI can see the rows when I query the table directly, but the dashboard shows an empty list for every user.\n\nFind out why the Data API returns nothing and fix it.",
"promptSourcePath": "evals/benchmark/resolve-dataapi-001-empty-results/PROMPT.md",
"run": 1,
"sourcePath": "claude-code-opus-5.5/resolve-dataapi-001-empty-results/run-1/result.json"
},
- {
- "experiment": "claude-code-opus-5.5",
- "experimentSuite": "benchmark",
- "experimentDisplay": {
- "agent": "claude-code",
- "modelProvider": "anthropic",
- "modelId": "claude-opus-5-5",
- "reasoningEffort": "high"
- },
- "eval": "resolve-dataapi-001-empty-results",
- "stage": "resolve",
- "product": [
- "data-api",
- "database",
- "auth"
- ],
- "topic": [
- "rls",
- "sdk"
- ],
- "suite": "benchmark",
- "interface": "mcp",
- "passed": true,
- "checks": [
- {
- "name": "RLS still enabled on bookmarks",
- "passed": true
- },
- {
- "name": "user A reads own bookmarks",
- "passed": true
- },
- {
- "name": "user B cannot read user A bookmarks",
- "passed": true
- },
- {
- "name": "anon reads no bookmarks",
- "passed": true
- },
- {
- "name": "user A can save a new bookmark",
- "passed": true
- },
- {
- "name": "user B cannot insert a bookmark as user A",
- "passed": true
- },
- {
- "name": "diagnosed RLS and added owner-scoped policies",
- "passed": true,
- "judgeNotes": "Diagnosed RLS with no policies as the cause of empty Data API results, created owner-scoped SELECT and INSERT policies for authenticated users, kept RLS enabled, and verified access."
- }
- ],
- "skills": {
- "available": [
- "supabase",
- "supabase-postgres-best-practices"
- ],
- "loaded": [
- "supabase"
- ]
- },
- "docs": {
- "calls": []
- },
- "usage": [
- {
- "model": "claude-opus-5-5",
- "inputTokens": 315506,
- "cacheReadInputTokens": 293993,
- "cacheWriteInputTokens": 21489,
- "outputTokens": 5299
- }
- ],
- "stepCount": 18,
- "toolCallCount": 16,
- "agentRunDurationMs": 66314,
- "sandboxUsage": {
- "memory": 8192
- },
- "prompt": "Our app lets signed-in users save bookmarks and view them on their dashboard. Bookmarks are stored in the `bookmarks` table and are private — a user must only ever see their own. \nUsers also need to be able to save new bookmarks from the app.\n\nI can see the rows when I query the table directly, but the dashboard shows an empty list for every user.\n\nFind out why the Data API returns nothing and fix it.",
- "promptSourcePath": "evals/benchmark/resolve-dataapi-001-empty-results/PROMPT.md",
- "run": 2,
- "sourcePath": "claude-code-opus-5.5/resolve-dataapi-001-empty-results/run-2/result.json"
- },
- {
- "experiment": "claude-code-opus-5.5",
- "experimentSuite": "benchmark",
- "experimentDisplay": {
- "agent": "claude-code",
- "modelProvider": "anthropic",
- "modelId": "claude-opus-5-5",
- "reasoningEffort": "high"
- },
- "eval": "resolve-dataapi-001-empty-results",
- "stage": "resolve",
- "product": [
- "data-api",
- "database",
- "auth"
- ],
- "topic": [
- "rls",
- "sdk"
- ],
- "suite": "benchmark",
- "interface": "mcp",
- "passed": true,
- "checks": [
- {
- "name": "RLS still enabled on bookmarks",
- "passed": true
- },
- {
- "name": "user A reads own bookmarks",
- "passed": true
- },
- {
- "name": "user B cannot read user A bookmarks",
- "passed": true
- },
- {
- "name": "anon reads no bookmarks",
- "passed": true
- },
- {
- "name": "user A can save a new bookmark",
- "passed": true
- },
- {
- "name": "user B cannot insert a bookmark as user A",
- "passed": true
- },
- {
- "name": "diagnosed RLS and added owner-scoped policies",
- "passed": true,
- "judgeNotes": "Correctly diagnosed deny-all RLS, created authenticated-only owner-scoped SELECT and INSERT policies, and kept RLS enabled."
- }
- ],
- "skills": {
- "available": [
- "supabase",
- "supabase-postgres-best-practices"
- ],
- "loaded": []
- },
- "docs": {
- "calls": []
- },
- "usage": [
- {
- "model": "claude-opus-5-5",
- "inputTokens": 323324,
- "cacheReadInputTokens": 305485,
- "cacheWriteInputTokens": 17811,
- "outputTokens": 5414
- }
- ],
- "stepCount": 17,
- "toolCallCount": 16,
- "agentRunDurationMs": 59898,
- "sandboxUsage": {
- "memory": 8192
- },
- "prompt": "Our app lets signed-in users save bookmarks and view them on their dashboard. Bookmarks are stored in the `bookmarks` table and are private — a user must only ever see their own. \nUsers also need to be able to save new bookmarks from the app.\n\nI can see the rows when I query the table directly, but the dashboard shows an empty list for every user.\n\nFind out why the Data API returns nothing and fix it.",
- "promptSourcePath": "evals/benchmark/resolve-dataapi-001-empty-results/PROMPT.md",
- "run": 3,
- "sourcePath": "claude-code-opus-5.5/resolve-dataapi-001-empty-results/run-3/result.json"
- },
{
"experiment": "claude-code-opus-5.5",
"experimentSuite": "benchmark",
@@ -24644,6 +24478,74 @@
"run": 3,
"sourcePath": "claude-code-sonnet-5-no-skills/resolve-security-002-rls-cross-tenant-leak/run-3/result.json"
},
+ {
+ "experiment": "claude-code-sonnet-5.5",
+ "experimentSuite": "benchmark",
+ "experimentDisplay": {
+ "agent": "claude-code",
+ "modelProvider": "anthropic",
+ "modelId": "claude-sonnet-5-5",
+ "reasoningEffort": "medium"
+ },
+ "eval": "resolve-dataapi-001-empty-results",
+ "stage": "resolve",
+ "product": [
+ "data-api",
+ "database",
+ "auth"
+ ],
+ "topic": [
+ "rls",
+ "sdk"
+ ],
+ "suite": "benchmark",
+ "interface": "mcp",
+ "passed": false,
+ "checks": [
+ {
+ "name": "created auth sessions",
+ "passed": false,
+ "notes": "Internal server error"
+ }
+ ],
+ "skills": {
+ "available": [
+ "supabase",
+ "supabase-postgres-best-practices"
+ ],
+ "loaded": [
+ "supabase"
+ ]
+ },
+ "docs": {
+ "calls": []
+ },
+ "usage": [
+ {
+ "model": "claude-sonnet-5-5",
+ "inputTokens": 407471,
+ "cacheReadInputTokens": 387145,
+ "cacheWriteInputTokens": 20296,
+ "outputTokens": 4168
+ }
+ ],
+ "stepCount": 19,
+ "toolCallCount": 17,
+ "agentRunDurationMs": 32505,
+ "sandboxUsage": {
+ "activeCpuDurationMs": 190445,
+ "duration": 176643,
+ "memory": 8192,
+ "networkTransfer": {
+ "ingress": 744557150,
+ "egress": 2844827
+ }
+ },
+ "prompt": "Our app lets signed-in users save bookmarks and view them on their dashboard. Bookmarks are stored in the `bookmarks` table and are private — a user must only ever see their own. \nUsers also need to be able to save new bookmarks from the app.\n\nI can see the rows when I query the table directly, but the dashboard shows an empty list for every user.\n\nFind out why the Data API returns nothing and fix it.",
+ "promptSourcePath": "evals/benchmark/resolve-dataapi-001-empty-results/PROMPT.md",
+ "run": 1,
+ "sourcePath": "claude-code-sonnet-5.5/resolve-dataapi-001-empty-results/run-1/result.json"
+ },
{
"experiment": "codex-gpt-6-luna",
"experimentSuite": "benchmark",
From fa8111e87877e80f58b96ae5db1c088de301ed91 Mon Sep 17 00:00:00 2001
From: Matt Rossman <22670878+mattrossman@users.noreply.github.com>
Date: Mon, 26 Oct 2026 11:21:27 -0400
Subject: [PATCH 06/18] Revert "chore: refresh eval results"
This reverts commit 1d09dd8b74f75d88c0ef40c7c96cc9f2b4dc9fc6.
---
apps/web/src/data/eval-results.json | 268 +++++++++++++++++++---------
1 file changed, 183 insertions(+), 85 deletions(-)
diff --git a/apps/web/src/data/eval-results.json b/apps/web/src/data/eval-results.json
index d8f3c5df7..ce7cde21e 100644
--- a/apps/web/src/data/eval-results.json
+++ b/apps/web/src/data/eval-results.json
@@ -5697,7 +5697,7 @@
"agent": "claude-code",
"modelProvider": "anthropic",
"modelId": "claude-opus-5-5",
- "reasoningEffort": "medium"
+ "reasoningEffort": "high"
},
"eval": "resolve-dataapi-001-empty-results",
"stage": "resolve",
@@ -5741,7 +5741,7 @@
{
"name": "diagnosed RLS and added owner-scoped policies",
"passed": true,
- "judgeNotes": "Diagnosed RLS enabled with no policies as deny-all. Created authenticated-only SELECT USING and INSERT WITH CHECK policies matching user_id to auth.uid(), and kept RLS enabled."
+ "judgeNotes": "Diagnosed RLS deny-all, kept RLS enabled, and created authenticated SELECT and INSERT policies scoped to each user's bookmarks."
}
],
"skills": {
@@ -5749,7 +5749,9 @@
"supabase",
"supabase-postgres-best-practices"
],
- "loaded": []
+ "loaded": [
+ "supabase"
+ ]
},
"docs": {
"calls": []
@@ -5757,29 +5759,193 @@
"usage": [
{
"model": "claude-opus-5-5",
- "inputTokens": 279324,
- "cacheReadInputTokens": 263102,
- "cacheWriteInputTokens": 16198,
- "outputTokens": 4214
+ "inputTokens": 291639,
+ "cacheReadInputTokens": 270261,
+ "cacheWriteInputTokens": 21356,
+ "outputTokens": 5474
}
],
- "stepCount": 15,
- "toolCallCount": 14,
- "agentRunDurationMs": 47003,
+ "stepCount": 17,
+ "toolCallCount": 15,
+ "agentRunDurationMs": 60110,
"sandboxUsage": {
- "activeCpuDurationMs": 145124,
- "duration": 161920,
- "memory": 8192,
- "networkTransfer": {
- "ingress": 746108858,
- "egress": 3825012
- }
+ "memory": 8192
},
"prompt": "Our app lets signed-in users save bookmarks and view them on their dashboard. Bookmarks are stored in the `bookmarks` table and are private — a user must only ever see their own. \nUsers also need to be able to save new bookmarks from the app.\n\nI can see the rows when I query the table directly, but the dashboard shows an empty list for every user.\n\nFind out why the Data API returns nothing and fix it.",
"promptSourcePath": "evals/benchmark/resolve-dataapi-001-empty-results/PROMPT.md",
"run": 1,
"sourcePath": "claude-code-opus-5.5/resolve-dataapi-001-empty-results/run-1/result.json"
},
+ {
+ "experiment": "claude-code-opus-5.5",
+ "experimentSuite": "benchmark",
+ "experimentDisplay": {
+ "agent": "claude-code",
+ "modelProvider": "anthropic",
+ "modelId": "claude-opus-5-5",
+ "reasoningEffort": "high"
+ },
+ "eval": "resolve-dataapi-001-empty-results",
+ "stage": "resolve",
+ "product": [
+ "data-api",
+ "database",
+ "auth"
+ ],
+ "topic": [
+ "rls",
+ "sdk"
+ ],
+ "suite": "benchmark",
+ "interface": "mcp",
+ "passed": true,
+ "checks": [
+ {
+ "name": "RLS still enabled on bookmarks",
+ "passed": true
+ },
+ {
+ "name": "user A reads own bookmarks",
+ "passed": true
+ },
+ {
+ "name": "user B cannot read user A bookmarks",
+ "passed": true
+ },
+ {
+ "name": "anon reads no bookmarks",
+ "passed": true
+ },
+ {
+ "name": "user A can save a new bookmark",
+ "passed": true
+ },
+ {
+ "name": "user B cannot insert a bookmark as user A",
+ "passed": true
+ },
+ {
+ "name": "diagnosed RLS and added owner-scoped policies",
+ "passed": true,
+ "judgeNotes": "Diagnosed RLS with no policies as the cause of empty Data API results, created owner-scoped SELECT and INSERT policies for authenticated users, kept RLS enabled, and verified access."
+ }
+ ],
+ "skills": {
+ "available": [
+ "supabase",
+ "supabase-postgres-best-practices"
+ ],
+ "loaded": [
+ "supabase"
+ ]
+ },
+ "docs": {
+ "calls": []
+ },
+ "usage": [
+ {
+ "model": "claude-opus-5-5",
+ "inputTokens": 315506,
+ "cacheReadInputTokens": 293993,
+ "cacheWriteInputTokens": 21489,
+ "outputTokens": 5299
+ }
+ ],
+ "stepCount": 18,
+ "toolCallCount": 16,
+ "agentRunDurationMs": 66314,
+ "sandboxUsage": {
+ "memory": 8192
+ },
+ "prompt": "Our app lets signed-in users save bookmarks and view them on their dashboard. Bookmarks are stored in the `bookmarks` table and are private — a user must only ever see their own. \nUsers also need to be able to save new bookmarks from the app.\n\nI can see the rows when I query the table directly, but the dashboard shows an empty list for every user.\n\nFind out why the Data API returns nothing and fix it.",
+ "promptSourcePath": "evals/benchmark/resolve-dataapi-001-empty-results/PROMPT.md",
+ "run": 2,
+ "sourcePath": "claude-code-opus-5.5/resolve-dataapi-001-empty-results/run-2/result.json"
+ },
+ {
+ "experiment": "claude-code-opus-5.5",
+ "experimentSuite": "benchmark",
+ "experimentDisplay": {
+ "agent": "claude-code",
+ "modelProvider": "anthropic",
+ "modelId": "claude-opus-5-5",
+ "reasoningEffort": "high"
+ },
+ "eval": "resolve-dataapi-001-empty-results",
+ "stage": "resolve",
+ "product": [
+ "data-api",
+ "database",
+ "auth"
+ ],
+ "topic": [
+ "rls",
+ "sdk"
+ ],
+ "suite": "benchmark",
+ "interface": "mcp",
+ "passed": true,
+ "checks": [
+ {
+ "name": "RLS still enabled on bookmarks",
+ "passed": true
+ },
+ {
+ "name": "user A reads own bookmarks",
+ "passed": true
+ },
+ {
+ "name": "user B cannot read user A bookmarks",
+ "passed": true
+ },
+ {
+ "name": "anon reads no bookmarks",
+ "passed": true
+ },
+ {
+ "name": "user A can save a new bookmark",
+ "passed": true
+ },
+ {
+ "name": "user B cannot insert a bookmark as user A",
+ "passed": true
+ },
+ {
+ "name": "diagnosed RLS and added owner-scoped policies",
+ "passed": true,
+ "judgeNotes": "Correctly diagnosed deny-all RLS, created authenticated-only owner-scoped SELECT and INSERT policies, and kept RLS enabled."
+ }
+ ],
+ "skills": {
+ "available": [
+ "supabase",
+ "supabase-postgres-best-practices"
+ ],
+ "loaded": []
+ },
+ "docs": {
+ "calls": []
+ },
+ "usage": [
+ {
+ "model": "claude-opus-5-5",
+ "inputTokens": 323324,
+ "cacheReadInputTokens": 305485,
+ "cacheWriteInputTokens": 17811,
+ "outputTokens": 5414
+ }
+ ],
+ "stepCount": 17,
+ "toolCallCount": 16,
+ "agentRunDurationMs": 59898,
+ "sandboxUsage": {
+ "memory": 8192
+ },
+ "prompt": "Our app lets signed-in users save bookmarks and view them on their dashboard. Bookmarks are stored in the `bookmarks` table and are private — a user must only ever see their own. \nUsers also need to be able to save new bookmarks from the app.\n\nI can see the rows when I query the table directly, but the dashboard shows an empty list for every user.\n\nFind out why the Data API returns nothing and fix it.",
+ "promptSourcePath": "evals/benchmark/resolve-dataapi-001-empty-results/PROMPT.md",
+ "run": 3,
+ "sourcePath": "claude-code-opus-5.5/resolve-dataapi-001-empty-results/run-3/result.json"
+ },
{
"experiment": "claude-code-opus-5.5",
"experimentSuite": "benchmark",
@@ -24478,74 +24644,6 @@
"run": 3,
"sourcePath": "claude-code-sonnet-5-no-skills/resolve-security-002-rls-cross-tenant-leak/run-3/result.json"
},
- {
- "experiment": "claude-code-sonnet-5.5",
- "experimentSuite": "benchmark",
- "experimentDisplay": {
- "agent": "claude-code",
- "modelProvider": "anthropic",
- "modelId": "claude-sonnet-5-5",
- "reasoningEffort": "medium"
- },
- "eval": "resolve-dataapi-001-empty-results",
- "stage": "resolve",
- "product": [
- "data-api",
- "database",
- "auth"
- ],
- "topic": [
- "rls",
- "sdk"
- ],
- "suite": "benchmark",
- "interface": "mcp",
- "passed": false,
- "checks": [
- {
- "name": "created auth sessions",
- "passed": false,
- "notes": "Internal server error"
- }
- ],
- "skills": {
- "available": [
- "supabase",
- "supabase-postgres-best-practices"
- ],
- "loaded": [
- "supabase"
- ]
- },
- "docs": {
- "calls": []
- },
- "usage": [
- {
- "model": "claude-sonnet-5-5",
- "inputTokens": 407471,
- "cacheReadInputTokens": 387145,
- "cacheWriteInputTokens": 20296,
- "outputTokens": 4168
- }
- ],
- "stepCount": 19,
- "toolCallCount": 17,
- "agentRunDurationMs": 32505,
- "sandboxUsage": {
- "activeCpuDurationMs": 190445,
- "duration": 176643,
- "memory": 8192,
- "networkTransfer": {
- "ingress": 744557150,
- "egress": 2844827
- }
- },
- "prompt": "Our app lets signed-in users save bookmarks and view them on their dashboard. Bookmarks are stored in the `bookmarks` table and are private — a user must only ever see their own. \nUsers also need to be able to save new bookmarks from the app.\n\nI can see the rows when I query the table directly, but the dashboard shows an empty list for every user.\n\nFind out why the Data API returns nothing and fix it.",
- "promptSourcePath": "evals/benchmark/resolve-dataapi-001-empty-results/PROMPT.md",
- "run": 1,
- "sourcePath": "claude-code-sonnet-5.5/resolve-dataapi-001-empty-results/run-1/result.json"
- },
{
"experiment": "codex-gpt-6-luna",
"experimentSuite": "benchmark",
From 0ec0de6f27ccbecc534afc5e8b7a8a8ca01804d4 Mon Sep 17 00:00:00 2001
From: Matt Rossman <22670878+mattrossman@users.noreply.github.com>
Date: Mon, 26 Oct 2026 11:33:48 -0400
Subject: [PATCH 07/18] style(evals): format opencode runner test
---
packages/core/src/agents/opencode/runner.test.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/core/src/agents/opencode/runner.test.ts b/packages/core/src/agents/opencode/runner.test.ts
index f2fff3781..1eee6bbdd 100644
--- a/packages/core/src/agents/opencode/runner.test.ts
+++ b/packages/core/src/agents/opencode/runner.test.ts
@@ -130,7 +130,9 @@ describe('opencode runner extractUsage', () => {
it('returns undefined without usage', () => {
expect(extract(undefined, 'anthropic/claude-sonnet-5-5')).toBeUndefined();
- expect(extract('not json\n', 'anthropic/claude-sonnet-5-5')).toBeUndefined();
+ expect(
+ extract('not json\n', 'anthropic/claude-sonnet-5-5')
+ ).toBeUndefined();
expect(
extract(
JSON.stringify({
From 3ec9967e3795cc3aef0626ab5e9d43633d1c13cd Mon Sep 17 00:00:00 2001
From: Matt Rossman <22670878+mattrossman@users.noreply.github.com>
Date: Mon, 26 Oct 2026 16:45:00 -0400
Subject: [PATCH 08/18] feat(evals): explain each benchmark experiment's model
and effort on the results page
---
apps/framework/lib/result-files.ts | 3 +
apps/framework/scripts/export-results.test.ts | 36 +++++++++
apps/framework/scripts/export-results.ts | 17 ++++
apps/web/src/App.tsx | 6 +-
.../src/components/experiment-rationale.tsx | 65 ++++++++++++++++
apps/web/src/lib/eval-results.ts | 5 ++
experiments/presets.ts | 77 ++++++++++++++++++-
packages/core/src/eval-metadata.ts | 8 ++
packages/core/src/index.ts | 5 ++
9 files changed, 220 insertions(+), 2 deletions(-)
create mode 100644 apps/web/src/components/experiment-rationale.tsx
diff --git a/apps/framework/lib/result-files.ts b/apps/framework/lib/result-files.ts
index dbc79323c..80ad64922 100644
--- a/apps/framework/lib/result-files.ts
+++ b/apps/framework/lib/result-files.ts
@@ -11,6 +11,7 @@ import {
getExperimentDisplayMetadata,
type ExperimentConfig,
type ExperimentDisplayMetadata,
+ type ExperimentRationale,
} from '@supabase-evals/core';
import type { ExperimentSuite } from '@supabase-evals/core/eval-metadata';
import { discoverExperimentFiles } from './experiment-files.js';
@@ -23,6 +24,7 @@ const EXPERIMENTS_DIR = join(ROOT, 'experiments');
export interface ExperimentMetadata {
display: ExperimentDisplayMetadata;
+ rationale?: ExperimentRationale;
suites: ExperimentSuite[];
}
@@ -35,6 +37,7 @@ export async function loadExperimentMetadata(): Promise<
const config: ExperimentConfig = mod.default;
map.set(experiment.name, {
display: getExperimentDisplayMetadata(config),
+ rationale: config.rationale,
suites: config.suite ?? [],
});
}
diff --git a/apps/framework/scripts/export-results.test.ts b/apps/framework/scripts/export-results.test.ts
index d850fc541..e01e22898 100644
--- a/apps/framework/scripts/export-results.test.ts
+++ b/apps/framework/scripts/export-results.test.ts
@@ -32,3 +32,39 @@ describe('toEvalResult cliVersion precedence', () => {
expect(result.cliVersion).toBe('2.109.1');
});
});
+
+describe('toEvalResult experimentRationale', () => {
+ const display = {
+ agent: 'claude-code',
+ modelProvider: 'anthropic',
+ modelId: 'claude-opus-5-5',
+ reasoningEffort: 'medium',
+ } as const;
+ const experimentData = {
+ display,
+ rationale: { summary: 'Default model.', sources: [] },
+ suites: [],
+ };
+
+ const build = async (experimentDisplay: Record) =>
+ toEvalResult(
+ rawEvalResultSchema.parse({
+ experiment: 'test-experiment',
+ eval: PINNED_EVAL_ID,
+ interface: 'cli',
+ experimentDisplay,
+ }),
+ 'test-experiment/run-1/result.json',
+ await readPrompt(PINNED_EVAL_ID),
+ experimentData
+ );
+
+ it('attaches the rationale when the run matches the config', async () => {
+ expect((await build(display)).experimentRationale).toBeDefined();
+ });
+
+ it('drops the rationale when the run used a different effort', async () => {
+ const result = await build({ ...display, reasoningEffort: 'high' });
+ expect(result.experimentRationale).toBeUndefined();
+ });
+});
diff --git a/apps/framework/scripts/export-results.ts b/apps/framework/scripts/export-results.ts
index 04c9c6f42..11b61db2b 100644
--- a/apps/framework/scripts/export-results.ts
+++ b/apps/framework/scripts/export-results.ts
@@ -7,6 +7,7 @@ import { rawEvalResultSchema } from '@supabase-evals/core/eval-metadata';
import type {
EvalResult,
EvalSuite,
+ ExperimentDisplayMetadata,
ExperimentSuite,
} from '@supabase-evals/core/eval-metadata';
import {
@@ -54,6 +55,14 @@ const EXPECTED_RUNS_FLAG = readFlag(rawArgs, 'runs');
const OUTPUT_FLAG = readRepeatedFlag(rawArgs, 'output')[0];
const outputPath = OUTPUT_FLAG ? resolve(ROOT, OUTPUT_FLAG) : OUTPUT_PATH;
+/** True when the run used the model and effort the experiment config names today. */
+function sameModelAndEffort(
+ a: ExperimentDisplayMetadata,
+ b: ExperimentDisplayMetadata | undefined
+) {
+ return a.modelId === b?.modelId && a.reasoningEffort === b?.reasoningEffort;
+}
+
export function toEvalResult(
parsedResult: ReturnType,
sourcePath: string,
@@ -70,6 +79,14 @@ export function toEvalResult(
experimentSuite,
experimentDisplay:
parsedResult.experimentDisplay ?? experimentData?.display,
+ experimentRationale:
+ !parsedResult.experimentDisplay ||
+ sameModelAndEffort(
+ parsedResult.experimentDisplay,
+ experimentData?.display
+ )
+ ? experimentData?.rationale
+ : undefined,
eval: parsedResult.eval,
stage: promptData?.stage ?? parsedResult.stage,
product: promptData?.product ?? parsedResult.product,
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index 8f3f154ef..e2bdd072f 100644
--- a/apps/web/src/App.tsx
+++ b/apps/web/src/App.tsx
@@ -1,6 +1,7 @@
import { useQueryStates } from "nuqs"
import { EvalOverviewCards } from "@/components/eval-overview-cards"
+import { ExperimentRationale } from "@/components/experiment-rationale"
import { PageContainer } from "@/components/page-container"
import { ResultsTable } from "@/components/results/results-table"
import { SiteFooter } from "@/components/site-footer"
@@ -43,9 +44,12 @@ export function App() {
}}
/>
-
+
+
+
+
>
) : (
diff --git a/apps/web/src/components/experiment-rationale.tsx b/apps/web/src/components/experiment-rationale.tsx
new file mode 100644
index 000000000..6abf79c34
--- /dev/null
+++ b/apps/web/src/components/experiment-rationale.tsx
@@ -0,0 +1,65 @@
+import { ExperimentLabel } from "@/components/results/experiment-label"
+import {
+ getExperimentRationale,
+ getSuiteResults,
+ getVisibleExperiments,
+} from "@/lib/eval-results"
+
+/** Why each benchmark experiment uses its model and effort, with links to check it. */
+export function ExperimentRationale() {
+ const experiments = getVisibleExperiments(
+ getSuiteResults("benchmark")
+ ).filter((experiment) => getExperimentRationale(experiment))
+
+ if (!experiments.length) {
+ return null
+ }
+
+ return (
+
+
+ Why these experiments
+
+
+ Where an agent has a default model and effort, we use it. Where we
+ picked one ourselves, we say why.
+
+
+ {experiments.map((experiment) => {
+ const rationale = getExperimentRationale(experiment)
+ return (
+
+
+
+ {rationale?.summary}
+
+
+ )
+ })}
+
+
+ )
+}
diff --git a/apps/web/src/lib/eval-results.ts b/apps/web/src/lib/eval-results.ts
index a25674c72..a10da0f47 100644
--- a/apps/web/src/lib/eval-results.ts
+++ b/apps/web/src/lib/eval-results.ts
@@ -123,6 +123,11 @@ export function getExperimentDisplay(experiment: string) {
?.experimentDisplay
}
+export function getExperimentRationale(experiment: string) {
+ return sortedResults.find((result) => result.experiment === experiment)
+ ?.experimentRationale
+}
+
export function getExperimentResults(
experiment: string,
sourceResults = sortedResults
diff --git a/experiments/presets.ts b/experiments/presets.ts
index 991c68d93..8cb416364 100644
--- a/experiments/presets.ts
+++ b/experiments/presets.ts
@@ -11,7 +11,7 @@ import { localStackRuntime } from '@supabase-evals/sandbox';
type ExperimentPreset = Pick<
ExperimentConfig,
- 'agent' | 'runtime' | 'localStack' | 'skills'
+ 'agent' | 'rationale' | 'runtime' | 'localStack' | 'skills'
>;
const skills = ['supabase', 'supabase-postgres-best-practices'];
@@ -27,6 +27,20 @@ export const claudeCodeOpus55 = {
model: 'claude-opus-5-5',
reasoningEffort: 'medium',
}),
+ rationale: {
+ summary:
+ "Claude Code's default model on most plans, at its default effort.",
+ sources: [
+ {
+ label: 'Default model',
+ url: 'https://code.claude.com/docs/en/model-config#default-model-setting',
+ },
+ {
+ label: 'Default effort',
+ url: 'https://code.claude.com/docs/en/model-config#adjust-effort-level',
+ },
+ ],
+ },
runtime: defaultRuntime(),
localStack: localStackRuntime(),
skills,
@@ -37,6 +51,20 @@ export const claudeCodeSonnet55 = {
model: 'claude-sonnet-5-5',
reasoningEffort: 'medium',
}),
+ rationale: {
+ summary:
+ "Claude Code's Sonnet model for daily coding tasks, at its default effort.",
+ sources: [
+ {
+ label: 'Model aliases',
+ url: 'https://code.claude.com/docs/en/model-config',
+ },
+ {
+ label: 'Default effort',
+ url: 'https://code.claude.com/docs/en/model-config#adjust-effort-level',
+ },
+ ],
+ },
runtime: defaultRuntime(),
localStack: localStackRuntime(),
skills,
@@ -47,6 +75,16 @@ export const codexGpt6Luna = {
model: 'gpt-6-luna',
reasoningEffort: 'medium',
}),
+ rationale: {
+ summary:
+ 'A cost-effective model for everyday tasks, and the one behind the Supabase dashboard assistant.',
+ sources: [
+ {
+ label: 'Dashboard assistant default model',
+ url: 'https://github.com/supabase/supabase/blob/d98c277790766d2ceb905b675eb0a718f25e6345/apps/studio/lib/ai/model.utils.ts#L92',
+ },
+ ],
+ },
runtime: defaultRuntime(),
localStack: localStackRuntime(),
skills,
@@ -57,6 +95,19 @@ export const codexGpt61Sol = {
model: 'gpt-6.1-sol',
reasoningEffort: 'medium',
}),
+ rationale: {
+ summary: "Codex's default model, at its default effort.",
+ sources: [
+ {
+ label: 'Where the catalog comes from',
+ url: 'https://github.com/openai/codex/blob/03748ad5e18a3c9c1f0828561dcf1f3f0d7b2f77/codex-rs/models-manager/src/manager.rs#L394-L436',
+ },
+ {
+ label: 'How the default is picked',
+ url: 'https://github.com/openai/codex/blob/7d4c7a0767cdf81ff76c8fa5a05c22929568e3c6/codex-rs/models-manager/src/manager.rs#L845-L851',
+ },
+ ],
+ },
runtime: defaultRuntime(),
localStack: localStackRuntime(),
skills,
@@ -67,6 +118,19 @@ export const grok47 = {
model: 'grok-4.7',
reasoningEffort: 'high',
}),
+ rationale: {
+ summary: "Grok Build's default model, at its default effort.",
+ sources: [
+ {
+ label: 'Grok 4.7 and Grok Build',
+ url: 'https://docs.x.ai/developers/grok-4-7',
+ },
+ {
+ label: 'Default effort',
+ url: 'https://docs.x.ai/developers/models/grok-4.7',
+ },
+ ],
+ },
runtime: defaultRuntime(),
localStack: localStackRuntime(),
skills,
@@ -76,6 +140,17 @@ export const opencodeKimiK3 = {
agent: opencodeAgent({
model: 'moonshotai/kimi-k3',
}),
+ rationale: {
+ summary:
+ 'An open-weight model we picked to represent open-weight options in OpenCode.',
+ sources: [
+ { label: 'OpenCode usage data', url: 'https://opencode.ai/data/' },
+ {
+ label: 'Kimi K3 usage on OpenCode',
+ url: 'https://opencode.ai/data/moonshotai/kimi-k3',
+ },
+ ],
+ },
runtime: defaultRuntime(),
localStack: localStackRuntime(),
skills,
diff --git a/packages/core/src/eval-metadata.ts b/packages/core/src/eval-metadata.ts
index d6f4d16a1..f18fd0e3e 100644
--- a/packages/core/src/eval-metadata.ts
+++ b/packages/core/src/eval-metadata.ts
@@ -96,6 +96,13 @@ export type ExperimentDisplayMetadata = z.infer<
typeof experimentDisplayMetadataSchema
>;
+/** Why an experiment uses its model and effort, with links to check it. */
+export const experimentRationaleSchema = z.object({
+ summary: z.string(),
+ sources: z.array(z.object({ label: z.string(), url: z.string().url() })),
+});
+export type ExperimentRationale = z.infer;
+
/**
* Interface(s) the agent uses to act on Supabase — a benchmark dimension
* (cross-team KPI), not the runtime switch. `mcp` = the platform-lite MCP/tool
@@ -424,6 +431,7 @@ const evalResultShape = {
experimentSuite: experimentSuiteSchema.optional(),
profile: experimentSuiteSchema.optional(),
experimentDisplay: experimentDisplayMetadataSchema.optional(),
+ experimentRationale: experimentRationaleSchema.optional(),
eval: z.string(),
stage: evalStageSchema.optional(),
product: z.array(evalProductSchema).optional(),
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index ef8bae3ee..7bf86cf7d 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -46,6 +46,7 @@ import type {
EvalMetadata,
EvalSuite,
ExperimentDisplayMetadata,
+ ExperimentRationale,
ExperimentSuite,
JudgeCall,
ModelProvider,
@@ -93,6 +94,7 @@ export {
evalSuiteSchema,
experimentSuiteSchema,
experimentDisplayMetadataSchema,
+ experimentRationaleSchema,
modelProviderSchema,
rawEvalResultSchema,
reasoningEffortSchema,
@@ -144,6 +146,7 @@ export type {
EvalSuite,
EvalStage,
ExperimentDisplayMetadata,
+ ExperimentRationale,
ExperimentSuite,
ModelProvider,
ParsedEvalMarkdown,
@@ -645,6 +648,8 @@ export type ExperimentConfig = {
/** Named experiment suites this experiment belongs to. */
suite?: ExperimentSuite[];
agent: AgentHarness;
+ /** Why this model and effort, shown next to the experiment on the results page. */
+ rationale?: ExperimentRationale;
runtime: EvalRuntime;
/** Local-stack environment (e.g. localStackRuntime() from @supabase-evals/sandbox). */
localStack?: LocalStackRuntime;
From e27d80aa4c2d2be5ca07b4c50b9d5d046a6ea2a4 Mon Sep 17 00:00:00 2001
From: Matt Rossman <22670878+mattrossman@users.noreply.github.com>
Date: Wed, 28 Oct 2026 10:27:42 -0400
Subject: [PATCH 09/18] refactor: keep benchmark rationales in website catalog
---
apps/framework/lib/result-files.ts | 3 -
.../scripts/check-experiment-rationales.ts | 7 +
apps/framework/scripts/export-results.test.ts | 36 ----
apps/framework/scripts/export-results.ts | 17 --
apps/web/package.json | 8 +-
.../src/components/experiment-rationale.tsx | 8 +-
apps/web/src/data/experiment-rationales.json | 158 ++++++++++++++++++
apps/web/src/lib/eval-results.test.ts | 77 +++++++++
apps/web/src/lib/eval-results.ts | 25 ++-
apps/web/src/lib/experiment-rationales.ts | 51 ++++++
experiments/presets.ts | 77 +--------
packages/core/src/eval-metadata.ts | 1 -
packages/core/src/index.ts | 5 -
13 files changed, 326 insertions(+), 147 deletions(-)
create mode 100644 apps/framework/scripts/check-experiment-rationales.ts
create mode 100644 apps/web/src/data/experiment-rationales.json
create mode 100644 apps/web/src/lib/experiment-rationales.ts
diff --git a/apps/framework/lib/result-files.ts b/apps/framework/lib/result-files.ts
index 80ad64922..dbc79323c 100644
--- a/apps/framework/lib/result-files.ts
+++ b/apps/framework/lib/result-files.ts
@@ -11,7 +11,6 @@ import {
getExperimentDisplayMetadata,
type ExperimentConfig,
type ExperimentDisplayMetadata,
- type ExperimentRationale,
} from '@supabase-evals/core';
import type { ExperimentSuite } from '@supabase-evals/core/eval-metadata';
import { discoverExperimentFiles } from './experiment-files.js';
@@ -24,7 +23,6 @@ const EXPERIMENTS_DIR = join(ROOT, 'experiments');
export interface ExperimentMetadata {
display: ExperimentDisplayMetadata;
- rationale?: ExperimentRationale;
suites: ExperimentSuite[];
}
@@ -37,7 +35,6 @@ export async function loadExperimentMetadata(): Promise<
const config: ExperimentConfig = mod.default;
map.set(experiment.name, {
display: getExperimentDisplayMetadata(config),
- rationale: config.rationale,
suites: config.suite ?? [],
});
}
diff --git a/apps/framework/scripts/check-experiment-rationales.ts b/apps/framework/scripts/check-experiment-rationales.ts
new file mode 100644
index 000000000..a6189f1f9
--- /dev/null
+++ b/apps/framework/scripts/check-experiment-rationales.ts
@@ -0,0 +1,7 @@
+import { loadExperimentMetadata } from '../lib/result-files.js';
+import { missingBenchmarkRationales } from '../../web/src/lib/experiment-rationales.js';
+
+const missing = missingBenchmarkRationales(await loadExperimentMetadata());
+if (missing.length) {
+ throw new Error(`Missing benchmark rationales: ${missing.join(', ')}`);
+}
diff --git a/apps/framework/scripts/export-results.test.ts b/apps/framework/scripts/export-results.test.ts
index e01e22898..d850fc541 100644
--- a/apps/framework/scripts/export-results.test.ts
+++ b/apps/framework/scripts/export-results.test.ts
@@ -32,39 +32,3 @@ describe('toEvalResult cliVersion precedence', () => {
expect(result.cliVersion).toBe('2.109.1');
});
});
-
-describe('toEvalResult experimentRationale', () => {
- const display = {
- agent: 'claude-code',
- modelProvider: 'anthropic',
- modelId: 'claude-opus-5-5',
- reasoningEffort: 'medium',
- } as const;
- const experimentData = {
- display,
- rationale: { summary: 'Default model.', sources: [] },
- suites: [],
- };
-
- const build = async (experimentDisplay: Record) =>
- toEvalResult(
- rawEvalResultSchema.parse({
- experiment: 'test-experiment',
- eval: PINNED_EVAL_ID,
- interface: 'cli',
- experimentDisplay,
- }),
- 'test-experiment/run-1/result.json',
- await readPrompt(PINNED_EVAL_ID),
- experimentData
- );
-
- it('attaches the rationale when the run matches the config', async () => {
- expect((await build(display)).experimentRationale).toBeDefined();
- });
-
- it('drops the rationale when the run used a different effort', async () => {
- const result = await build({ ...display, reasoningEffort: 'high' });
- expect(result.experimentRationale).toBeUndefined();
- });
-});
diff --git a/apps/framework/scripts/export-results.ts b/apps/framework/scripts/export-results.ts
index 11b61db2b..04c9c6f42 100644
--- a/apps/framework/scripts/export-results.ts
+++ b/apps/framework/scripts/export-results.ts
@@ -7,7 +7,6 @@ import { rawEvalResultSchema } from '@supabase-evals/core/eval-metadata';
import type {
EvalResult,
EvalSuite,
- ExperimentDisplayMetadata,
ExperimentSuite,
} from '@supabase-evals/core/eval-metadata';
import {
@@ -55,14 +54,6 @@ const EXPECTED_RUNS_FLAG = readFlag(rawArgs, 'runs');
const OUTPUT_FLAG = readRepeatedFlag(rawArgs, 'output')[0];
const outputPath = OUTPUT_FLAG ? resolve(ROOT, OUTPUT_FLAG) : OUTPUT_PATH;
-/** True when the run used the model and effort the experiment config names today. */
-function sameModelAndEffort(
- a: ExperimentDisplayMetadata,
- b: ExperimentDisplayMetadata | undefined
-) {
- return a.modelId === b?.modelId && a.reasoningEffort === b?.reasoningEffort;
-}
-
export function toEvalResult(
parsedResult: ReturnType,
sourcePath: string,
@@ -79,14 +70,6 @@ export function toEvalResult(
experimentSuite,
experimentDisplay:
parsedResult.experimentDisplay ?? experimentData?.display,
- experimentRationale:
- !parsedResult.experimentDisplay ||
- sameModelAndEffort(
- parsedResult.experimentDisplay,
- experimentData?.display
- )
- ? experimentData?.rationale
- : undefined,
eval: parsedResult.eval,
stage: promptData?.stage ?? parsedResult.stage,
product: promptData?.product ?? parsedResult.product,
diff --git a/apps/web/package.json b/apps/web/package.json
index c02742a1b..42d6c0944 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -4,13 +4,13 @@
"version": "0.0.1",
"type": "module",
"scripts": {
- "dev": "vite",
- "build": "node -e \"console.log('Node.js version:', process.version)\" && tsc -b && vite build && vite build --ssr src/entry-server.tsx --outDir dist-ssr && node scripts/prerender.ts",
+ "dev": "pnpm --filter @supabase-evals/framework exec node --import tsx/esm scripts/check-experiment-rationales.ts && vite",
+ "build": "pnpm --filter @supabase-evals/framework exec node --import tsx/esm scripts/check-experiment-rationales.ts && node -e \"console.log('Node.js version:', process.version)\" && tsc -b && vite build && vite build --ssr src/entry-server.tsx --outDir dist-ssr && node scripts/prerender.ts",
"lint": "eslint .",
"format": "prettier --write \"**/*.{ts,tsx}\"",
"format:check": "prettier --check \"**/*.{ts,tsx}\"",
- "typecheck": "tsc -b --noEmit",
- "test": "vitest run",
+ "typecheck": "pnpm --filter @supabase-evals/framework exec node --import tsx/esm scripts/check-experiment-rationales.ts && tsc -b --noEmit",
+ "test": "pnpm --filter @supabase-evals/framework exec node --import tsx/esm scripts/check-experiment-rationales.ts && vitest run",
"preview": "vite preview"
},
"dependencies": {
diff --git a/apps/web/src/components/experiment-rationale.tsx b/apps/web/src/components/experiment-rationale.tsx
index 6abf79c34..b9e830549 100644
--- a/apps/web/src/components/experiment-rationale.tsx
+++ b/apps/web/src/components/experiment-rationale.tsx
@@ -24,8 +24,7 @@ export function ExperimentRationale() {
Why these experiments
- Where an agent has a default model and effort, we use it. Where we
- picked one ourselves, we say why.
+ Why we chose the model and effort for each benchmark experiment.
{experiments.map((experiment) => {
@@ -56,6 +55,11 @@ export function ExperimentRationale() {
{rationale?.summary}
+ {rationale && (
+
+ As of {rationale.asOf}
+
+ )}
)
})}
diff --git a/apps/web/src/data/experiment-rationales.json b/apps/web/src/data/experiment-rationales.json
new file mode 100644
index 000000000..0bbf03913
--- /dev/null
+++ b/apps/web/src/data/experiment-rationales.json
@@ -0,0 +1,158 @@
+[
+ {
+ "experiment": "claude-code-opus-5.5",
+ "display": {
+ "agent": "claude-code",
+ "modelProvider": "anthropic",
+ "modelId": "claude-opus-5-5",
+ "reasoningEffort": "medium"
+ },
+ "rationale": {
+ "summary": "Claude Code's default model on most plans, at its default effort.",
+ "sources": [
+ {
+ "label": "Default model",
+ "url": "https://code.claude.com/docs/en/model-config#default-model-setting"
+ },
+ {
+ "label": "Default effort",
+ "url": "https://code.claude.com/docs/en/model-config#adjust-effort-level"
+ }
+ ],
+ "asOf": "2026-10-26"
+ }
+ },
+ {
+ "experiment": "claude-code-sonnet-5",
+ "display": {
+ "agent": "claude-code",
+ "modelProvider": "anthropic",
+ "modelId": "claude-sonnet-5",
+ "reasoningEffort": "high"
+ },
+ "rationale": {
+ "summary": "Anthropic's latest Sonnet model for coding, at Claude Code's default high effort.",
+ "sources": [
+ {
+ "label": "Sonnet 5 announcement",
+ "url": "https://www.anthropic.com/news/claude-sonnet-5"
+ },
+ {
+ "label": "Default effort",
+ "url": "https://platform.claude.com/docs/en/models/sonnet-5/overview"
+ }
+ ],
+ "asOf": "2026-07-02"
+ }
+ },
+ {
+ "experiment": "claude-code-sonnet-5.5",
+ "display": {
+ "agent": "claude-code",
+ "modelProvider": "anthropic",
+ "modelId": "claude-sonnet-5-5",
+ "reasoningEffort": "medium"
+ },
+ "rationale": {
+ "summary": "Claude Code's Sonnet model for daily coding tasks, at its default effort.",
+ "sources": [
+ {
+ "label": "Model aliases",
+ "url": "https://code.claude.com/docs/en/model-config"
+ },
+ {
+ "label": "Default effort",
+ "url": "https://code.claude.com/docs/en/model-config#adjust-effort-level"
+ }
+ ],
+ "asOf": "2026-10-26"
+ }
+ },
+ {
+ "experiment": "codex-gpt-6-luna",
+ "display": {
+ "agent": "codex",
+ "modelProvider": "openai",
+ "modelId": "gpt-6-luna",
+ "reasoningEffort": "medium"
+ },
+ "rationale": {
+ "summary": "A cost-effective model for everyday tasks, and the one behind the Supabase dashboard assistant.",
+ "sources": [
+ {
+ "label": "Dashboard assistant default model",
+ "url": "https://github.com/supabase/supabase/blob/d98c277790766d2ceb905b675eb0a718f25e6345/apps/studio/lib/ai/model.utils.ts#L92"
+ }
+ ],
+ "asOf": "2026-10-26"
+ }
+ },
+ {
+ "experiment": "codex-gpt-6.1-sol",
+ "display": {
+ "agent": "codex",
+ "modelProvider": "openai",
+ "modelId": "gpt-6.1-sol",
+ "reasoningEffort": "medium"
+ },
+ "rationale": {
+ "summary": "Codex's default model, at its default effort.",
+ "sources": [
+ {
+ "label": "Where the catalog comes from",
+ "url": "https://github.com/openai/codex/blob/03748ad5e18a3c9c1f0828561dcf1f3f0d7b2f77/codex-rs/models-manager/src/manager.rs#L394-L436"
+ },
+ {
+ "label": "How the default is picked",
+ "url": "https://github.com/openai/codex/blob/7d4c7a0767cdf81ff76c8fa5a05c22929568e3c6/codex-rs/models-manager/src/manager.rs#L845-L851"
+ }
+ ],
+ "asOf": "2026-10-26"
+ }
+ },
+ {
+ "experiment": "grok-4.7",
+ "display": {
+ "agent": "grok",
+ "modelProvider": "spacexai",
+ "modelId": "grok-4.7",
+ "reasoningEffort": "high"
+ },
+ "rationale": {
+ "summary": "Grok Build's default model, at its default effort.",
+ "sources": [
+ {
+ "label": "Grok 4.7 and Grok Build",
+ "url": "https://docs.x.ai/developers/grok-4-7"
+ },
+ {
+ "label": "Default effort",
+ "url": "https://docs.x.ai/developers/models/grok-4.7"
+ }
+ ],
+ "asOf": "2026-10-26"
+ }
+ },
+ {
+ "experiment": "opencode-kimi-k3",
+ "display": {
+ "agent": "opencode",
+ "modelProvider": "moonshotai",
+ "modelId": "moonshotai/kimi-k3"
+ },
+ "rationale": {
+ "summary": "An open-weight model we picked to represent open-weight options in OpenCode.",
+ "sources": [
+ {
+ "label": "OpenCode usage data",
+ "url": "https://opencode.ai/data/"
+ },
+ {
+ "label": "Kimi K3 usage on OpenCode",
+ "url": "https://opencode.ai/data/moonshotai/kimi-k3"
+ }
+ ],
+ "asOf": "2026-10-26"
+ }
+ }
+]
diff --git a/apps/web/src/lib/eval-results.test.ts b/apps/web/src/lib/eval-results.test.ts
index 67e05ed5a..208ea8066 100644
--- a/apps/web/src/lib/eval-results.test.ts
+++ b/apps/web/src/lib/eval-results.test.ts
@@ -1,11 +1,13 @@
import { describe, expect, it } from "vitest"
import { makeResult } from "@/lib/eval-results.fixture"
+import { missingBenchmarkRationales } from "@/lib/experiment-rationales"
import {
EXPERIMENT_SUITES,
JOURNEY_STAGES,
UNASSIGNED_PRODUCT,
getEvalIds,
+ getExperimentRationale,
getProductKeys,
getProductResults,
getVisibleExperiments,
@@ -218,3 +220,78 @@ describe("the exported results", () => {
expect(inconsistent).toEqual([])
})
})
+
+describe("getExperimentRationale", () => {
+ const display = {
+ agent: "opencode",
+ modelProvider: "moonshotai",
+ modelId: "moonshotai/kimi-k3",
+ } as const
+ const match = makeResult({
+ experiment: "opencode-kimi-k3",
+ experimentDisplay: display,
+ })
+
+ it("shows current rationale for matching results", () => {
+ expect(
+ getExperimentRationale(match.experiment, [match])?.summary
+ ).toContain("open-weight")
+ })
+
+ it("hides rationale for mixed or missing metadata", () => {
+ const other = makeResult({
+ experiment: match.experiment,
+ experimentDisplay: { ...display, modelId: "other" },
+ })
+ const missing = makeResult({ experiment: match.experiment })
+ expect(
+ getExperimentRationale(match.experiment, [match, other])
+ ).toBeUndefined()
+ expect(
+ getExperimentRationale(match.experiment, [match, missing])
+ ).toBeUndefined()
+ expect(
+ getExperimentRationale("removed-experiment", [match])
+ ).toBeUndefined()
+ })
+})
+
+describe("benchmark rationale coverage", () => {
+ const kimi = {
+ agent: "opencode",
+ modelProvider: "moonshotai",
+ modelId: "moonshotai/kimi-k3",
+ } as const
+
+ it("requires an entry matching each current benchmark model and effort", () => {
+ expect(
+ missingBenchmarkRationales([
+ ["opencode-kimi-k3", { display: kimi, suites: ["benchmark"] }],
+ ["new-experiment", { display: kimi, suites: ["benchmark"] }],
+ [
+ "opencode-kimi-k3",
+ {
+ display: { ...kimi, reasoningEffort: "high" },
+ suites: ["benchmark"],
+ },
+ ],
+ ["other", { display: kimi, suites: ["no-skills"] }],
+ ])
+ ).toEqual(["new-experiment", "opencode-kimi-k3"])
+ })
+
+ it("shows the historical Sonnet rationale when its results are present", () => {
+ const old = makeResult({
+ experiment: "claude-code-sonnet-5",
+ experimentDisplay: {
+ agent: "claude-code",
+ modelProvider: "anthropic",
+ modelId: "claude-sonnet-5",
+ reasoningEffort: "high",
+ },
+ })
+ expect(getExperimentRationale(old.experiment, [old])?.asOf).toBe(
+ "2026-07-02"
+ )
+ })
+})
diff --git a/apps/web/src/lib/eval-results.ts b/apps/web/src/lib/eval-results.ts
index a10da0f47..55efddcd2 100644
--- a/apps/web/src/lib/eval-results.ts
+++ b/apps/web/src/lib/eval-results.ts
@@ -4,6 +4,10 @@ import {
type EvalResult,
} from "@supabase-evals/core/eval-metadata"
import rawResults from "@/data/eval-results.json"
+import {
+ experimentRationales,
+ sameExperimentDisplay,
+} from "@/lib/experiment-rationales"
/**
* The exported eval results and the queries the site runs over them. Everything
@@ -123,9 +127,24 @@ export function getExperimentDisplay(experiment: string) {
?.experimentDisplay
}
-export function getExperimentRationale(experiment: string) {
- return sortedResults.find((result) => result.experiment === experiment)
- ?.experimentRationale
+export function getExperimentRationale(
+ experiment: string,
+ sourceResults = sortedResults
+) {
+ const results = sourceResults.filter(
+ (result) => result.experiment === experiment
+ )
+ if (!results.length) return undefined
+
+ return experimentRationales.find(
+ (entry) =>
+ entry.experiment === experiment &&
+ results.every(
+ ({ experimentDisplay }) =>
+ experimentDisplay &&
+ sameExperimentDisplay(experimentDisplay, entry.display)
+ )
+ )?.rationale
}
export function getExperimentResults(
diff --git a/apps/web/src/lib/experiment-rationales.ts b/apps/web/src/lib/experiment-rationales.ts
new file mode 100644
index 000000000..fb2c6daf9
--- /dev/null
+++ b/apps/web/src/lib/experiment-rationales.ts
@@ -0,0 +1,51 @@
+import { z } from "zod"
+import {
+ experimentDisplayMetadataSchema,
+ experimentRationaleSchema,
+ type ExperimentDisplayMetadata,
+ type ExperimentSuite,
+} from "@supabase-evals/core/eval-metadata"
+import rawRationales from "../data/experiment-rationales.json" with { type: "json" }
+
+export const experimentRationales = z
+ .array(
+ z.object({
+ experiment: z.string(),
+ display: experimentDisplayMetadataSchema,
+ rationale: experimentRationaleSchema.extend({
+ summary: z.string().trim().min(1),
+ asOf: z.iso.date(),
+ }),
+ })
+ )
+ .parse(rawRationales)
+
+export function sameExperimentDisplay(
+ a: ExperimentDisplayMetadata,
+ b: ExperimentDisplayMetadata
+) {
+ return (
+ a.agent === b.agent &&
+ a.modelProvider === b.modelProvider &&
+ a.modelId === b.modelId &&
+ a.reasoningEffort === b.reasoningEffort
+ )
+}
+
+export function missingBenchmarkRationales(
+ experiments: Iterable<
+ [string, { display: ExperimentDisplayMetadata; suites: ExperimentSuite[] }]
+ >
+) {
+ return Array.from(experiments)
+ .filter(([, config]) => config.suites.includes("benchmark"))
+ .filter(
+ ([name, config]) =>
+ !experimentRationales.some(
+ (entry) =>
+ entry.experiment === name &&
+ sameExperimentDisplay(entry.display, config.display)
+ )
+ )
+ .map(([name]) => name)
+}
diff --git a/experiments/presets.ts b/experiments/presets.ts
index 8cb416364..991c68d93 100644
--- a/experiments/presets.ts
+++ b/experiments/presets.ts
@@ -11,7 +11,7 @@ import { localStackRuntime } from '@supabase-evals/sandbox';
type ExperimentPreset = Pick<
ExperimentConfig,
- 'agent' | 'rationale' | 'runtime' | 'localStack' | 'skills'
+ 'agent' | 'runtime' | 'localStack' | 'skills'
>;
const skills = ['supabase', 'supabase-postgres-best-practices'];
@@ -27,20 +27,6 @@ export const claudeCodeOpus55 = {
model: 'claude-opus-5-5',
reasoningEffort: 'medium',
}),
- rationale: {
- summary:
- "Claude Code's default model on most plans, at its default effort.",
- sources: [
- {
- label: 'Default model',
- url: 'https://code.claude.com/docs/en/model-config#default-model-setting',
- },
- {
- label: 'Default effort',
- url: 'https://code.claude.com/docs/en/model-config#adjust-effort-level',
- },
- ],
- },
runtime: defaultRuntime(),
localStack: localStackRuntime(),
skills,
@@ -51,20 +37,6 @@ export const claudeCodeSonnet55 = {
model: 'claude-sonnet-5-5',
reasoningEffort: 'medium',
}),
- rationale: {
- summary:
- "Claude Code's Sonnet model for daily coding tasks, at its default effort.",
- sources: [
- {
- label: 'Model aliases',
- url: 'https://code.claude.com/docs/en/model-config',
- },
- {
- label: 'Default effort',
- url: 'https://code.claude.com/docs/en/model-config#adjust-effort-level',
- },
- ],
- },
runtime: defaultRuntime(),
localStack: localStackRuntime(),
skills,
@@ -75,16 +47,6 @@ export const codexGpt6Luna = {
model: 'gpt-6-luna',
reasoningEffort: 'medium',
}),
- rationale: {
- summary:
- 'A cost-effective model for everyday tasks, and the one behind the Supabase dashboard assistant.',
- sources: [
- {
- label: 'Dashboard assistant default model',
- url: 'https://github.com/supabase/supabase/blob/d98c277790766d2ceb905b675eb0a718f25e6345/apps/studio/lib/ai/model.utils.ts#L92',
- },
- ],
- },
runtime: defaultRuntime(),
localStack: localStackRuntime(),
skills,
@@ -95,19 +57,6 @@ export const codexGpt61Sol = {
model: 'gpt-6.1-sol',
reasoningEffort: 'medium',
}),
- rationale: {
- summary: "Codex's default model, at its default effort.",
- sources: [
- {
- label: 'Where the catalog comes from',
- url: 'https://github.com/openai/codex/blob/03748ad5e18a3c9c1f0828561dcf1f3f0d7b2f77/codex-rs/models-manager/src/manager.rs#L394-L436',
- },
- {
- label: 'How the default is picked',
- url: 'https://github.com/openai/codex/blob/7d4c7a0767cdf81ff76c8fa5a05c22929568e3c6/codex-rs/models-manager/src/manager.rs#L845-L851',
- },
- ],
- },
runtime: defaultRuntime(),
localStack: localStackRuntime(),
skills,
@@ -118,19 +67,6 @@ export const grok47 = {
model: 'grok-4.7',
reasoningEffort: 'high',
}),
- rationale: {
- summary: "Grok Build's default model, at its default effort.",
- sources: [
- {
- label: 'Grok 4.7 and Grok Build',
- url: 'https://docs.x.ai/developers/grok-4-7',
- },
- {
- label: 'Default effort',
- url: 'https://docs.x.ai/developers/models/grok-4.7',
- },
- ],
- },
runtime: defaultRuntime(),
localStack: localStackRuntime(),
skills,
@@ -140,17 +76,6 @@ export const opencodeKimiK3 = {
agent: opencodeAgent({
model: 'moonshotai/kimi-k3',
}),
- rationale: {
- summary:
- 'An open-weight model we picked to represent open-weight options in OpenCode.',
- sources: [
- { label: 'OpenCode usage data', url: 'https://opencode.ai/data/' },
- {
- label: 'Kimi K3 usage on OpenCode',
- url: 'https://opencode.ai/data/moonshotai/kimi-k3',
- },
- ],
- },
runtime: defaultRuntime(),
localStack: localStackRuntime(),
skills,
diff --git a/packages/core/src/eval-metadata.ts b/packages/core/src/eval-metadata.ts
index f18fd0e3e..97d2c0326 100644
--- a/packages/core/src/eval-metadata.ts
+++ b/packages/core/src/eval-metadata.ts
@@ -431,7 +431,6 @@ const evalResultShape = {
experimentSuite: experimentSuiteSchema.optional(),
profile: experimentSuiteSchema.optional(),
experimentDisplay: experimentDisplayMetadataSchema.optional(),
- experimentRationale: experimentRationaleSchema.optional(),
eval: z.string(),
stage: evalStageSchema.optional(),
product: z.array(evalProductSchema).optional(),
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 7bf86cf7d..ef8bae3ee 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -46,7 +46,6 @@ import type {
EvalMetadata,
EvalSuite,
ExperimentDisplayMetadata,
- ExperimentRationale,
ExperimentSuite,
JudgeCall,
ModelProvider,
@@ -94,7 +93,6 @@ export {
evalSuiteSchema,
experimentSuiteSchema,
experimentDisplayMetadataSchema,
- experimentRationaleSchema,
modelProviderSchema,
rawEvalResultSchema,
reasoningEffortSchema,
@@ -146,7 +144,6 @@ export type {
EvalSuite,
EvalStage,
ExperimentDisplayMetadata,
- ExperimentRationale,
ExperimentSuite,
ModelProvider,
ParsedEvalMarkdown,
@@ -648,8 +645,6 @@ export type ExperimentConfig = {
/** Named experiment suites this experiment belongs to. */
suite?: ExperimentSuite[];
agent: AgentHarness;
- /** Why this model and effort, shown next to the experiment on the results page. */
- rationale?: ExperimentRationale;
runtime: EvalRuntime;
/** Local-stack environment (e.g. localStackRuntime() from @supabase-evals/sandbox). */
localStack?: LocalStackRuntime;
From f7c2341adc027be1f0fe6ebc2ee28f661eb0fc3e Mon Sep 17 00:00:00 2001
From: Matt Rossman <22670878+mattrossman@users.noreply.github.com>
Date: Mon, 9 Nov 2026 12:47:40 -0500
Subject: [PATCH 10/18] refactor: link benchmark rationale claims inline
---
.../src/components/experiment-rationale.tsx | 29 +++----
apps/web/src/data/experiment-rationales.json | 85 +++++++++----------
apps/web/src/lib/eval-results.test.ts | 16 +++-
apps/web/src/lib/experiment-rationales.ts | 12 ++-
packages/core/src/eval-metadata.ts | 7 --
5 files changed, 80 insertions(+), 69 deletions(-)
diff --git a/apps/web/src/components/experiment-rationale.tsx b/apps/web/src/components/experiment-rationale.tsx
index b9e830549..5b88a3e8a 100644
--- a/apps/web/src/components/experiment-rationale.tsx
+++ b/apps/web/src/components/experiment-rationale.tsx
@@ -34,26 +34,25 @@ export function ExperimentRationale() {
key={experiment}
className="flex flex-col gap-1 px-5 py-3 text-sm"
>
-
-
-
-
-
- {rationale?.sources.map((source) => (
+
+
+
+
+ {rationale?.summary.map((part, index) =>
+ typeof part === "string" ? (
+ part
+ ) : (
- {source.label}
+ {part.text}
- ))}
-
-
-
- {rationale?.summary}
+ )
+ )}
{rationale && (
diff --git a/apps/web/src/data/experiment-rationales.json b/apps/web/src/data/experiment-rationales.json
index f0449bea3..9dba1adec 100644
--- a/apps/web/src/data/experiment-rationales.json
+++ b/apps/web/src/data/experiment-rationales.json
@@ -8,16 +8,18 @@
"reasoningEffort": "medium"
},
"rationale": {
- "summary": "Claude Code's default model on most plans, at its default effort.",
- "sources": [
+ "summary": [
+ "Claude Code's ",
{
- "label": "Default model",
+ "text": "default model",
"url": "https://code.claude.com/docs/en/model-config#default-model-setting"
},
+ " on most plans, at its ",
{
- "label": "Default effort",
+ "text": "default effort",
"url": "https://code.claude.com/docs/en/model-config#adjust-effort-level"
- }
+ },
+ "."
],
"asOf": "2026-10-26"
}
@@ -31,16 +33,18 @@
"reasoningEffort": "high"
},
"rationale": {
- "summary": "Anthropic's latest Sonnet model for coding, at Claude Code's default high effort.",
- "sources": [
+ "summary": [
+ "Anthropic's ",
{
- "label": "Sonnet 5 announcement",
+ "text": "latest Sonnet model for coding",
"url": "https://www.anthropic.com/news/claude-sonnet-5"
},
+ ", at Claude Code's ",
{
- "label": "Default effort",
+ "text": "default high effort",
"url": "https://platform.claude.com/docs/en/models/sonnet-5/overview"
- }
+ },
+ "."
],
"asOf": "2026-07-02"
}
@@ -54,16 +58,18 @@
"reasoningEffort": "medium"
},
"rationale": {
- "summary": "Claude Code's Sonnet model for daily coding tasks, at its default effort.",
- "sources": [
+ "summary": [
+ "Claude Code's ",
{
- "label": "Model aliases",
+ "text": "Sonnet model for daily coding tasks",
"url": "https://code.claude.com/docs/en/model-config"
},
+ ", at its ",
{
- "label": "Default effort",
+ "text": "default effort",
"url": "https://code.claude.com/docs/en/model-config#adjust-effort-level"
- }
+ },
+ "."
],
"asOf": "2026-10-26"
}
@@ -77,12 +83,13 @@
"reasoningEffort": "medium"
},
"rationale": {
- "summary": "A cost-effective model for everyday tasks, and the one behind the Supabase dashboard assistant.",
- "sources": [
+ "summary": [
+ "A cost-effective model for everyday tasks, and the one behind the ",
{
- "label": "Dashboard assistant default model",
+ "text": "Supabase dashboard assistant",
"url": "https://github.com/supabase/supabase/blob/d98c277790766d2ceb905b675eb0a718f25e6345/apps/studio/lib/ai/model.utils.ts#L92"
- }
+ },
+ "."
],
"asOf": "2026-10-26"
}
@@ -96,20 +103,13 @@
"reasoningEffort": "medium"
},
"rationale": {
- "summary": "Codex's default model for ChatGPT accounts, at its default effort.",
- "sources": [
+ "summary": [
+ "Codex's ",
{
- "label": "Model and effort guidance",
+ "text": "default model for ChatGPT accounts, at its default effort",
"url": "https://learn.chatgpt.com/docs/models?surface=app#pick-a-reasoning-effort"
},
- {
- "label": "Where the catalog comes from",
- "url": "https://github.com/openai/codex/blob/03748ad5e18a3c9c1f0828561dcf1f3f0d7b2f77/codex-rs/models-manager/src/manager.rs#L394-L436"
- },
- {
- "label": "How the default is picked",
- "url": "https://github.com/openai/codex/blob/7d4c7a0767cdf81ff76c8fa5a05c22929568e3c6/codex-rs/models-manager/src/manager.rs#L845-L851"
- }
+ "."
],
"asOf": "2026-11-09"
}
@@ -123,16 +123,18 @@
"reasoningEffort": "high"
},
"rationale": {
- "summary": "Chosen to match Grok Build's default model and effort when selected.",
- "sources": [
+ "summary": [
+ "Grok Build's ",
{
- "label": "Grok 4.7 and Grok Build",
+ "text": "default model",
"url": "https://docs.x.ai/developers/grok-4-7"
},
+ ", at its ",
{
- "label": "Model catalog",
+ "text": "default effort",
"url": "https://github.com/xai-org/grok-build/blob/2bdd1d6a6369de0e8c68132ea4539e9abd9e14a8/crates/codegen/xai-grok-shell/src/remote/client.rs#L832-L862"
- }
+ },
+ "."
],
"asOf": "2026-10-26"
}
@@ -145,16 +147,13 @@
"modelId": "moonshotai/kimi-k3"
},
"rationale": {
- "summary": "An open-weight model we picked to represent open-weight options in OpenCode.",
- "sources": [
+ "summary": [
+ "Included as an ",
{
- "label": "OpenCode usage data",
- "url": "https://opencode.ai/data/"
- },
- {
- "label": "Kimi K3 usage on OpenCode",
+ "text": "open-weight option",
"url": "https://opencode.ai/data/moonshotai/kimi-k3"
- }
+ },
+ " in OpenCode."
],
"asOf": "2026-10-26"
}
diff --git a/apps/web/src/lib/eval-results.test.ts b/apps/web/src/lib/eval-results.test.ts
index c1b19035c..5196e12ce 100644
--- a/apps/web/src/lib/eval-results.test.ts
+++ b/apps/web/src/lib/eval-results.test.ts
@@ -1,3 +1,6 @@
+import { createElement } from "react"
+import { renderToStaticMarkup } from "react-dom/server"
+import { ExperimentRationale } from "@/components/experiment-rationale"
import { describe, expect, it } from "vitest"
import { makeResult } from "@/lib/eval-results.fixture"
@@ -235,7 +238,7 @@ describe("getExperimentRationale", () => {
it("shows current rationale for matching results", () => {
expect(
getExperimentRationale(match.experiment, [match])?.summary
- ).toContain("open-weight")
+ ).toContainEqual(expect.objectContaining({ text: "open-weight option" }))
})
it("hides rationale for mixed or missing metadata", () => {
@@ -295,3 +298,14 @@ describe("benchmark rationale coverage", () => {
)
})
})
+
+describe("inline rationale links", () => {
+ it("links the claim inside the sentence", () => {
+ const html = renderToStaticMarkup(createElement(ExperimentRationale))
+ expect(html).toContain(
+ 'href="https://code.claude.com/docs/en/model-config#default-model-setting"'
+ )
+ expect(html).toContain(">default model on most plans, at its ")
+ expect(html).toContain(">default effort.")
+ })
+})
diff --git a/apps/web/src/lib/experiment-rationales.ts b/apps/web/src/lib/experiment-rationales.ts
index eec6d588d..cef155ea0 100644
--- a/apps/web/src/lib/experiment-rationales.ts
+++ b/apps/web/src/lib/experiment-rationales.ts
@@ -1,7 +1,6 @@
import { z } from "zod"
import {
experimentDisplayMetadataSchema,
- experimentRationaleSchema,
type ExperimentDisplayMetadata,
} from "@supabase-evals/core/eval-metadata"
import rawRationales from "../data/experiment-rationales.json" with { type: "json" }
@@ -11,8 +10,15 @@ export const experimentRationales = z
z.object({
experiment: z.string(),
display: experimentDisplayMetadataSchema,
- rationale: experimentRationaleSchema.extend({
- summary: z.string().trim().min(1),
+ rationale: z.object({
+ summary: z
+ .array(
+ z.union([
+ z.string().min(1),
+ z.object({ text: z.string().min(1), url: z.url() }),
+ ])
+ )
+ .min(1),
asOf: z.iso.date(),
}),
})
diff --git a/packages/core/src/eval-metadata.ts b/packages/core/src/eval-metadata.ts
index e3aeb1255..0762689f1 100644
--- a/packages/core/src/eval-metadata.ts
+++ b/packages/core/src/eval-metadata.ts
@@ -87,13 +87,6 @@ export type ExperimentDisplayMetadata = z.infer<
typeof experimentDisplayMetadataSchema
>;
-/** Why an experiment uses its model and effort, with links to check it. */
-export const experimentRationaleSchema = z.object({
- summary: z.string(),
- sources: z.array(z.object({ label: z.string(), url: z.string().url() })),
-});
-export type ExperimentRationale = z.infer;
-
/**
* Interface(s) the agent uses to act on Supabase — a benchmark dimension
* (cross-team KPI), not the runtime switch. `mcp` = the platform-lite MCP/tool
From e3cdcc8085614b4a355071d6a817cb1be1a3b197 Mon Sep 17 00:00:00 2001
From: Matt Rossman <22670878+mattrossman@users.noreply.github.com>
Date: Mon, 9 Nov 2026 14:13:06 -0500
Subject: [PATCH 11/18] style: clarify benchmark rationale hierarchy
---
.../src/components/experiment-rationale.tsx | 39 +++++++++++++------
1 file changed, 27 insertions(+), 12 deletions(-)
diff --git a/apps/web/src/components/experiment-rationale.tsx b/apps/web/src/components/experiment-rationale.tsx
index 5b88a3e8a..994ce57ca 100644
--- a/apps/web/src/components/experiment-rationale.tsx
+++ b/apps/web/src/components/experiment-rationale.tsx
@@ -30,14 +30,11 @@ export function ExperimentRationale() {
{experiments.map((experiment) => {
const rationale = getExperimentRationale(experiment)
return (
-
-
+
+
-
+
{rationale?.summary.map((part, index) =>
typeof part === "string" ? (
part
@@ -47,22 +44,40 @@ export function ExperimentRationale() {
href={part.url}
target="_blank"
rel="noreferrer"
- className="underline underline-offset-2 hover:text-foreground"
+ className="underline decoration-muted-foreground/40 underline-offset-4 hover:text-foreground hover:decoration-foreground"
>
{part.text}
)
)}
- {rationale && (
-
- As of {rationale.asOf}
-
- )}
)
})}
+
+
+ Source review dates
+
+
+ When the sources supporting each model and effort choice were last
+ reviewed.
+
+
+ {experiments.map((experiment) => {
+ const rationale = getExperimentRationale(experiment)
+ return (
+
+
+ {rationale?.asOf}
+
+ )
+ })}
+
+
)
}
From 3b210aaea7e7ca46f37840fb1c5400bef08f5d86 Mon Sep 17 00:00:00 2001
From: Matt Rossman <22670878+mattrossman@users.noreply.github.com>
Date: Mon, 9 Nov 2026 14:16:49 -0500
Subject: [PATCH 12/18] style: align source review dates
---
.../src/components/experiment-rationale.tsx | 34 +++++++++++--------
1 file changed, 20 insertions(+), 14 deletions(-)
diff --git a/apps/web/src/components/experiment-rationale.tsx b/apps/web/src/components/experiment-rationale.tsx
index 994ce57ca..d910ced17 100644
--- a/apps/web/src/components/experiment-rationale.tsx
+++ b/apps/web/src/components/experiment-rationale.tsx
@@ -63,20 +63,26 @@ export function ExperimentRationale() {
When the sources supporting each model and effort choice were last
reviewed.
-
- {experiments.map((experiment) => {
- const rationale = getExperimentRationale(experiment)
- return (
-
-
- {rationale?.asOf}
-
- )
- })}
-
+
+
+ Source review dates by experiment
+
+
+ {experiments.map((experiment) => {
+ const rationale = getExperimentRationale(experiment)
+ return (
+
+
+
+
+
+ {rationale?.asOf}
+
+
+ )
+ })}
+
+
)
From bde2808d2c77ea0d595b530d990ab7aa3c15ecc0 Mon Sep 17 00:00:00 2001
From: Matt Rossman <22670878+mattrossman@users.noreply.github.com>
Date: Mon, 9 Nov 2026 15:07:41 -0500
Subject: [PATCH 13/18] fix: scope prerender count to results rows
---
apps/web/scripts/prerender.ts | 15 ++++-----------
apps/web/src/components/results/results-table.tsx | 1 +
2 files changed, 5 insertions(+), 11 deletions(-)
diff --git a/apps/web/scripts/prerender.ts b/apps/web/scripts/prerender.ts
index d2ada590e..5b1d0ac0d 100644
--- a/apps/web/scripts/prerender.ts
+++ b/apps/web/scripts/prerender.ts
@@ -12,8 +12,6 @@ const htmlFile = path.join(appDir, "dist", "index.html")
const serverEntryFile = path.join(appDir, "dist-ssr", "entry-server.js")
const ROOT_DIV = '
'
-/** The results table renders one header row above its data rows. */
-const HEADER_ROWS = 1
/** Rendered by App when the results file parses to nothing. */
const EMPTY_STATE = "No result files found"
@@ -73,15 +71,10 @@ if (markup.includes(EMPTY_STATE)) {
fail("the app rendered its empty state, so the results data did not load")
}
-const expectedRows = expectedTableRows + HEADER_ROWS
-const tableRows = markup.match(/
Date: Mon, 9 Nov 2026 15:31:48 -0500
Subject: [PATCH 14/18] refactor: attach rationales to published experiments
---
CONTRIBUTING.md | 4 +-
.../scripts/check-experiment-rationales.ts | 7 -
apps/web/package.json | 8 +-
apps/web/scripts/fetch-braintrust.test.ts | 41 +++++
apps/web/scripts/fetch-braintrust.ts | 98 ++++++-----
.../components/experiment-rationale.test.tsx | 26 +++
.../src/components/experiment-rationale.tsx | 1 -
apps/web/src/data/experiment-rationales.json | 161 ------------------
apps/web/src/data/publication.ts | 130 ++++++++++++++
apps/web/src/lib/eval-results.test.ts | 100 ++---------
apps/web/src/lib/eval-results.ts | 20 +--
apps/web/src/lib/experiment-rationales.ts | 55 ------
packages/core/src/eval-metadata.ts | 13 ++
13 files changed, 283 insertions(+), 381 deletions(-)
delete mode 100644 apps/framework/scripts/check-experiment-rationales.ts
create mode 100644 apps/web/src/components/experiment-rationale.test.tsx
delete mode 100644 apps/web/src/data/experiment-rationales.json
create mode 100644 apps/web/src/data/publication.ts
delete mode 100644 apps/web/src/lib/experiment-rationales.ts
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index dcb037620..1b725930c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -64,9 +64,9 @@ Include refreshed results for PRs with new/changed evals so a reviewer can inspe
## Publishing and consuming results
-To publish a benchmark, upload complete runs with `--runs` and update the experiment IDs in [fetch-braintrust.ts](apps/web/scripts/fetch-braintrust.ts). Include every experiment in `experiments/benchmark/`, using its name as the key.
+To publish a benchmark, upload complete runs with `--runs` and update the pins and rationales in [publication.ts](apps/web/src/data/publication.ts). Include every experiment in `experiments/benchmark/`.
-For local UI work with a partial run, temporarily pin only the uploaded experiment IDs and start the server with `EVALS_ALLOW_PARTIAL=1 pnpm web`.
+For local UI work with a partial run, temporarily keep only the uploaded experiment IDs and their rationales in the catalog and start the server with `EVALS_ALLOW_PARTIAL=1 pnpm web`.
The published benchmark is at https://supabase.com/evals/eval-results.json. For other suites or history, query the Evals Braintrust project. For example, replace `EXPERIMENT_ID` below to read an experiment's scored runs:
diff --git a/apps/framework/scripts/check-experiment-rationales.ts b/apps/framework/scripts/check-experiment-rationales.ts
deleted file mode 100644
index a6189f1f9..000000000
--- a/apps/framework/scripts/check-experiment-rationales.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { loadExperimentMetadata } from '../lib/result-files.js';
-import { missingBenchmarkRationales } from '../../web/src/lib/experiment-rationales.js';
-
-const missing = missingBenchmarkRationales(await loadExperimentMetadata());
-if (missing.length) {
- throw new Error(`Missing benchmark rationales: ${missing.join(', ')}`);
-}
diff --git a/apps/web/package.json b/apps/web/package.json
index a93390533..edd1c4655 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -4,13 +4,13 @@
"version": "0.0.1",
"type": "module",
"scripts": {
- "dev": "pnpm --filter @supabase-evals/framework exec node --import tsx/esm scripts/check-experiment-rationales.ts && node --env-file-if-exists=../../.env scripts/fetch-braintrust.ts --dev && vite",
- "build": "pnpm --filter @supabase-evals/framework exec node --import tsx/esm scripts/check-experiment-rationales.ts && node --env-file-if-exists=../../.env scripts/fetch-braintrust.ts && tsc -b && vite build && vite build --ssr src/entry-server.tsx --outDir dist-ssr && node scripts/prerender.ts",
+ "dev": "node --env-file-if-exists=../../.env scripts/fetch-braintrust.ts --dev && vite",
+ "build": "node --env-file-if-exists=../../.env scripts/fetch-braintrust.ts && tsc -b && vite build && vite build --ssr src/entry-server.tsx --outDir dist-ssr && node scripts/prerender.ts",
"lint": "eslint .",
"format": "prettier --write \"**/*.{ts,tsx}\"",
"format:check": "prettier --check \"**/*.{ts,tsx}\"",
- "typecheck": "pnpm --filter @supabase-evals/framework exec node --import tsx/esm scripts/check-experiment-rationales.ts && tsc -b --noEmit",
- "test": "pnpm --filter @supabase-evals/framework exec node --import tsx/esm scripts/check-experiment-rationales.ts && vitest run",
+ "typecheck": "tsc -b --noEmit",
+ "test": "vitest run",
"preview": "vite preview"
},
"dependencies": {
diff --git a/apps/web/scripts/fetch-braintrust.test.ts b/apps/web/scripts/fetch-braintrust.test.ts
index 163e79a6d..9244b7d86 100644
--- a/apps/web/scripts/fetch-braintrust.test.ts
+++ b/apps/web/scripts/fetch-braintrust.test.ts
@@ -2,8 +2,10 @@ import { test } from "vitest"
import assert from "node:assert/strict"
import {
assemble,
+ attachRationales,
createBraintrustRequest,
fetchExperiments,
+ publicationPins,
retryAfterMs,
} from "./fetch-braintrust.ts"
@@ -59,6 +61,45 @@ const usage: Parameters[3][number] = {
const make = (roots = [root], children = [score, task], usages = [usage]) =>
assemble([experiment], roots, children, usages, ["example-eval"])
+test("carries the pinned experiment's rationale into published results", () => {
+ const rationale = {
+ summary: ["A ", { text: "model", url: "https://example.com" }, "."],
+ asOf: "2026-01-01",
+ }
+ const withoutSkills = "d2e33ddc-64f1-4392-892c-d4db9f3d0146"
+ const entries = [
+ { experiment: "example", withSkills: id, withoutSkills, rationale },
+ ]
+ const [pinned, unskilled] = attachRationales(
+ [experiment, { ...experiment, id: withoutSkills }],
+ entries
+ )
+ assert.deepEqual(unskilled.rationale, rationale)
+ const [result] = assemble(
+ [pinned],
+ [root],
+ [score, task],
+ [usage],
+ ["example-eval"]
+ )
+ assert.deepEqual(result.experimentRationale, rationale)
+ assert.throws(() => attachRationales([experiment], []), /Missing rationale/)
+})
+
+test("requires both variants outside partial mode and rejects duplicate names", () => {
+ const rationale = { summary: ["A model."], asOf: "2026-01-01" }
+ const entry = { experiment: "example", withSkills: id, rationale }
+ assert.throws(
+ () => publicationPins([entry], false),
+ /Missing pinned variants/
+ )
+ assert.deepEqual(publicationPins([entry], true), { example: id })
+ assert.throws(
+ () => publicationPins([entry, entry], true),
+ /distinct experiment names/
+ )
+})
+
test("joins failed runs, scorer checks, task duration, and trace usage", () => {
const [result] = make()
assert.equal(result.passed, false)
diff --git a/apps/web/scripts/fetch-braintrust.ts b/apps/web/scripts/fetch-braintrust.ts
index c255d4612..002afd229 100644
--- a/apps/web/scripts/fetch-braintrust.ts
+++ b/apps/web/scripts/fetch-braintrust.ts
@@ -4,25 +4,15 @@ import { fileURLToPath } from "node:url"
import { setTimeout as delay } from "node:timers/promises"
import { z } from "zod"
import pRetry, { AbortError } from "p-retry"
-import { evalResultSchema } from "@supabase-evals/core/eval-metadata"
+import {
+ evalResultSchema,
+ experimentRationaleSchema,
+} from "@supabase-evals/core/eval-metadata"
+import { publication } from "../src/data/publication.ts"
const app = resolve(dirname(fileURLToPath(import.meta.url)), "..")
const root = resolve(app, "../..")
const output = resolve(app, "src/data/braintrust-results.json")
-const publication = {
- "claude-code-opus-5.5": "c8e0f865-ca59-4703-a19d-507a234e100a",
- "claude-code-opus-5.5-no-skills": "e0fd16ad-e117-4165-8e30-9346ebe7e835",
- "claude-code-sonnet-5.5": "ede84738-9f79-428f-bdfe-9d8adb2a6489",
- "claude-code-sonnet-5.5-no-skills": "15a067ef-f04e-44f5-9f94-7b8c0b0e10f1",
- "codex-gpt-6-luna": "b4a9c5ce-60fc-4604-bc99-595269b7fd7d",
- "codex-gpt-6-luna-no-skills": "123ecf1b-1004-458c-98cf-b17450b65c92",
- "codex-gpt-6.1-sol": "d2e33ddc-64f1-4392-892c-d4db9f3d0146",
- "codex-gpt-6.1-sol-no-skills": "f2a8dad6-2d8b-4088-8f5f-165d00a24058",
- "grok-4.7": "e986dead-a1d3-4523-b47d-33a82ff1348b",
- "grok-4.7-no-skills": "545924a6-a02c-482c-9ef7-b4dfc1a993c7",
- "opencode-kimi-k3": "66f84377-4b0d-4be2-8f41-8e0756cbc40a",
- "opencode-kimi-k3-no-skills": "63331944-8a8c-4396-8add-d6fbe88f80d3",
-}
const uuid = z.uuid()
const metadataSchema = z
.object({
@@ -64,7 +54,41 @@ const usageSchema = z.object({
input_tokens: z.number().nullable(),
output_tokens: z.number().nullable(),
})
-type Experiment = z.infer
+type Experiment = z.infer & {
+ rationale?: z.infer
+}
+
+export function attachRationales(
+ experiments: Experiment[],
+ entries: typeof publication
+) {
+ return experiments.map((experiment) => {
+ const entry = entries.find(
+ ({ withSkills, withoutSkills }) =>
+ withSkills === experiment.id || withoutSkills === experiment.id
+ )
+ if (!entry) throw new Error(`Missing rationale for ${experiment.id}`)
+ return { ...experiment, rationale: entry.rationale }
+ })
+}
+
+export function publicationPins(
+ entries: typeof publication,
+ allowPartial: boolean
+) {
+ const pinEntries: [string, string][] = []
+ for (const { experiment, withSkills, withoutSkills } of entries) {
+ if ((!withSkills || !withoutSkills) && !allowPartial)
+ throw new Error(`Missing pinned variants: ${experiment}`)
+ if (withSkills) pinEntries.push([experiment, withSkills])
+ if (withoutSkills)
+ pinEntries.push([`${experiment}-no-skills`, withoutSkills])
+ }
+ const pins = Object.fromEntries(pinEntries)
+ if (Object.keys(pins).length !== pinEntries.length)
+ throw new Error("Publication must contain distinct experiment names")
+ return pins
+}
type Root = z.infer
type Child = z.infer
type Usage = z.infer
@@ -154,6 +178,7 @@ export function assemble(
}
const parsed = evalResultSchema.safeParse({
experiment: em.experiment,
+ experimentRationale: exp.rationale,
experimentDisplay: {
agent: em.agent,
modelProvider: em.model_provider,
@@ -419,37 +444,24 @@ async function main() {
"Content-Type": "application/json",
}
const request = createBraintrustRequest(headers, fetch)
- const experiments = await fetchExperiments(request, projectId, publication)
+ const pins = publicationPins(publication, allowPartial)
+ const experiments = attachRationales(
+ await fetchExperiments(request, projectId, pins),
+ publication
+ )
const experimentNames = (
await readdir(resolve(root, "experiments/benchmark"))
)
- .filter((f) => f.endsWith(".experiment.ts"))
- .map((f) => f.slice(0, -".experiment.ts".length))
- .sort()
- const selectedNames = experiments.map((e) => e.metadata.experiment).sort()
- if (
- !allowPartial &&
- JSON.stringify(experimentNames) !== JSON.stringify(selectedNames)
- ) {
- const missing = experimentNames.filter(
- (name) => !selectedNames.includes(name)
- )
- const unexpected = selectedNames.filter(
- (name) => !experimentNames.includes(name)
- )
- throw new Error(
- [
- "Pinned experiments do not match active benchmark experiments.",
- ...(missing.length ? [`Missing: ${missing.join(", ")}`] : []),
- ...(unexpected.length ? [`Unexpected: ${unexpected.join(", ")}`] : []),
- ...(process.argv.includes("--dev")
- ? [
- "To run with partial results locally, use EVALS_ALLOW_PARTIAL=1 pnpm web.",
- ]
- : []),
- ].join("\n")
+ .filter((name) => name.endsWith(".experiment.ts"))
+ .map((name) => name.slice(0, -".experiment.ts".length))
+ const missing = experimentNames.filter((name) => !(name in pins))
+ const unexpected = Object.keys(pins).filter(
+ (name) => !experimentNames.includes(name)
+ )
+ if (!allowPartial && (missing.length || unexpected.length))
+ console.warn(
+ `Published experiments differ from current benchmark. Missing: ${missing.join(", ") || "none"}. Unexpected: ${unexpected.join(", ") || "none"}.`
)
- }
const evalIds = []
for (const dir of await readdir(resolve(root, "evals/benchmark"), {
withFileTypes: true,
diff --git a/apps/web/src/components/experiment-rationale.test.tsx b/apps/web/src/components/experiment-rationale.test.tsx
new file mode 100644
index 000000000..144b591ea
--- /dev/null
+++ b/apps/web/src/components/experiment-rationale.test.tsx
@@ -0,0 +1,26 @@
+import { renderToStaticMarkup } from "react-dom/server"
+import { describe, expect, it, vi } from "vitest"
+
+vi.mock("@/lib/eval-results", () => ({
+ getSuiteResults: () => [],
+ getVisibleExperiments: () => ["test-experiment"],
+ getExperimentDisplay: () => undefined,
+ getExperimentRationale: () => ({
+ summary: [
+ "See the ",
+ { text: "model", url: "https://example.com/model" },
+ ".",
+ ],
+ asOf: "2026-01-01",
+ }),
+}))
+
+import { ExperimentRationale } from "./experiment-rationale"
+
+describe("inline rationale links", () => {
+ it("links the claim inside the sentence", () => {
+ const html = renderToStaticMarkup( )
+ expect(html).toContain('See the model .")
+ })
+})
diff --git a/apps/web/src/components/experiment-rationale.tsx b/apps/web/src/components/experiment-rationale.tsx
index d910ced17..15155e2c2 100644
--- a/apps/web/src/components/experiment-rationale.tsx
+++ b/apps/web/src/components/experiment-rationale.tsx
@@ -5,7 +5,6 @@ import {
getVisibleExperiments,
} from "@/lib/eval-results"
-/** Why each benchmark experiment uses its model and effort, with links to check it. */
export function ExperimentRationale() {
const experiments = getVisibleExperiments(
getSuiteResults("benchmark")
diff --git a/apps/web/src/data/experiment-rationales.json b/apps/web/src/data/experiment-rationales.json
deleted file mode 100644
index 9dba1adec..000000000
--- a/apps/web/src/data/experiment-rationales.json
+++ /dev/null
@@ -1,161 +0,0 @@
-[
- {
- "experiment": "claude-code-opus-5.5",
- "display": {
- "agent": "claude-code",
- "modelProvider": "anthropic",
- "modelId": "claude-opus-5-5",
- "reasoningEffort": "medium"
- },
- "rationale": {
- "summary": [
- "Claude Code's ",
- {
- "text": "default model",
- "url": "https://code.claude.com/docs/en/model-config#default-model-setting"
- },
- " on most plans, at its ",
- {
- "text": "default effort",
- "url": "https://code.claude.com/docs/en/model-config#adjust-effort-level"
- },
- "."
- ],
- "asOf": "2026-10-26"
- }
- },
- {
- "experiment": "claude-code-sonnet-5",
- "display": {
- "agent": "claude-code",
- "modelProvider": "anthropic",
- "modelId": "claude-sonnet-5",
- "reasoningEffort": "high"
- },
- "rationale": {
- "summary": [
- "Anthropic's ",
- {
- "text": "latest Sonnet model for coding",
- "url": "https://www.anthropic.com/news/claude-sonnet-5"
- },
- ", at Claude Code's ",
- {
- "text": "default high effort",
- "url": "https://platform.claude.com/docs/en/models/sonnet-5/overview"
- },
- "."
- ],
- "asOf": "2026-07-02"
- }
- },
- {
- "experiment": "claude-code-sonnet-5.5",
- "display": {
- "agent": "claude-code",
- "modelProvider": "anthropic",
- "modelId": "claude-sonnet-5-5",
- "reasoningEffort": "medium"
- },
- "rationale": {
- "summary": [
- "Claude Code's ",
- {
- "text": "Sonnet model for daily coding tasks",
- "url": "https://code.claude.com/docs/en/model-config"
- },
- ", at its ",
- {
- "text": "default effort",
- "url": "https://code.claude.com/docs/en/model-config#adjust-effort-level"
- },
- "."
- ],
- "asOf": "2026-10-26"
- }
- },
- {
- "experiment": "codex-gpt-6-luna",
- "display": {
- "agent": "codex",
- "modelProvider": "openai",
- "modelId": "gpt-6-luna",
- "reasoningEffort": "medium"
- },
- "rationale": {
- "summary": [
- "A cost-effective model for everyday tasks, and the one behind the ",
- {
- "text": "Supabase dashboard assistant",
- "url": "https://github.com/supabase/supabase/blob/d98c277790766d2ceb905b675eb0a718f25e6345/apps/studio/lib/ai/model.utils.ts#L92"
- },
- "."
- ],
- "asOf": "2026-10-26"
- }
- },
- {
- "experiment": "codex-gpt-6.1-sol",
- "display": {
- "agent": "codex",
- "modelProvider": "openai",
- "modelId": "gpt-6.1-sol",
- "reasoningEffort": "medium"
- },
- "rationale": {
- "summary": [
- "Codex's ",
- {
- "text": "default model for ChatGPT accounts, at its default effort",
- "url": "https://learn.chatgpt.com/docs/models?surface=app#pick-a-reasoning-effort"
- },
- "."
- ],
- "asOf": "2026-11-09"
- }
- },
- {
- "experiment": "grok-4.7",
- "display": {
- "agent": "grok",
- "modelProvider": "spacexai",
- "modelId": "grok-4.7",
- "reasoningEffort": "high"
- },
- "rationale": {
- "summary": [
- "Grok Build's ",
- {
- "text": "default model",
- "url": "https://docs.x.ai/developers/grok-4-7"
- },
- ", at its ",
- {
- "text": "default effort",
- "url": "https://github.com/xai-org/grok-build/blob/2bdd1d6a6369de0e8c68132ea4539e9abd9e14a8/crates/codegen/xai-grok-shell/src/remote/client.rs#L832-L862"
- },
- "."
- ],
- "asOf": "2026-10-26"
- }
- },
- {
- "experiment": "opencode-kimi-k3",
- "display": {
- "agent": "opencode",
- "modelProvider": "moonshotai",
- "modelId": "moonshotai/kimi-k3"
- },
- "rationale": {
- "summary": [
- "Included as an ",
- {
- "text": "open-weight option",
- "url": "https://opencode.ai/data/moonshotai/kimi-k3"
- },
- " in OpenCode."
- ],
- "asOf": "2026-10-26"
- }
- }
-]
diff --git a/apps/web/src/data/publication.ts b/apps/web/src/data/publication.ts
new file mode 100644
index 000000000..7a93bbbb1
--- /dev/null
+++ b/apps/web/src/data/publication.ts
@@ -0,0 +1,130 @@
+import { z } from "zod"
+import { experimentRationaleSchema } from "@supabase-evals/core/eval-metadata"
+
+const publicationSchema = z.array(
+ z.object({
+ experiment: z.string(),
+ withSkills: z.uuid().optional(),
+ withoutSkills: z.uuid().optional(),
+ rationale: experimentRationaleSchema,
+ })
+)
+
+type Publication = z.infer[number]
+
+// Review the rationale whenever either pinned experiment ID changes.
+const entries = [
+ {
+ experiment: "claude-code-opus-5.5",
+ withSkills: "c8e0f865-ca59-4703-a19d-507a234e100a",
+ withoutSkills: "e0fd16ad-e117-4165-8e30-9346ebe7e835",
+ rationale: {
+ summary: [
+ "Claude Code's ",
+ {
+ text: "default model",
+ url: "https://code.claude.com/docs/en/model-config#default-model-setting",
+ },
+ " on most plans, at its ",
+ {
+ text: "default effort",
+ url: "https://code.claude.com/docs/en/model-config#adjust-effort-level",
+ },
+ ".",
+ ],
+ asOf: "2026-10-26",
+ },
+ },
+ {
+ experiment: "claude-code-sonnet-5.5",
+ withSkills: "ede84738-9f79-428f-bdfe-9d8adb2a6489",
+ withoutSkills: "15a067ef-f04e-44f5-9f94-7b8c0b0e10f1",
+ rationale: {
+ summary: [
+ "Claude Code's ",
+ {
+ text: "Sonnet model for daily coding tasks",
+ url: "https://code.claude.com/docs/en/model-config",
+ },
+ ", at its ",
+ {
+ text: "default effort",
+ url: "https://code.claude.com/docs/en/model-config#adjust-effort-level",
+ },
+ ".",
+ ],
+ asOf: "2026-10-26",
+ },
+ },
+ {
+ experiment: "codex-gpt-6-luna",
+ withSkills: "b4a9c5ce-60fc-4604-bc99-595269b7fd7d",
+ withoutSkills: "123ecf1b-1004-458c-98cf-b17450b65c92",
+ rationale: {
+ summary: [
+ "A cost-effective model for everyday tasks, and the one behind the ",
+ {
+ text: "Supabase dashboard assistant",
+ url: "https://github.com/supabase/supabase/blob/d98c277790766d2ceb905b675eb0a718f25e6345/apps/studio/lib/ai/model.utils.ts#L92",
+ },
+ ".",
+ ],
+ asOf: "2026-10-26",
+ },
+ },
+ {
+ experiment: "codex-gpt-6.1-sol",
+ withSkills: "d2e33ddc-64f1-4392-892c-d4db9f3d0146",
+ withoutSkills: "f2a8dad6-2d8b-4088-8f5f-165d00a24058",
+ rationale: {
+ summary: [
+ "Codex's ",
+ {
+ text: "default model for ChatGPT accounts, at its default effort",
+ url: "https://learn.chatgpt.com/docs/models?surface=app#pick-a-reasoning-effort",
+ },
+ ".",
+ ],
+ asOf: "2026-11-09",
+ },
+ },
+ {
+ experiment: "grok-4.7",
+ withSkills: "e986dead-a1d3-4523-b47d-33a82ff1348b",
+ withoutSkills: "545924a6-a02c-482c-9ef7-b4dfc1a993c7",
+ rationale: {
+ summary: [
+ "Grok Build's ",
+ {
+ text: "default model",
+ url: "https://docs.x.ai/developers/grok-4-7",
+ },
+ ", at its ",
+ {
+ text: "default effort",
+ url: "https://github.com/xai-org/grok-build/blob/2bdd1d6a6369de0e8c68132ea4539e9abd9e14a8/crates/codegen/xai-grok-shell/src/remote/client.rs#L832-L862",
+ },
+ ".",
+ ],
+ asOf: "2026-10-26",
+ },
+ },
+ {
+ experiment: "opencode-kimi-k3",
+ withSkills: "66f84377-4b0d-4be2-8f41-8e0756cbc40a",
+ withoutSkills: "63331944-8a8c-4396-8add-d6fbe88f80d3",
+ rationale: {
+ summary: [
+ "Included as an ",
+ {
+ text: "open-weight option",
+ url: "https://opencode.ai/data/moonshotai/kimi-k3",
+ },
+ " in OpenCode.",
+ ],
+ asOf: "2026-10-26",
+ },
+ },
+] satisfies Publication[]
+
+export const publication = publicationSchema.parse(entries)
diff --git a/apps/web/src/lib/eval-results.test.ts b/apps/web/src/lib/eval-results.test.ts
index 5196e12ce..bf3e851d0 100644
--- a/apps/web/src/lib/eval-results.test.ts
+++ b/apps/web/src/lib/eval-results.test.ts
@@ -1,10 +1,6 @@
-import { createElement } from "react"
-import { renderToStaticMarkup } from "react-dom/server"
-import { ExperimentRationale } from "@/components/experiment-rationale"
import { describe, expect, it } from "vitest"
import { makeResult } from "@/lib/eval-results.fixture"
-import { missingBenchmarkRationales } from "@/lib/experiment-rationales"
import {
EXPERIMENT_SUITES,
getSuiteResults,
@@ -20,6 +16,16 @@ import {
sortedResults,
} from "@/lib/eval-results"
+it("reads the rationale carried by the published result", () => {
+ const rationale = { summary: ["Published choice."], asOf: "2026-01-01" }
+ const result = makeResult({
+ experiment: "pinned",
+ experimentRationale: rationale,
+ })
+ expect(getExperimentRationale("pinned", [result])).toEqual(rationale)
+ expect(getExperimentRationale("other", [result])).toBeUndefined()
+})
+
describe("sortResults", () => {
const order = (results: ReturnType[]) =>
[...results].sort(sortResults).map((result) => result.eval)
@@ -223,89 +229,3 @@ describe("the exported results", () => {
expect(inconsistent).toEqual([])
})
})
-
-describe("getExperimentRationale", () => {
- const display = {
- agent: "opencode",
- modelProvider: "moonshotai",
- modelId: "moonshotai/kimi-k3",
- } as const
- const match = makeResult({
- experiment: "opencode-kimi-k3",
- experimentDisplay: display,
- })
-
- it("shows current rationale for matching results", () => {
- expect(
- getExperimentRationale(match.experiment, [match])?.summary
- ).toContainEqual(expect.objectContaining({ text: "open-weight option" }))
- })
-
- it("hides rationale for mixed or missing metadata", () => {
- const other = makeResult({
- experiment: match.experiment,
- experimentDisplay: { ...display, modelId: "other" },
- })
- const missing = makeResult({ experiment: match.experiment })
- expect(
- getExperimentRationale(match.experiment, [match, other])
- ).toBeUndefined()
- expect(
- getExperimentRationale(match.experiment, [match, missing])
- ).toBeUndefined()
- expect(
- getExperimentRationale("removed-experiment", [match])
- ).toBeUndefined()
- })
-})
-
-describe("benchmark rationale coverage", () => {
- const kimi = {
- agent: "opencode",
- modelProvider: "moonshotai",
- modelId: "moonshotai/kimi-k3",
- } as const
-
- it("requires an entry matching each current benchmark model and effort", () => {
- expect(
- missingBenchmarkRationales([
- ["benchmark/opencode-kimi-k3", { display: kimi }],
- ["benchmark/opencode-kimi-k3-no-skills", { display: kimi }],
- ["benchmark/new-experiment", { display: kimi }],
- [
- "benchmark/opencode-kimi-k3",
- {
- display: { ...kimi, reasoningEffort: "high" },
- },
- ],
- ["cli/other", { display: kimi }],
- ])
- ).toEqual(["benchmark/new-experiment", "benchmark/opencode-kimi-k3"])
- })
-
- it("shows the historical Sonnet rationale when its results are present", () => {
- const old = makeResult({
- experiment: "claude-code-sonnet-5",
- experimentDisplay: {
- agent: "claude-code",
- modelProvider: "anthropic",
- modelId: "claude-sonnet-5",
- reasoningEffort: "high",
- },
- })
- expect(getExperimentRationale(old.experiment, [old])?.asOf).toBe(
- "2026-07-02"
- )
- })
-})
-
-describe("inline rationale links", () => {
- it("links the claim inside the sentence", () => {
- const html = renderToStaticMarkup(createElement(ExperimentRationale))
- expect(html).toContain(
- 'href="https://code.claude.com/docs/en/model-config#default-model-setting"'
- )
- expect(html).toContain(">default model on most plans, at its ")
- expect(html).toContain(">default effort.")
- })
-})
diff --git a/apps/web/src/lib/eval-results.ts b/apps/web/src/lib/eval-results.ts
index 65fec25b5..6b7d91318 100644
--- a/apps/web/src/lib/eval-results.ts
+++ b/apps/web/src/lib/eval-results.ts
@@ -4,10 +4,6 @@ import {
type EvalResult,
} from "@supabase-evals/core/eval-metadata"
import rawResults from "@/data/eval-results.json"
-import {
- experimentRationales,
- sameExperimentDisplay,
-} from "@/lib/experiment-rationales"
/**
* The exported eval results and the queries the site runs over them. Everything
@@ -134,20 +130,8 @@ export function getExperimentRationale(
experiment: string,
sourceResults = sortedResults
) {
- const results = sourceResults.filter(
- (result) => result.experiment === experiment
- )
- if (!results.length) return undefined
-
- return experimentRationales.find(
- (entry) =>
- entry.experiment === experiment.replace(/-no-skills$/, "") &&
- results.every(
- ({ experimentDisplay }) =>
- experimentDisplay &&
- sameExperimentDisplay(experimentDisplay, entry.display)
- )
- )?.rationale
+ return sourceResults.find((result) => result.experiment === experiment)
+ ?.experimentRationale
}
export function getExperimentResults(
diff --git a/apps/web/src/lib/experiment-rationales.ts b/apps/web/src/lib/experiment-rationales.ts
deleted file mode 100644
index cef155ea0..000000000
--- a/apps/web/src/lib/experiment-rationales.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import { z } from "zod"
-import {
- experimentDisplayMetadataSchema,
- type ExperimentDisplayMetadata,
-} from "@supabase-evals/core/eval-metadata"
-import rawRationales from "../data/experiment-rationales.json" with { type: "json" }
-
-export const experimentRationales = z
- .array(
- z.object({
- experiment: z.string(),
- display: experimentDisplayMetadataSchema,
- rationale: z.object({
- summary: z
- .array(
- z.union([
- z.string().min(1),
- z.object({ text: z.string().min(1), url: z.url() }),
- ])
- )
- .min(1),
- asOf: z.iso.date(),
- }),
- })
- )
- .parse(rawRationales)
-
-export function sameExperimentDisplay(
- a: ExperimentDisplayMetadata,
- b: ExperimentDisplayMetadata
-) {
- return (
- a.agent === b.agent &&
- a.modelProvider === b.modelProvider &&
- a.modelId === b.modelId &&
- a.reasoningEffort === b.reasoningEffort
- )
-}
-
-export function missingBenchmarkRationales(
- experiments: Iterable<[string, { display: ExperimentDisplayMetadata }]>
-) {
- return Array.from(experiments)
- .filter(([name]) => name.startsWith("benchmark/"))
- .filter(
- ([name, config]) =>
- !experimentRationales.some(
- (entry) =>
- entry.experiment ===
- name.slice("benchmark/".length).replace(/-no-skills$/, "") &&
- sameExperimentDisplay(entry.display, config.display)
- )
- )
- .map(([name]) => name)
-}
diff --git a/packages/core/src/eval-metadata.ts b/packages/core/src/eval-metadata.ts
index 0762689f1..1d42c58d9 100644
--- a/packages/core/src/eval-metadata.ts
+++ b/packages/core/src/eval-metadata.ts
@@ -457,9 +457,22 @@ const evalResultShape = {
// Raw result files may carry extra fields we don't model; tolerate them.
export const rawEvalResultSchema = z.looseObject(evalResultShape);
+export const experimentRationaleSchema = z.object({
+ summary: z
+ .array(
+ z.union([
+ z.string().min(1),
+ z.object({ text: z.string().min(1), url: z.url() }),
+ ])
+ )
+ .min(1),
+ asOf: z.iso.date(),
+});
+
// Web-facing result; a clean strict object so its inferred type stays usable.
export const evalResultSchema = z.object({
...evalResultShape,
+ experimentRationale: experimentRationaleSchema.optional(),
passed: z.boolean(),
prompt: z.string().optional(),
promptSourcePath: z.string().optional(),
From 2809237228066287f27e35019cd3c3b398fb1679 Mon Sep 17 00:00:00 2001
From: Matt Rossman <22670878+mattrossman@users.noreply.github.com>
Date: Mon, 9 Nov 2026 16:22:14 -0500
Subject: [PATCH 15/18] feat: show experiment upload dates
---
apps/web/scripts/fetch-braintrust.test.ts | 5 +++--
apps/web/scripts/fetch-braintrust.ts | 7 ++++++-
.../src/components/experiment-rationale.test.tsx | 8 +++++++-
apps/web/src/components/experiment-rationale.tsx | 16 ++++++----------
apps/web/src/data/publication.ts | 6 ------
apps/web/src/lib/eval-results.test.ts | 2 +-
packages/core/src/eval-metadata.ts | 2 +-
7 files changed, 24 insertions(+), 22 deletions(-)
diff --git a/apps/web/scripts/fetch-braintrust.test.ts b/apps/web/scripts/fetch-braintrust.test.ts
index 9244b7d86..b88521a18 100644
--- a/apps/web/scripts/fetch-braintrust.test.ts
+++ b/apps/web/scripts/fetch-braintrust.test.ts
@@ -12,6 +12,7 @@ import {
const id = "63331944-8a8c-4396-8add-d6fbe88f80d3"
const experiment: Parameters[0][number] = {
id,
+ created: "2026-10-28T18:53:01.928Z",
metadata: {
experiment: "example",
expected_runs: 1,
@@ -64,7 +65,6 @@ const make = (roots = [root], children = [score, task], usages = [usage]) =>
test("carries the pinned experiment's rationale into published results", () => {
const rationale = {
summary: ["A ", { text: "model", url: "https://example.com" }, "."],
- asOf: "2026-01-01",
}
const withoutSkills = "d2e33ddc-64f1-4392-892c-d4db9f3d0146"
const entries = [
@@ -83,11 +83,12 @@ test("carries the pinned experiment's rationale into published results", () => {
["example-eval"]
)
assert.deepEqual(result.experimentRationale, rationale)
+ assert.equal(result.experimentCreatedAt, experiment.created)
assert.throws(() => attachRationales([experiment], []), /Missing rationale/)
})
test("requires both variants outside partial mode and rejects duplicate names", () => {
- const rationale = { summary: ["A model."], asOf: "2026-01-01" }
+ const rationale = { summary: ["A model."] }
const entry = { experiment: "example", withSkills: id, rationale }
assert.throws(
() => publicationPins([entry], false),
diff --git a/apps/web/scripts/fetch-braintrust.ts b/apps/web/scripts/fetch-braintrust.ts
index 002afd229..1f046d8df 100644
--- a/apps/web/scripts/fetch-braintrust.ts
+++ b/apps/web/scripts/fetch-braintrust.ts
@@ -20,7 +20,11 @@ const metadataSchema = z
expected_runs: z.number().int().positive().optional(),
})
.passthrough()
-const experimentSchema = z.object({ id: uuid, metadata: metadataSchema })
+const experimentSchema = z.object({
+ id: uuid,
+ created: z.iso.datetime(),
+ metadata: metadataSchema,
+})
const rootSchema = z.object({
id: z.string(),
experiment_id: uuid,
@@ -179,6 +183,7 @@ export function assemble(
const parsed = evalResultSchema.safeParse({
experiment: em.experiment,
experimentRationale: exp.rationale,
+ experimentCreatedAt: exp.created,
experimentDisplay: {
agent: em.agent,
modelProvider: em.model_provider,
diff --git a/apps/web/src/components/experiment-rationale.test.tsx b/apps/web/src/components/experiment-rationale.test.tsx
index 144b591ea..627f9341f 100644
--- a/apps/web/src/components/experiment-rationale.test.tsx
+++ b/apps/web/src/components/experiment-rationale.test.tsx
@@ -5,13 +5,15 @@ vi.mock("@/lib/eval-results", () => ({
getSuiteResults: () => [],
getVisibleExperiments: () => ["test-experiment"],
getExperimentDisplay: () => undefined,
+ getExperimentResults: () => [
+ { experimentCreatedAt: "2026-10-28T18:53:01.928Z" },
+ ],
getExperimentRationale: () => ({
summary: [
"See the ",
{ text: "model", url: "https://example.com/model" },
".",
],
- asOf: "2026-01-01",
}),
}))
@@ -22,5 +24,9 @@ describe("inline rationale links", () => {
const html = renderToStaticMarkup( )
expect(html).toContain('See the model .")
+ expect(html).toContain("Experiment upload dates")
+ expect(html).toContain(
+ 'dateTime="2026-10-28T18:53:01.928Z">2026-10-28'
+ )
})
})
diff --git a/apps/web/src/components/experiment-rationale.tsx b/apps/web/src/components/experiment-rationale.tsx
index 15155e2c2..3cb467c06 100644
--- a/apps/web/src/components/experiment-rationale.tsx
+++ b/apps/web/src/components/experiment-rationale.tsx
@@ -1,6 +1,7 @@
import { ExperimentLabel } from "@/components/results/experiment-label"
import {
getExperimentRationale,
+ getExperimentResults,
getSuiteResults,
getVisibleExperiments,
} from "@/lib/eval-results"
@@ -56,26 +57,21 @@ export function ExperimentRationale() {
- Source review dates
+ Experiment upload dates
-
- When the sources supporting each model and effort choice were last
- reviewed.
-
-
- Source review dates by experiment
-
+ Experiment upload dates
{experiments.map((experiment) => {
- const rationale = getExperimentRationale(experiment)
+ const createdAt =
+ getExperimentResults(experiment)[0]?.experimentCreatedAt
return (
- {rationale?.asOf}
+ {createdAt?.slice(0, 10)}
)
diff --git a/apps/web/src/data/publication.ts b/apps/web/src/data/publication.ts
index 7a93bbbb1..12ee3ae20 100644
--- a/apps/web/src/data/publication.ts
+++ b/apps/web/src/data/publication.ts
@@ -32,7 +32,6 @@ const entries = [
},
".",
],
- asOf: "2026-10-26",
},
},
{
@@ -53,7 +52,6 @@ const entries = [
},
".",
],
- asOf: "2026-10-26",
},
},
{
@@ -69,7 +67,6 @@ const entries = [
},
".",
],
- asOf: "2026-10-26",
},
},
{
@@ -85,7 +82,6 @@ const entries = [
},
".",
],
- asOf: "2026-11-09",
},
},
{
@@ -106,7 +102,6 @@ const entries = [
},
".",
],
- asOf: "2026-10-26",
},
},
{
@@ -122,7 +117,6 @@ const entries = [
},
" in OpenCode.",
],
- asOf: "2026-10-26",
},
},
] satisfies Publication[]
diff --git a/apps/web/src/lib/eval-results.test.ts b/apps/web/src/lib/eval-results.test.ts
index bf3e851d0..5cf41f43e 100644
--- a/apps/web/src/lib/eval-results.test.ts
+++ b/apps/web/src/lib/eval-results.test.ts
@@ -17,7 +17,7 @@ import {
} from "@/lib/eval-results"
it("reads the rationale carried by the published result", () => {
- const rationale = { summary: ["Published choice."], asOf: "2026-01-01" }
+ const rationale = { summary: ["Published choice."] }
const result = makeResult({
experiment: "pinned",
experimentRationale: rationale,
diff --git a/packages/core/src/eval-metadata.ts b/packages/core/src/eval-metadata.ts
index 1d42c58d9..e3b119341 100644
--- a/packages/core/src/eval-metadata.ts
+++ b/packages/core/src/eval-metadata.ts
@@ -466,13 +466,13 @@ export const experimentRationaleSchema = z.object({
])
)
.min(1),
- asOf: z.iso.date(),
});
// Web-facing result; a clean strict object so its inferred type stays usable.
export const evalResultSchema = z.object({
...evalResultShape,
experimentRationale: experimentRationaleSchema.optional(),
+ experimentCreatedAt: z.iso.datetime().optional(),
passed: z.boolean(),
prompt: z.string().optional(),
promptSourcePath: z.string().optional(),
From bf27a7634cf4c14c12cb3e768100d41f3df3e190 Mon Sep 17 00:00:00 2001
From: Matt Rossman <22670878+mattrossman@users.noreply.github.com>
Date: Mon, 9 Nov 2026 16:30:50 -0500
Subject: [PATCH 16/18] style: trim experiment section introduction
---
apps/web/src/components/experiment-rationale.tsx | 3 ---
1 file changed, 3 deletions(-)
diff --git a/apps/web/src/components/experiment-rationale.tsx b/apps/web/src/components/experiment-rationale.tsx
index 3cb467c06..a21f0d87b 100644
--- a/apps/web/src/components/experiment-rationale.tsx
+++ b/apps/web/src/components/experiment-rationale.tsx
@@ -23,9 +23,6 @@ export function ExperimentRationale() {
>
Why these experiments
-
- Why we chose the model and effort for each benchmark experiment.
-
{experiments.map((experiment) => {
const rationale = getExperimentRationale(experiment)
From 410bc4edcff7e1e5a97e96039e88c26c74bfbd6b Mon Sep 17 00:00:00 2001
From: Matt Rossman <22670878+mattrossman@users.noreply.github.com>
Date: Mon, 9 Nov 2026 17:00:43 -0500
Subject: [PATCH 17/18] fix: cite Codex model recommendation
---
apps/web/src/data/publication.ts | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/apps/web/src/data/publication.ts b/apps/web/src/data/publication.ts
index 12ee3ae20..d79cd0c98 100644
--- a/apps/web/src/data/publication.ts
+++ b/apps/web/src/data/publication.ts
@@ -75,12 +75,11 @@ const entries = [
withoutSkills: "f2a8dad6-2d8b-4088-8f5f-165d00a24058",
rationale: {
summary: [
- "Codex's ",
{
- text: "default model for ChatGPT accounts, at its default effort",
- url: "https://learn.chatgpt.com/docs/models?surface=app#pick-a-reasoning-effort",
+ text: "OpenAI's recommended Codex model for paid ChatGPT plans",
+ url: "https://learn.chatgpt.com/docs/models?surface=cli#gpt-55-retirement",
},
- ".",
+ ", run at medium effort.",
],
},
},
From 2d6b8846f4b7af9d8dab2ceaaf7afa5b1115c9b9 Mon Sep 17 00:00:00 2001
From: Matt Rossman <22670878+mattrossman@users.noreply.github.com>
Date: Wed, 11 Nov 2026 13:38:38 -0500
Subject: [PATCH 18/18] fix: keep experiment upload dates visible
---
apps/web/src/components/experiment-rationale.tsx | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/apps/web/src/components/experiment-rationale.tsx b/apps/web/src/components/experiment-rationale.tsx
index a21f0d87b..e622c2d92 100644
--- a/apps/web/src/components/experiment-rationale.tsx
+++ b/apps/web/src/components/experiment-rationale.tsx
@@ -52,11 +52,11 @@ export function ExperimentRationale() {
)
})}
-
-
+
+
Experiment upload dates
-
-
+
+
Experiment upload dates
{experiments.map((experiment) => {
@@ -75,7 +75,7 @@ export function ExperimentRationale() {
})}
-
+
)
}