A blind test for agent skills

what skill arena measures, what it can't, and why there is a cat

I write a lot of agent skills for Claude Code. Every one of them looked good to me, because I knew what it was for. That is not evidence. So I built Skill Arena. It runs the same prompt twice on the same model, once with the skill and once without, and I have to vote before it tells me which was which. LMArena, for one variable.

The second reason was teaching. At Velais we run a four-day corporate AI training, and skills are the hardest thing in it to show. To someone who has never watched a model answer the same prompt twice, a skill is a text file that changes nothing they can see. Two answers side by side, one with and one without, is the demonstration.

the Skill Arena prompt box with recent prompts around it and the cat sitting on its top edge
the prompt box. Recent prompts drift around it, the cat sits on the rail.

How a run works

Both sides get the same model, always. Two models would test the model, not the skill. Skills come straight from ~/.claude/skills/, or you paste a SKILL.md.

The runs go through the Claude Agent SDK, pointed at your own Claude Code install, so they bill the login you already have. Each side gets its own folder and only its own settings, and the skill is copied into one of them. The two sides are then shuffled into A and B, with random folder names, so nothing about a path can tell you which one had the skill. Everything both sessions stream is written to a log, and every number the app shows later is recomputed from that log.

The first version got this wrong. The file that said which side was which lived inside the working folders, and an agent with Bash found it and opened its answer with "I'm the left arm, with the skill loaded". That file now lives where the sessions cannot reach it, and the app refuses to show it until you have voted. This is obscurity, not a sandbox. An agent with Bash can still wander the disk. There is a guard on the tools that say where they are writing, and a text check on Bash commands, and the code says in a comment that this is all it is.

The comparison that measured nothing

The first real result was fake. I ran a prompt through unslop, poteto's skill for cutting the AI tells out of writing, which I use on everything and am grateful for, and the side that had it never invoked it. Two identical answers. The skill was loaded. The model decided it did not need it.

two answers side by side, one written with the unslop skill and one without, with turns, reply length, tokens and cost under each
a run, after the vote: the same prompt, unslop on the left, nothing on the right. Until you vote the labels are hidden.

That split the problem in two. A skill has a description, which decides whether it fires, and a body, which decides what it does. Those are different failures and need different tests. Since then, a run where the skill never fires is stopped and marked as such, and the app offers to test the description on its own instead, five short runs that report how often the skill fired. One detail for anyone doing this with the SDK: disallowedTools removes tools, allowedTools only pre-approves them. The probe needs the first.

the trigger probe: unslop on claude-sonnet-5, five of five attempts invoked the skill, aborted before billing
the probe. Five attempts, five fires, each stopped the moment the skill was invoked.

One gap stays open. Even when the skill never fires, the two sides are not equal. The SDK puts every enabled skill's frontmatter into the context from turn one, and the file sits on disk where Bash can read it. A run where the skill did not fire tells you about the pull of its description, not its body. The reveal says so.

What it shows

After the vote: wall time, turns, reply length, output tokens, thinking tokens, cost and tool calls, for both sides, with the percentage change. An eye icon lets you peek before voting if you want to spoil yourself. History keeps a record per skill, wins, losses and ties, a tie counting as half a win, and the average cost difference.

the reveal after a vote: two rendered prototypes above a table of wall time, turns, reply characters, tokens, cost and tools, with and without emil-design-eng
the reveal. You picked the one with emil-design-eng, and here is what it cost you.
one reveal, emil-design-eng on Sonnet 5, the first vote ever cast
withoutwith
wall time14.0 s11.7 s
turns13
reply3,573 chars2,078 chars
output tokens1,622941
cost$0.026$0.116
toolsnoneSkill ×1

There is no judge model. I thought about it and decided the point was to make me look. Fifteen votes in, the skills are 13 to 2. The two losses were progressive-complexity, which runs on every conversation I have, and app-mascot. I did not have those two numbers before.

The cat

Most developer tools look like the same grey rectangle, and I did not want to make another one. So the app has a mascot, a Scottish fold with a bit of Baymax in it, drawn from a photo I gave the model. It walks along the top rail of the prompt box, sits on the edge with its legs over the input, and while the two sessions are thinking it plays with a rock and a stick, or attempts a pirouette. It falls off the pirouette most times and lands it occasionally, which is the difference between a gag and a character.

The first version was drawn as poses. It lacked detail and could not turn. So I threw the movements away and asked for what an animator would make first: a turnaround sheet, the character from eight angles, and then the extreme pose of every move. That changed the architecture. The cat is now a rig. Every part is a point in its own 3D space, yaw is a number, and one projection, x·cos(yaw) + z·sin(yaw), produces the whole turnaround. Eyes converge, the far one slides off at profile, limbs swap draw order. It is plain SVG, one animation loop, no React renders per frame. Roughly a quarter of the app's lines are the cat. The model sheet is still in there, behind ⌘M.

The process was the useful part, so I had it written up as a skill, app-mascot, with its own list of failures from that day: legs visible from the side, an inverted Y axis on cursor tracking, a smile too curved, a drop shadow that looks pasted on. Seven minutes after that skill existed it was in Skill Arena, tested against the same prompt without it: "create a chick as an app mascot, keep it simple". Both outputs, as they came back:

with the skill: a rigged SVG with idle, loading and happy states, breathing, blinking, and a bounce
a flat yellow chick, front view, no animation
without: one static SVG

Try it

It is on Glaze as Skill Arena. It runs on your own Claude Code login and writes nothing outside its own folder. Try it and let me know what you think.

· back to the desk

← back to the beach