There are only a few ways to tell a coding agent what to do, and they’re all the same way wearing different clothes. You type a prompt, or you say it out loud. When you catch yourself typing the same prompt for the tenth time, you give it a name and save it — now it’s a skill. Some skills come with the tools; others you write yourself, for the things you want done the same way every single time.

I lean on a handful. One keeps my test coverage above ninety-five percent. One runs the whole app through a simulator, or drives a real browser, and clicks through it before I’m allowed to call anything shippable. One holds a build to its design — every colour, every margin, every state — to within a hair of the mockup.

That last one taught me the most, so let me come back to it.

A skill is not a button

Here’s the thing it took me too long to see. A skill is not a button. It does not execute. When you invoke one, nothing runs except this: a paragraph of instructions you wrote earlier gets pasted into the model’s context at the right moment. That is the whole mechanism. The skill is a preformed prompt, and the model reads it exactly the way it reads everything else in the window — as text to weigh, not an order to obey.

Which means the model can follow it. Or half-follow it. Or read it, nod, and quietly do something else. Nothing in the machinery guarantees that the words in your skill become true. You wrote a wish and handed it over. Whether it comes true is a separate question entirely.

Why the wish gets dropped

Instructions don’t get dropped because the model is lazy. They get dropped because context is finite and lossy, and your skill is competing for attention with everything else in the window — the task, the files, the last ten tool results, the model’s own reasoning. The longer the run, the more crowded it gets, and the fainter any single instruction becomes. A crowded window thinks worse; I’ve written about that pull before.

And the model satisfices. It does enough to look done, and then it stops — because looking done is its actual stopping condition. Nothing measured whether the goal was met. No one was checking. So “keep coverage above ninety-five percent” quietly becomes “wrote some tests, seems fine,” and the run ends with a green-looking summary and a number nobody verified.

The example that doesn’t forgive you

The design-parity skill is where this gets brutal, because the gap between the wish and the work is enormous and invisible.

“Match the design to ninety-five percent” sounds like one instruction. It is not. Every single difference between the build and the mockup is a feature — a real code change, and usually one that touches logic that already exists. A different empty state is a new branch. A tighter margin is a layout that now has to hold at every width. A hover you didn’t have is state you have to add. The design file looks like a picture; it is actually a list of demands, each of which is work, and some of which quietly contradict the code you already shipped.

So a skill that says “achieve parity” is the purest kind of wish. The model makes a pass, fixes the three things it happened to notice, decides it looks close, and declares victory — because nothing in the loop ever turned “parity” into a number it had to beat. It was never accountable to the goal. It was accountable to looking done.

A skill is a wish. A check is a contract. Only one of them ships.

— the thing I kept relearning

The missing half: a check and a loop

The fix isn’t a better prompt. You cannot prompt your way out of “nothing verified this.” The fix is to give the instruction the two things it’s missing: a way to be checked, and a loop that repeats until the check agrees.

An instruction becomes reliable only when “done” stops being the model’s opinion and becomes a measurement the model doesn’t control. Tests that must actually pass. A parity audit that returns a percentage. A functional run that is either green or red. The model no longer gets to say it’s finished — the check says so, and if the check disagrees, the work goes around again.

A wish versus a contract Open loop: a skill is read and the model declares itself done, with nothing verifying the goal. Closed loop: the skill carries a checkable definition of done; a check runs and the work loops until the goal is actually met. OPEN LOOP — A WISH skill / prompt model takes a pass model says “done” Nothing checks it. The skill is read, then half-done or dropped — and you find out later. CLOSED LOOP — A CONTRACT skill + a checkable “done” model takes a pass run the check tests · parity · functional done passes fails → loop back, repeat The check decides “done”, not the model. It loops until the goal is actually met.
Two ways to invoke the same skill. Left: the model decides it’s done. Right: a check decides — and the work loops until the goal is met. The verification is the real skill; the prompt is just the opening move.

In the open loop, the skill goes in, the model takes a pass, and the model decides it’s done. In the closed one, the skill carries a definition of done that a machine can evaluate; the model takes a pass; the check runs; and on failure the whole thing loops — not because the model felt like trying again, but because the goal isn’t met and the loop’s only exit is the goal being met.

It’s the same agent loop as ever — a model, in a loop, with tools — but the stopping condition has changed hands. It used to belong to the model. Now it belongs to the check.

while not done:
    attempt_the_work()        # the model's pass
    done = run_the_check()    # tests green? parity >= 95%? functional run passes?
# only now is it actually done

That run_the_check() is the real skill. The prompt was just the opening move.

How I write skills now

So I’ve stopped writing skills that say do X and started writing skills that say do X; here is exactly how we’ll know it’s done; do not stop until it is. Three parts, always: the work, a definition of done a machine can evaluate, and the instruction to loop on failure.

The coverage skill doesn’t say “keep coverage high” — it runs the coverage tool, reads the number, and is not allowed to finish below the line. The functional skill doesn’t say “test it” — it drives the simulator or the browser, and the run either passes or the work continues. The parity skill doesn’t say “match the design” — it audits the build against the file, gets a percentage, and keeps going until the percentage clears the bar or it tells me, precisely, what it couldn’t close and why.

The wish is still there at the top. It’s just no longer alone.

The reliable way

This is, in the end, the whole answer to how do I make AI do what I want, reliably? You make what you want measurable, and then you refuse to let the work stop until the measurement agrees.

A skill, by itself, is a hope you’ve written down. It becomes reliable the moment you give it a way to be checked and a reason to keep going. Hope, plus a loop, plus a number that doesn’t lie. That’s the contract. Everything before it is just a wish.