Skip to content

fix(sandbox): install skills natively per harness (AI-1034) - #240

Merged
mattrossman merged 7 commits into
mainfrom
sean/ai-1034-native-skills-install
Sep 11, 2026
Merged

mattrossman merged 7 commits into
mainfrom
sean/ai-1034-native-skills-install

Conversation

@seanoliver

@seanoliver seanoliver commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

What's inside

Bottom of a 2-PR stack under #241, which removes the system prompt we write for the CLI agents.

Claude Tag wrote the first pass at this (previously #180, now closed).

Problem

Installing a skill means copying its folder somewhere an agent will look, and each agent reads a different directory.

  • skills add takes a flag naming which agents to install for, but we weren't not passing it.
  • Without that flag, the CLI tries to detect an installed agent, and when it cannot, it installs for all 71 it knows about.
  • So every sandbox got ~52 folders it had no use for: .aider-desk, .factory, .kilocode, .windsurf, .zencoder, and non-dotted data/ and skills/ among them. Each holds a copy of the same two Supabase skills.
  • It also didn't install Codex skills into the native .agents/skills folder, which required us to include an addendum to the Codex prompt to get it to look in the Claude skills folder (.claude/skills).

Changes

Install

Name the three agents we run in this repo:

skills add '<dir>' --agent claude-code codex opencode --skill '*' --copy --yes

That writes two directories, .claude/skills and .agents/skills, instead of 52.

--copy is required so that .claude/skills gets created in an empty eval workspace. Without it only .agents/skills shows up.

After installing, installSkills confirms every skill is actually present in both directories (.claude/skills and .agents/skills). It used to check .claude/skills only.

Plumbing for the next PR

The prompt builders now take the agent as an argument and ignore it. This is used by #241 to skip the prompt text for the CLI agents.

How to review

Confirm the new install works:

pnpm --filter @supabase-evals/sandbox test:docker

If interested, run both install strategies side by side in a temp folder to see the difference:

mkdir -p /tmp/probe/stage/skills/demo /tmp/probe/old /tmp/probe/new
printf -- '---\nname: demo\ndescription: Demo.\n---\n\nBody.\n' > /tmp/probe/stage/skills/demo/SKILL.md

# old, no --agent
cd /tmp/probe/old
env -u CLAUDECODE -u CLAUDE_CODE_ENTRYPOINT npx -y skills@1.5.11 add /tmp/probe/stage --skill '*' --copy --yes
ls -A

# new, three agents named
cd /tmp/probe/new
env -u CLAUDECODE -u CLAUDE_CODE_ENTRYPOINT npx -y skills@1.5.11 add /tmp/probe/stage --agent claude-code codex opencode --skill '*' --copy --yes
ls -A

You should see 53 entries (old) vs. 3 (new).

Follow up tasks

  • Remove skills-lock.json from the workspace after installing. The CLI drops it in the root and it ends up in the exported workspace, same as before this change.
  • Assert the full set of workspace directories in the docker test. It currently samples five of the ~52 the old fallback created, so it catches that fallback firing but does not prove nothing else was written.
  • Add a check that each agent actually surfaces the skills it finds. Both this PR and fix(framework): remove the harness system prompt for CLI agents (AI-1034) #241 assume it, and nothing tests it.

Ref AI-1034

Skills were installed with a flagless `skills add`, which — finding no
agent CLI installed yet — falls back to every one of the 71 agents the CLI
knows. That scattered ~53 stray roots across the workspace (`.adal`,
`.factory`, and non-dotted `data/` and `skills/` among them), and the
workspace is exported into run artifacts and scored. It is also
order-dependent: had an agent CLI been installed first, the fallback would
have quietly stopped producing `.claude/skills` altogether.

`skills add` now names the three CLI harnesses explicitly, which installs
into exactly the two project scopes they discover natively:

- `.claude/skills/` — Claude Code
- `.agents/skills/` — Codex and OpenCode

This is the actual fix for Codex, which does not read `.claude/skills` at
all and therefore saw no skills in any eval. All three are installed
unconditionally: the ids collapse to two directories, an unused copy costs
a few kilobytes, and no agent id has to be threaded through
`createAgentEnvironment` for correctness. Argument order matters —
`--agent` is variadic, so the source directory must precede it and
`--skill` terminates the list. `--copy` stays: symlink mode skips agents
whose top-level directory does not already exist.

The post-install check now verifies every agent scope, so a skill missing
from one of them fails loudly instead of leaving that harness silently
skill-less.

The session's harness id is threaded through to the two prompt-addendum
builders (`buildSkillsPrompt`, `buildToolSurfaceAddendum`, the latter
lifted out of `startSession` so it is testable), but neither branches on
it yet: every harness still gets the same injected text as before. What
each one should actually be told is a separate change.

Refs AI-1034, #164
Review fixes on the explicit-agent install:

- The no-`--agent` fallback does reach both `.claude/skills` and
  `.agents/skills`, so it was never the reason skills failed to arrive.
  It is a pollution and determinism problem: ~52 stray roots in the
  scored workspace, and detection that depends on the environment. The
  order-dependence the comment claimed does not reproduce on 1.5.11.
- `LocalStackSessionArgs.agent` said the addendum builders tailor their
  text to it. They take it and ignore it here.
- Quote the staging dir in the install command.
- The docker test checks a sample of the fallback's stray roots, not the
  whole set. Say so, and include the non-dotted ones.
- README described the injected-listing path as though it were the
  native one.
The README claimed the harness does not describe installed skills. It
still does, for every harness, and this change's own unit test pins that.
@vercel

vercel Bot commented Aug 25, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
evals Ready Ready Preview Sep 11, 2026 1:51pm UTC

Request Review

Comment thread packages/sandbox/src/skills.ts Outdated
…kills-install

# Conflicts:
#	apps/framework/harness/run-eval.ts
`SKILLS_INSTALL_AGENTS` and `SKILLS_INSTALL_DIRS` were two hand-maintained
lists. Adding a harness meant remembering both; forgetting one left that
harness running evals with no skills installed, silently.

Both are now derived from `SKILLS_PATH_BY_AGENT`, a
`Record<AgentHarnessId, string | null>`. The record is exhaustive by type, so
adding an id to `agentHarnessIdSchema` fails to compile until its project scope
is declared. Verified: adding a fifth id errors with TS2741 naming the missing
harness.

`SKILLS_INSTALL_DIR` now reads from the same map rather than repeating
`.claude/skills`, so the prompt listing cannot drift from the install either.

Refs AI-1034
@seanoliver
seanoliver requested a review from mattrossman August 31, 2026 22:29
Comment thread README.md

@mattrossman mattrossman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Sanity checked pnpm eval -- --eval build-cli-002-declarative-schema --experiment codex-gpt-5.4-mini

In that run it was reading Claude's skill files instead but it sounds like that will be fixed by subsequent PR #241 in the stack by trimming the buildSkillsPrompt

@mattrossman
mattrossman merged commit 03467fb into main Sep 11, 2026
7 checks passed
mattrossman added a commit that referenced this pull request Sep 11, 2026
) (#241)

This is stacked on #240. Review that first; this diff is against it. No
results are touched.

## Problem

We wrote our own system prompt into every eval run, for all four agents:

> You are an agent solving a Supabase eval task in a Linux workspace.
Use the provided tools to inspect and modify the workspace and run
commands. When you are done, end your turn with a short summary of what
you did.

- Claude Code, Codex and OpenCode ship their own system prompt. Ours was
layered on top, and Codex and OpenCode have no system-prompt flag, so
for them it went onto the user prompt.
- It described `bash` and `files_read`, which only the in-process
`ai-sdk` agent has.
- "End your turn with a short summary" shapes `agentReport`, which the
LLM judge reads. That is a scoring change.
- It says Supabase, and "project". An agent that would otherwise pick a
database has been handed the answer (#164).

## Changes

| Agent | Before | After |
|---|---|---|
| `ai-sdk` | framing + tool list + skills list | one neutral line + tool
list + skills list |
| `claude-code` | ours appended to its own | nothing |
| `codex` | ours prepended to the user prompt | nothing |
| `opencode` | ours prepended to the user prompt | nothing |

- CLI agents take no system prompt at all. `createCliAgent` no longer
stages a system-prompt file or passes a path to the runner;
`--append-system-prompt-file` and the Codex/OpenCode prepend are gone. A
non-empty `systemPrompt` for a CLI agent throws.
- The `ai-sdk` base prompt is now `You are an agent. Use the provided
tools to complete the task.` It is the same in both modes. No Supabase,
no project, no stopping instructions.
- `buildToolSurfaceAddendum` and `buildSkillsPrompt` return `''` for
every CLI agent. The bare sandbox no longer takes an `agent` or returns
a `promptAddendum`.
- Prompt assembly moves out of `run-eval.ts` into `system-prompt.ts` so
it can be unit tested.
- `run-eval.ts` only starts a run when it is the entry point. It used to
call `main()` on import, which the harness unit tests now trigger.
- Each run records `systemPrompt` in its raw `result.json`.
`export-results` drops it, so nothing reaches the site.

Since the first review: the system-prompt-file plumbing for CLI agents
is deleted rather than made optional, and the ai-sdk base prompt is
rewritten for #164. The branch is re-applied on the current #240 (the
retry loop is gone there), so the commits are new. #240 now has `main`
merged in, which brings the usage and duration fields from #270.

## Testing

- `pnpm --filter @supabase-evals/core test`, `pnpm --filter
@supabase-evals/sandbox test`, `pnpm --filter @supabase-evals/framework
test`, `pnpm --filter @supabase-evals/web test`: all pass locally.
- `typecheck` for framework, sandbox and web passes. `biome check .` is
clean.
- Unverified against real CLIs since the deletion. The earlier sandbox
runs (Claude Code
[32785480269](https://github.com/supabase/evals/actions/runs/32785480269),
Codex
[32880822811](https://github.com/supabase/evals/actions/runs/32880822811),
OpenCode
[32880839623](https://github.com/supabase/evals/actions/runs/32880839623))
recorded an empty prompt and still loaded the Supabase skills, and the
path they exercised (no flag, task alone) is the one that remains.

## Score movement

Full benchmark on both branches before this revision, artifact only,
nothing published. Both used `runs=2`.

| | #240, no prompt change | #241, prompt removed | |
|---|---|---|---|
| all pairs | 86.0% | 84.7% | 21 flips, 12 down 9 up |
| `claude-code` | 92.3% | 92.3% | no change |
| `codex` | 81.5% | 75.0% | down 6.5 points |
| `opencode` | 82.6% | 89.1% | up 6.5 points |

This looks like run-to-run variability. Once #229 lands and scores
average across runs, the numbers should settle.

## Follow-ups

- Re-run the benchmark comparison once #229 lands and the numbers can
carry attribution.
- Drop the "end your turn with a short summary" line from
`evals/resolve-performance-001-slow-query-cpu-spike/PROMPT.md:15`, the
last one left. It sits in the task text so it hits every agent equally,
and removing it invalidates that eval's recorded results, so it should
be a separate change.

Ref AI-1034

---------

Co-authored-by: Matt Rossman <22670878+mattrossman@users.noreply.github.com>
@mattrossman mattrossman mentioned this pull request Sep 11, 2026
NickLittman added a commit that referenced this pull request Sep 18, 2026
## What's inside

Adds Grok Build, xAI's coding agent, as a fourth CLI harness alongside
Claude Code, Codex and OpenCode.

- ~640 lines of runner + parser, the only files needing real review
(packages/core/src/agents/grok/)
- ~400 lines of parser tests, skimmable
- ~95 lines of experiments and plumbing: grok-4.6, grok-4.6-no-skills,
XAI_API_KEY through the workflow, the registry entry, the web label
- ~17k lines of results data, committed by the bot from two workflow
runs

Grok Build is xAI's coding agent. It calls the xAI API directly, so it
needs XAI_API_KEY rather than the gateway key the other harnesses share.

- GROK_HOME goes in the shell command, not the sandbox env map. docker
exec --env passes values verbatim, so a $HOME in an env value reaches
the CLI unexpanded. The CLI resolves it against the cwd, writes its
config, sessions and binary into a directory named $HOME inside the
scored workspace, and never reads the config.toml we stage. MCP servers
silently fail to connect, and the run scores 0 with an empty report that
looks like a model failure.
- Grok reads .claude/skills natively, but the skills CLI has no grok
agent id and rejects the whole install when one is named.
SKILLS_PATH_BY_AGENT gives grok null, and the claude-code entry
populates the directory it reads. Drop claude-code from that map and
grok loses its skills with nothing failing. Follow-up below.

## Results
- with skills (64/69 - 93%)
- without (58/69 - 84%)

23 benchmark evals × 3 runs on Vercel Sandboxes.

Not comparable to the other rows yet: they were last refreshed on
2026-09-09 (d462078), so they predate #278, #240 and #241, including the
system-prompt removal that #241 calls a scoring change.

## How to review

Needs XAI_API_KEY in .env. Use --concurrency 1 locally; Docker drops
containers under load and reports it as a timeout with a 0 score.

pnpm eval -- --experiment grok-4.6 --eval
resolve-dataapi-001-empty-results

## Follow up tasks

- Split "scope a harness reads" from "--agent ids the CLI accepts" in
SKILLS_PATH_BY_AGENT, so grok declares .claude/skills and a missing
install fails loudly instead of silently.
- Refresh the other five benchmark experiments. Every non-Grok row
predates #278/#240/#241, so the published table currently compares
across harness versions.
- The Grok CLI is pinned to 1.0.13; xAI shipped 12 releases in the
following 12 days. The streaming-json schema is undocumented and its
embedded docs already describe a stream it doesn't emit, so bump
deliberately and re-check the parser against a real transcript.

Ref AI-1143

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Matt Rossman <22670878+mattrossman@users.noreply.github.com>

This branch was successfully deployed

1 active deployment
Preview — 98e2c7b7 Deployed Sep 11, 2026 by vercel[bot]
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