Skip to content

feat: organize experiments by owner - #315

Merged
Rodriguespn merged 2 commits into
mainfrom
Rodriguespn/pr308-nested-experiments-proposal
Sep 22, 2026
Merged

Rodriguespn merged 2 commits into
mainfrom
Rodriguespn/pr308-nested-experiments-proposal

Conversation

@Rodriguespn

@Rodriguespn Rodriguespn commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

This change organizes experiments under per-owner folders so CODEOWNERS can scope them by path. Experiment IDs and result names remain unchanged.

AI-owned base configs live in experiments/presets.ts. The AI experiments extend those presets from experiments/ai/.

experiments/
├── presets.ts
└── ai/
    └── *.experiment.ts

Diff note: changes under experiments/ are file moves and config extraction. Runtime changes are limited to flat experiments/<team>/*.experiment.ts discovery in the runner and result exporter. CLI runtime, helper, and experiment changes are intentionally excluded.

How to review

Start with experiment-files.ts, then presets.ts and the two discovery call sites in run-eval.ts and export-results.ts.

Dry run one of the moved experiments using its unchanged ID:

pnpm eval:dry -- --eval resolve-dataapi-001-empty-results --experiment claude-code-sonnet-5

Verification

  • pnpm --filter @supabase-evals/framework typecheck
  • pnpm --filter @supabase-evals/framework test:vercel-runner (26 tests passed)
  • pnpm eval -- list returned the same 12 experiment IDs
  • Focused dry run for claude-code-sonnet-5
  • Result export using the moved experiment metadata
  • pnpm format
  • Targeted CI found and ran all 12 stable experiment IDs. Publishing hit the same workspace packaging failure on main.

Context: Slack thread

@vercel

vercel Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated
evals Ignored Ignored Preview Sep 22, 2026 11:51am UTC

Request Review

@Rodriguespn
Rodriguespn force-pushed the Rodriguespn/pr308-nested-experiments-proposal branch from 881432e to a2edffd Compare September 21, 2026 17:35
@Rodriguespn
Rodriguespn changed the base branch from columferry/cli-2398-cli-suite-and-arms to main September 21, 2026 17:35
@Rodriguespn
Rodriguespn marked this pull request as ready for review September 21, 2026 17:41
@Rodriguespn
Rodriguespn requested a review from a team as a code owner September 21, 2026 17:41
Comment thread apps/framework/lib/experiment-files.ts Outdated
Comment thread experiments/presets.ts Outdated
Comment thread experiments/ai/claude-code-sonnet-5-no-skills.experiment.ts Outdated
Comment thread .github/CODEOWNERS Outdated
@mattrossman

Copy link
Copy Markdown
Collaborator

Sanity checking w/ a regression run to verify results look normal still:
https://github.com/supabase/evals/actions/runs/35649310761

@mattrossman

mattrossman commented Sep 21, 2026 •

Copy link
Copy Markdown
Collaborator

The parts of the refresh that worked look fine, the broken parts are my fault from a bug in #303 that slipped in 🙈 will fix that separately.

(Edit: fixed the regression in #321 so you may wanna merge that in here to fully confirm regression results still look normal)

Co-authored-by: OpenAI Codex <codex@openai.com>
@Rodriguespn
Rodriguespn force-pushed the Rodriguespn/pr308-nested-experiments-proposal branch from a2edffd to 1e930b2 Compare September 22, 2026 11:51
@Rodriguespn

Copy link
Copy Markdown
Contributor Author

Re-ran the regression suite after rebasing onto #321. All 78 jobs completed and published successfully, with no packaging or discovery errors. The benchmark result was 50/78 passes, compared with 58/78 previously. The refreshed results are in #319.

Merging this 🚢

@Rodriguespn
Rodriguespn merged commit 837c1c3 into main Sep 22, 2026
10 checks passed
Coly010 added a commit that referenced this pull request Sep 22, 2026
…on (#324)

First of two PRs splitting #308's sandbox work out of CLI-team
experiment land, per the thread with @mattrossman and @Rodriguespn. This
one is independent of #315 and of the `cli` suite entirely — it only
widens an existing option.

## What changes

`localStackRuntime({ cliVersion })` already accepted an exact version.
It now also accepts a channel tag:

```ts
localStack: localStackRuntime({ cliVersion: 'beta' })
```

`'stable'` and `'beta'` resolve against npm's dist-tags for the
`supabase` package once, at session start. An eval's own `cliVersion:`
frontmatter pin still wins over the runtime option, exactly as before —
and because that check short-circuits, a pinned eval never makes the
network call at all.

The resolver itself moves in from `experiments/_lib/cli-channel.ts` (it
has been living in experiment land on the #308 branch). It does two
things beyond reading a dist-tag, both of which encode incidents rather
than caution:

- It verifies the matching `.deb` release asset actually exists, and
walks back through earlier `beta.N` candidates when the newest beta's
asset has not been published yet. This is a real npm/GitHub
draft-release skew we hit on `beta.52` in run 35274398896 — npm had the
version, the release asset did not exist, and the install failed inside
the sandbox.
- It honours `SUPABASE_CLI_STABLE_VERSION` / `SUPABASE_CLI_BETA_VERSION`
overrides, which is how a CI run pins one resolved version for all of
its jobs.

That last part is why `run-vercel-evals.ts` is in the diff: the two pins
are added to the env names forwarded into each sandbox's `.env`. Without
it, every sandbox job in a run re-resolves "latest" independently and a
run can score against two different CLI versions if a release lands
mid-run.

## Notes for review

- `cliVersion` is typed `CliChannel | (string & {})` rather than `string
| CliChannel` — the latter collapses to plain `string` and you lose
editor suggestions for the two tags that are the point of the option.
- `packages/sandbox`'s `test` script named its one test file literally,
so it would have silently skipped the new one. It is now `vitest run
test`; `docker.test.ts` already self-skips via
`describe.runIf(process.env.SANDBOX_DOCKER_TESTS)`, so it stays inert.

## What is deliberately not here

The `docker: 'no-daemon' | 'absent'` staging, `mountDockerSocket`,
`needsDocker`, and the runtime marker are all in the follow-up, stacked
on this branch. The `cli` eval/experiment suites, CODEOWNERS and
workflow wiring come after that, once #315 lands.

## Verification

`pnpm format:check`, `pnpm typecheck`, and the sandbox (89), core (162),
framework (8), vercel-runner (28) and web (83) suites pass. `pnpm
check`'s `test:framework` stage needs `AI_GATEWAY_API_KEY` for its judge
and is not run in CI; every other stage was run.
Coly010 added a commit that referenced this pull request Sep 23, 2026
Stacked on #324 — review that one first. Second of two PRs splitting
#308's sandbox work out of CLI-team experiment land.

This is @mattrossman's suggestion from the thread taken literally:
`dockerAwareLocalStackRuntime` dissolves into `localStackRuntime`, and
being able to control Docker availability becomes something any
experiment can ask for.

```ts
localStack: localStackRuntime({ cliVersion: 'beta', docker: 'absent' })
```

`docker` defaults to `'available'`, which is the existing code path. The
two new states stage a sandbox where the daemon is unreachable
(`'no-daemon'`) or the binary is gone entirely (`'absent'`).

## What staging actually involves

None of this is decorative — each step exists because a weaker version
of it let a "Docker-less" sandbox quietly still have Docker:

- The socket is **not bind-mounted at all** (`mountDockerSocket: false`
on `DockerSandbox`), rather than hidden or blocked from inside the
container, and its absence is asserted. CI's sandbox setup ends with
`chmod 666 /var/run/docker.sock`, so anything short of omitting the
mount is a no-op there.
- `docker`/`dockerd`/`docker-proxy` removal **asserts its
postcondition** instead of trusting `rm -f`, which always exits 0 — and
checks against both the root shell's PATH and the agent's own
`SANDBOX_PATH`, which differ.
- For `'no-daemon'`, `docker --version` is captured *before* removal so
the shim can keep echoing it. The CLI's managed-stack runtime probe
(supabase/cli#6563) reads that to *choose* Docker; if the shim didn't
answer, the CLI would pick a different runtime and we would be testing
the wrong thing.
- `DOCKER_HOST` points at `tcp://127.0.0.1:1`, which is never bound.
Deliberately not 2375, which Docker Desktop can legitimately expose.

There are guard errors for `projectRunning !== false` and for a hosted
link, since the harness can do neither without Docker.

### The `supabase` shim looks CLI-specific but isn't

It injects `-x <excluded services>` into `supabase start`. On the normal
path the *harness* runs `supabase start` and applies exclusions via
`buildSupabaseStartCommand`; on a Docker-less path the *agent* runs it,
so the shim is the only route by which the eval-level `includeServices`
field is honoured at all. `computeExcludedServices` already lived in
this package.

### The environment marker

Each session writes `/tmp/supabase-eval-runtime.json` recording what was
staged, exported now as `LOCAL_STACK_MARKER_PATH` /
`LocalStackEnvironmentMarker` so scorers can import it instead of
duplicating the schema. Scorers are meant to **report** it, never branch
on `docker` to decide pass/fail — that would grade an eval against its
own environment.

Two asymmetries worth knowing:

- On the Docker-less paths it is root-owned and `0444`, so the agent
cannot rewrite it to fake the environment it is being graded in, and a
write failure is **fatal** — there the marker is the evidence the
environment was staged as claimed. On the `'available'` path it goes
through the session's own non-root `exec`, so it is agent-writable and
metrics-only, and a write failure only **warns**. That path is the one
every experiment in the repo takes, and a transient `/tmp` failure
shouldn't kill a benchmark run over a marker nothing reads.
- `channel` is optional: populated only when the runtime option named a
channel *and* no per-eval `cliVersion:` pin overrode it. An
exact-version pin records `cliVersion` with no `channel`.

## Also here

`needsDocker` eval frontmatter (defaults to true), the eval-side
counterpart of this option — it is how a Docker-less experiment decides
which evals it can legitimately pick up.

## What is deliberately not here

`skipUnlessCli` / `skipUnlessDockerless` stay CLI-team-owned and land
with the `cli` suite, along with CODEOWNERS and the workflow wiring,
once #315 is in.

## Verification

`pnpm format:check`, `pnpm typecheck`, and the sandbox (89), core (162),
framework (8), vercel-runner (28) and web (83) suites pass.
`test:docker` needs a real daemon and free ports, so it was not run;
`pnpm check`'s `test:framework` stage needs `AI_GATEWAY_API_KEY` and is
not run in CI.
Coly010 added a commit that referenced this pull request Sep 23, 2026
Final chunk of the closed #308 — the piece @mattrossman asked for: the
minimal CLI suite plumbing and CODEOWNERS, standalone, with no CLI
experiment internals to review. **15 files, 260 lines**, and four of
those files are 11–13 lines each.

Everything it depended on is now on `main` (#315's
`experiments/<owner>/` layout, and #324 + #325's sandbox options), so
this is based on `main` and the diff is only its own content.

## What it adds

`cli` as an eval suite and an experiment suite, `@supabase/cli`
ownership over `/evals/cli/`, `/experiments/cli/` and
`cli-eval-results.json`, and the workflow wiring so `eval-refresh` and
`append-gh-pages-history` know the suite exists.

The substance is five environment columns: one scenario run unchanged
across forced CLI environments, so a failure isolates to *which*
environment broke. They're thin now that `experiments/presets.ts` exists
— the whole of the Docker-less column is:

```ts
export default defineExperiment({
  ...codexGpt6Luna,
  suite: ['cli'],
  // beta: the Docker-less path only exists in the managed stack, which ships in beta.
  localStack: localStackRuntime({ cliVersion: 'beta', docker: 'absent' }),
  skipEval: skipUnlessDockerless,
});
```

| column | environment | picks up |
|---|---|---|
| `codex-gpt-6-luna-cli-pinned` | repo-pinned CLI, Docker available |
every `interface: cli` eval that isn't hosted-linked |
| `…-cli-stable` | npm `latest` | same |
| `…-cli-beta` | npm `beta` | same |
| `…-cli-nodaemon` | beta, daemon unreachable | also needs `needsDocker:
false` + `projectRunning: false` |
| `…-cli-absent` | beta, no `docker` binary | also needs `needsDocker:
false` + `projectRunning: false` |

All five columns share one `skipEval: skipUnlessCli`, so they run the
same eval set and stay comparable — which is the whole point of the
suite. An earlier revision made the pinned column the shared benchmark
experiment with `'cli'` appended to its `suite`; that experiment has no
`skipEval`, so it picked up hosted-linked evals the other four skip. A
CLI-owned pinned experiment fixes that and leaves this PR touching
nothing outside CLI-owned paths.

`skipUnlessCli` / `skipUnlessDockerless` are the only things left over
from the old `experiments/_lib/`; they live in `experiments/cli/lib/`,
which discovery ignores for free since it only matches `*.experiment.ts`
directly under an owner directory.

## Verified, including the parts tests can't reach

`format:check`, `typecheck`, and the sandbox, core, framework,
`test:cli-lib`, vercel-runner and web suites all pass.

Two things unit tests can't cover, checked directly instead:

- **Discovery**, since it's filename-convention-based and fails
silently: all 16 experiments resolve and load, `experiments/cli/lib/` is
correctly not treated as an experiment, and the five columns resolve to
the intended runtimes and channels (`beta`, `beta`, `beta`, `stable`,
and none for the pinned baseline).
- **The workflow**, which only ever runs in CI, no longer resolves
channels at all — it passes through the manual `cli_stable_version` /
`cli_beta_version` dispatch inputs and otherwise lets
`run-vercel-evals.ts` derive the channels from the actual pairs.
Pre-filling both env vars would have short-circuited that narrowing.

Each run's results now record the CLI version the sandbox actually
installed, read from the session's environment marker, so a `stable` row
names its binary rather than just its channel. That needed
`cliVersionSchema` widened — it rejected every prerelease, so a beta
run's real version could not have been recorded at all.

`test:cli-lib` carries `--passWithNoTests` deliberately: `evals/cli/`
has no scorer tests until #281/#314/#316 land, and without the flag the
script's exit code depends on which of its two paths happens to be
populated.

## Note on the column names

These follow the `gpt-6-luna` rename from #328. They key
`cli-eval-results.json`, and no results exist yet, so there is nothing
to churn — but that also means the names should settle before the suite
is first run.

## Next

#281, #314, #316 and #307 get re-parented onto `main` once this lands —
they currently point at the retained branch of the closed #308. /cc
@Rodriguespn @kanadgupta
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants