Agent skills today are prose. A markdown file describes what the agent should do and, critically, how to invoke its tools: “run this script with bash”. All of it rides into the context window as text. The model reads it, interprets it, and improvises shell commands. Nothing is typed, nothing is verifiable before execution, and every tool description is paid again on every run. These are soft skills: suggestions the model may follow.
The fix is an old idea. MDX did it on the web years ago: one file, two readers that see different things in it. The writer sees prose; the renderer sees components. Split a skill file the same way and you get a hard skill. The prose stays soft, context the model reads. The tools become hard, typed declarations the engine executes.
The format
A hard skill is still a SKILL.md: markdown prose, plus tool code fences that declare executable tools. The grammar inside a fence is deliberately closed: an id, a description, typed params, and a run template. A malformed line is an error, not a guess.
--- skill: driver-admin description: Administer the Driver closed beta. --- This skill administers the Driver closed beta: the signup waitlist and existing users' plans. Accepting an email lets that person register and sends them an access email. Act on one email at a time, and never invent an address. ```tool id: accept_user description: Accept a waitlisted email so it can register params: email: type: string format: email description: the waitlisted email to grant access to run: uv run python waitlist_admin.py $email ```
The parser splits the file into two channels. The prose travels to the model as context, verbatim. The tool fences never reach the model as text: each one is compiled into a typed tool declaration, injected into the run’s contract, and replaced in the prose by its name. The model sees narrative plus a typed catalog; the engine sees commands.
The design
Because the tool blocks parse statically, you know exactly what a hard skill can touch without executing it: the capability manifest is a property of the file, not a promise in its prose. Because arguments substitute into argv words and never pass through a shell, a hostile value cannot smuggle in commands. Because the model only ever sees the declared tools, the bounded set is the contract, and every run leaves a replayable trace. And because it is plain markdown, the file renders readably anywhere.
Just as important is everything this design leaves out. No repeated token spend, no project exploration, no path caching. The agent never wanders your machine looking for context: the skill is the context, and the declaration is the guardrail. The model’s freedom is exactly as wide as the tools you wrote down, and not one command wider.
We use it
Our own closed-beta operations run on this: waitlist acceptance, plan changes, credit top-ups. One hard skill over the same admin scripts we already had. Reasoning runs in Driver cloud; the scripts run locally, with local secrets that never leave the machine.
$ driver skill "grant access to [email protected]" skill driver-admin · 1 tool accept_user([email protected]) │ ↳ accepted │ ✓ done · 1 tool call Access granted to [email protected]
Try it
The parser and compiler behind all of this ships on npm today as @crtrs/skill. It never calls a model: you own the loop, and the package referees both directions of the contract: what the model may call and what it actually called. The whole thing is four lines:
const { parseSkillFile, compile, resolve } = require('@crtrs/skill') const skill = parseSkillFile('SKILL.md') const system = compile(skill) // prose + call protocol const output = await myInference(system) // your model, your way const call = resolve(skill.tools, output) // typed, validated, argv ready
Hard skills shine wherever a script should never be improvised: deploys with exact flags, admin operations granted one email at a time, the recurring ops request you keep typing by hand.
Citation
@online{creators2026skills, author = {CREATORS}, title = {From Soft to Hard Skills}, date = {2026-07-26}, year = {2026}, url = {https://creators.industries/research/hard-skills}, }

