Deterministic Skills: Let Scripts Do What Scripts Do Well
A design philosophy for Claude Code skills: script every step a unit test could verify, and reserve the LLM for the judgment calls only it can make.
Short of it is: most of the skills I see people build are almost entirely prose, and that's the reason they get flaky the third time you run them.
I hit this wall building skills that run on their own, no me in the loop, no chance to answer a clarifying question. A skill that scans an inbox, transcribes a file, and emails me a digest either does that the same way every time or it doesn't get to run unattended at all. So I stopped writing skills as pure instructions and started asking one question about every step before I write anything: could a unit test verify this step's output?
If yes, it's a script. If the honest answer is "two competent people would do this differently," it stays an instruction for the model. That's the whole philosophy. I call it deterministic skill design, and once you see it you can't unsee the places you're asking a language model to do something a script could do byte-for-byte.
What "judgment" actually means here
The LLM's job in a finished skill is deciding, interpreting, and writing. Parsing a file, calling an API, checking a timestamp, applying a fixed rule, sending an email: none of that is judgment, and none of it should be prose. Writing in a voice, triaging what matters, naming something well, summarizing messy input: that's judgment, and that's the only place I want a model improvising.
The litmus test I actually use, every time: could a unit test verify this step's output? A parser, a file scan, a dedupe check, an API call, all pass that test. "Pick which of these emails matter" doesn't, because two people would draw the line differently and both would be right. When a step is a batch of consequential edits driven by judgment, there's a third pattern worth knowing: plan-validate-execute. The model writes a structured plan, a script validates it and fails loudly with specific errors, a script applies it. The model plans, the script touches the world. That pattern shows up more than you'd think once you're looking for it.
Why capable agents don't do this on their own
What surprised me: a capable agent can absolutely write you a well-scripted skill, sometimes. The same agent, the next day, on a slightly different task, writes the equivalent step as pure prose instead. It's not incompetence, it's luck, and it depends entirely on which neighboring skill it happened to skim for conventions before starting.
Every mechanical step left as prose is a place the agent can improvise differently each run. A different date format this time. A validation check that got skipped because the input looked fine. A parse that disagrees with the one it did last week on the same file, without telling anyone. None of that shows up as an error. It shows up as drift, and drift in a skill you're not watching closely is the kind of bug that costs you a debugging session three weeks later when you finally notice the digest has been silently wrong.
Scripting the mechanical steps doesn't just make the good run more likely. It makes the good run the only run.
The gate that actually catches the wrong call
Before I write a single script or a line of SKILL.md, I walk the entire workflow end to end, list every discrete step, and build a table: step, classification, what it becomes. I show that table before building anything.
That step matters more than it sounds like it should. It's the cheapest moment to catch a bad call, and people almost always know which of their "obvious" steps secretly need taste, the moment you make them look at the list. I've had this catch a dedupe rule I'd have scripted on autopilot, that actually needed a judgment call every single time. Skipping this step, doing the decomposition in your head and never writing it down, is the single thing that most reliably separates this from an agent's default behavior. Implicit design can't be checked by anyone, including future-you.
One more thing worth saying plainly: a lopsided table isn't a failure. A research skill or a voice-writing skill can come out 90% judgment, and that's correct. Don't force scripts onto steps that don't earn them. The failure mode to actually watch for runs the other direction: a 90%-mechanical workflow left sitting as prose because writing a script felt like more work than writing an instruction. It usually isn't, and the unattended-run version of your skill will thank you.
The folder shape that falls out of this
Do this consistently and skills start landing in the same shape every time:
SKILL.mdcarries the judgment: the decision criteria, the voice, the examples, the order to call things in, plus the decomposition table itself so the next person who touches the skill can see the design intent instead of reverse-engineering it.scripts/carries the mechanical steps, one script per step, each one handling its own error conditions instead of throwing a bare traceback back at the model.references/carries material the model only needs occasionally: a troubleshooting doc, a compatibility matrix, a pattern library. It stays out of SKILL.md so the instructions the model reads on every run stay short.config.json/config.example.jsonkeeps machine-specific paths and secrets out of the skill body entirely. The checked-in file has placeholder values; the real one stays local.
My own browser-automation skill is a real one built exactly this way: SKILL.md holds the decision guide for which of six browser surfaces to use and how to drive one safely, scripts/ holds a gateway-status check and a one-shot screenshot script, both genuinely mechanical, and references/ holds the pattern library and version-compatibility notes the model only needs when it's actually writing a new automation script, not on every run. If you're building browser automation specifically, I've written a full series on that; this guide is the "why the skill is shaped this way" companion to it.
The failure modes worth naming
A few traps I've walked into building these:
The LLM re-deriving script output. If a parser already emitted a date field as JSON, the instructions shouldn't then ask the model to "determine the date" from raw text. That's re-introducing exactly the improvisation you scripted to avoid.
Scripts that smuggle in judgment without saying so. A script that grows flags like --tone or --importance-threshold 0.7 is a judgment step wearing a mechanical costume. Delete the flag, give the decision back to the model where it belongs.
Over-scripting the subjective. Templating a reflective summary into fill-in-the-blanks kills the thing that made it worth having a model write it. The litmus test decides this, not a quota. Some skills are legitimately almost all prose, and that's fine.
Where this leaves you
This isn't about distrusting the model. It's about spending the model's judgment only where judgment is the actual product, and handing everything else to code that does the same thing correctly every time, whether you're watching or asleep. A skill that runs the same way at 3am as it does when you're sitting there watching it is a skill you can actually schedule and forget about, which is the whole point of building it in the first place.
I've packaged this exact methodology as a skill you can drop into Claude Code, sanitized for you to use on your own machine. Grab it below and point Claude at it next time you say "build me a skill for X." Go build something that runs correctly without you.
This guide was my gift to you. I want everyone to be able to punch above their weight class by leveraging AI to do more with what they've got.
If this helped and you want to know how I help companies through AI consulting, mentoring, or workshops — sign up for my email list or reach out below.