AI Agent

After Writing Dozens of Skills, I Distilled This Engineering Method: From `Never Triggered` to `Production-Ready`

An engineering method for writing Skills that are correct, stable, and maintainable long-term, distilled from writing dozens of them. Skills are three layers, not one file: L1 metadata for routing, L2 body for behavior, L3 resources for facts and determinism. Most "never triggers" are routing failures — fixed by a `description` that says what, when, and especially when *not* to use it. Execution drift is fixed with numbered steps, stop conditions, and rule-plus-reason instructions. Guardrails turn "should" into machine-checkable checkpoints, with tiered failure handling. Splitting should follow change-reasons, not file length. Four eval metrics (routing, step-following, checkpoints, task success) pin down what to fix. Ends with versioning, a trace-reading workflow, and a copy-paste checklist.

Contents

This article won't go over the difference between Skills and prompts, nor explain what MCP/A2A is — there are already plenty of articles covering those.

I'll assume you already know what a Skill is and have written a few yourself.

Below, we're looking at just one thing: how to write Skills that are correct, stable, and maintainable long-term within a team.


Preface: First Distinguish Which Kind of Problem You're Facing

Most people who have written Skills have gotten stuck on one of these two situations:

  1. Installed but never used: the Skill is clearly in the directory, yet the agent never touches it once.

  2. Used but useless: it triggers occasionally, but as it runs it starts improvising, and the SOP becomes a dead letter.

Faced with these, most people either make the prompt longer or harsher, or pile on a bunch of "MUST", "NEVER", "ALWAYS", "严禁".

But these two situations are completely different things. The former is a routing failure — it happens before the model decides whether to load the Skill at all. The latter is an execution failure — the Skill has been loaded, but the steps, tools, or acceptance criteria weren't followed.

One is "can't find it"; the other is "didn't follow it". The causes are different, so the fixes are different. Mixing them together and fixing both at once is why so many Skills never get better.

Let's talk about how to handle each one properly.


1. A Skill Has Three Layers — It's Not Just a Single File

Many people's Skill is just one lump of SKILL.md — trigger conditions, steps, reference material, examples, caveats, all crammed into a single file.

Such a Skill works in the short term but quickly goes out of control: the body grows longer and longer, and the critical constraints get diluted.

A better approach is progressive loading, which splits discovery, comprehension, and execution into three layers with completely different costs.

LayerWhen it's loadedWhat it carriesDesign responsibility
L1 MetadataAlways loaded at startupname, descriptionPrecise discovery & routing
L2 InstructionsLoaded only after relevance is judgedSKILL.md body: steps, constraints, examplesControlling execution behavior
L3 Resources & codeLoaded on demandScripts, templates, reference materialProviding facts, determinism, and artifacts

These three layers are officially endorsed by Anthropic. The benefit: in the resident context, a single Skill costs only a few tokens of metadata; most of the cost is paid only after loading.

Wherever you cut corners when writing a Skill, that's where problems accumulate:

  • Stuffing body text into L1 bloats the metadata, and every task pays tokens for Skills that were never used.

  • Stuffing reference material into L2 means every trigger reads a pile of irrelevant info, and the key information gets drowned out.

  • Describing deterministic logic in natural language in L2 makes the model "roughly follow along", and boundary conditions start to fail.

Below, I'll walk through each layer and how to do it better.


2. L1: Triggering

The description is what routing and discovery run on

2.1 The vast majority of "not working" cases come down to this one line

The description is the single line in the whole Skill most worth polishing. It decides whether the model will select this Skill at all.

The most common anti-pattern looks like this:

 ❌ Anti-example

name: docs-helper

description: Helps handle documents and related content.

The problem with this line: it only says "roughly what I can do" — it doesn't say "under what circumstances to come find me", nor "under what circumstances NOT to come find me". The model can only guess with a description like this.

A good description should answer at least three things:

  • What it does (capability statement — describe the business outcome in one sentence)

  • When to use it (positive trigger — ideally including the user's real wording)

  • When NOT to use it (exclusion clause + where to go instead)

 ✅ Good example

name: docx-contract-review

description: |

       Reviews contract files in .docx format; checks that required clauses,

       party names, dates, and signature/seal fields are complete.

       USE WHEN: the input is a .docx contract, or the user says

       "help me review this contract" / "see if there's a problem with this agreement".

       DO NOT USE FOR:

       \- PDF content extraction → use pdf-extract instead

       \- Contract revision/redlining → use docx-redline instead

       \- General writing advice → not applicable to this Skill

2.2 Exclusion clauses matter more than trigger clauses

"Don't trigger when it's NOT X" improves routing accuracy more than "trigger when it's X". Positive descriptions are inherently fuzzy — "review this contract" and "summarize this contract" are semantically very close, and the model misjudges easily. But if you explicitly write "not applicable to: general summarization, use doc-summarize instead", the boundary becomes clear.

Exclusion clauses have another benefit: they tell the model where to go when routing was wrong. Writing "use pdf-extract instead" effectively hands the model a routing table — far more useful than merely saying "don't use for PDF".

2.3 The trigger description should contain the user's actual words

The phrases listed under "USE WHEN" are not for hard keyword matching; they compensate for the model's inference bias about how people really speak.

Users don't say "please execute the canary release coordination process". They say:

  • "Roll payments-api out to prod"

  • "Let 5% of traffic through first"

  • "Validate version 1.42.0 with a small traffic slice first"

Write these real phrasings in, and routing hit rate improves noticeably.

2.4 An easy physical pitfall: confirm it actually "exists" first

Before rewriting the description, first confirm the Skill is really being scanned.

I once hit this: a Skill installed with npx skills add actually landed in ~/.agents/skills/, but the Claude Code I was using at the time scanned ~/.claude/skills/. Install location and runtime scan location are two different things. The user thinks it's installed, but the environment doesn't have it at all. The exact directory names vary by toolchain and version — don't memorize those two paths; the key is realizing that "where it's installed" and "where the runtime scans" can be two different places.

So the debugging order should go from the outside in:

Physical existence → manifest declaration → routing selection → body loading → script execution → output acceptance

If the directory isn't even scanned, rewriting the wording a hundred times won't produce a single signal.

2.5 Control how many Skills are mounted at once

Mounting too many Skills makes them compete for attention, and the false-trigger rate rises noticeably. In practice, keeping the simultaneously mounted count under 20 is safer. Package them by role or scenario; don't dump everything in at once.

If you genuinely need many, layer them: keep a small set of high-frequency ones resident, and load the rest dynamically per project or workspace.


3. L2: The Body

SKILL.md is a map, not a warehouse

3.1 Default assumption: "the model is already smart"

The most common mistake in writing Skills is treating the model like an elementary school student and teaching it everything.

Actually, you only need to write what it doesn't know. Domain conventions, internal company processes, and non-obvious pitfalls — that's what the Skill body should carry. Fluff like "read the code carefully first" or "make sure there are no syntax errors" isn't just useless; it dilutes the constraints that actually matter.

3.2 The minimum effective structure of the body

The skeleton I commonly use is this:

 Canary Release Coordination

# Goals & Non-Goals

Coordinate a staged production release.

Non-goals: not responsible for cluster provisioning, not responsible for database migration.

# Prerequisites

\- service, image\_tag, and namespace parameters are confirmed;

\- the caller has approved this production change;

\- baseline SLO data already exists.

# Execution Steps

1\. Preflight: run ./scripts/preflight.sh; any non-zero exit code stops immediately.

2\. Plan: output the target version, canary ratio, and rollback threshold.

3\. Execute: strictly run the declared deployment command exactly once.

4\. Verify: run smoke tests; all assertions in tests/smoke.yaml must pass.

5\. Decide: scale up only if SLO is met; otherwise roll back and emit an audit event.

# Mandatory Checks

\- Deployment operations must be idempotent;

\- canary traffic ratio must stay within policy-allowed bounds;

\- no secrets may be echoed into output.

# Failure & Fallback

\- Only network-class transient failures may be retried once;

\- on threshold-alarm triggers, call rollback.sh and stop immediately;

\- when in doubt, ask exactly one focused question; don't guess at the target service.

# Examples

Input: "Canary payments-api 1.42.0 to prod-us"

→ preflight → release 5% → smoke → scale up or roll back

Counter-example:

Input: "Why is payments-api so slow?"

→ Do NOT invoke this Skill; use k8s-troubleshoot instead.

A few key points:

  • "Non-goals" and "prerequisites" are not optional. The former stops the model from over-expanding its responsibilities; the latter lets the model know to stop and ask when conditions aren't met instead of pushing ahead stubbornly.

  • Steps must be numbered and have stop conditions. Writing "run the test, look at the result, then decide whether to continue" is the same as writing nothing.

  • Examples and counter-examples come in pairs. The counter-example delineates the boundary — prioritize the most easily confused neighboring Skill.

3.3 Control the size

Rule of thumb: keep the SKILL.md body under 500 lines. Beyond that, split it up:

  • Long reference material goes into references/, with the body stating when to read it;

  • Deterministic computation and validation go into scripts/;

  • Output templates and fixed formats go into assets/.

Anthropic's official guidance: scripts are executed by the agent (bash, python, node, etc. all work), and only the script's output enters the context; reference material is explicitly pointed to by the Skill and only read when needed.

3.4 Don't treat ALWAYS/NEVER as a cure-all

Piling on strong imperative words is a very common misconception, and it has two problems:

  • Overuse breeds fatigue — the model won't be more obedient just because you wrote three MUSTs;

  • It only says "what not to do", not "why" — and when the model hits a boundary case, it's precisely the "why" it relies on for analogical reasoning.

The better style is rule + reason:

 ❌ Anti-example

NEVER commit code directly to the main branch.

 ✅ Good example

Don't commit directly to main: every Thursday a release branch is

automatically cut from main, and code committed directly can silently

miss that release train. If you need an urgent fix, open a hotfix

branch and notify the release owner.

Give the reason, and the model can extrapolate correctly when it hits a situation you didn't anticipate.


4. How Strict Should Instructions Be? Set the Level by Risk

My practical experience: how strict an instruction should be is decided by the cost of getting it wrong, not by your level of anxiety.

Lock down every step and the Skill becomes brittle — it stalls the moment the environment shifts even slightly. Leave everything open and high-risk operations become accident-prone.

Risk levelTypical scenariosRecommended styleVerification method
HighDB migration, deletion, production writes, permission changesExact script + strong constraints + approval gateScript validation + human confirmation
MediumDeployment, bulk refactoring, config changesPseudocode + parameterized stepsdry-run + checkpoints
LowCode review, copy-polish, info summarizationNatural-language instructions, leave room for judgmentOutput format conventions

In short, it comes down to how expensive a mistake in this step is:

  • Irreversible → script it + require approval;

  • Reversible but wide blast radius → dry-run + checkpoints;

  • Fine to tweak freely → give a direction and let it improvise.

Anthropic expressed a similar view in Building Effective Agents: use workflows for what predefined code paths can solve, and leave flexibility to where the model genuinely needs dynamic decisions. Over-agentification is itself an anti-pattern.


5. L3: Guardrails

Turning "should" into "checkpoints"

A heads-up: the checks:, side_effects:, on_failure: fields in this chapter are not part of the official agent-skills frontmatter — no runtime currently auto-parses or executes them. They are an explicit contract: written out clearly so the agent can follow them, scripts can enforce them, and humans can review them. To make them actually take effect, you have to implement the corresponding logic in scripts/ or in the body.

5.1 The "thirty percent" principle

My experience: in a mature Skill, at least 30% of the constraints should be turned into machine-verifiable checkpoints.

Natural language suits semantic judgments that can't be exhaustively enumerated; code suits format, numeric, existence, and idempotency checks.

 ❌ Anti-example

Be careful not to leak secrets, and validate input parameters.

 ✅ Good example

checks:

       \- id: secret-leak-scan

         run: ./scripts/scan\_secrets.sh

         on\_failure: block        # halt immediately

       \- id: version-format-check

         run: ./scripts/semver\_check.sh "\${image\_tag}"

         on\_failure: block

       \- id: idempotency-rehearsal

         run: ./scripts/dry\_run.sh

         on\_failure: ask\_user     # stop and ask

Note that on_failure has three possible values, mapping to three handling strategies: block (halt immediately), ask_user (stop and ask), retry (can retry). Writing the handling strategy in as well is what makes the Skill complete.

5.2 Declare a side-effect contract

If a Skill produces external side effects, it's best to write them down clearly:

inputs:

       service: string              # service name

       image\_tag: semver            # image version

       namespace: enum(prod-us, prod-eu)

side\_effects:

       \- runs kubectl apply against the target namespace

idempotent: true               # whether the operation is idempotent

dry\_run: scripts/dry\_run.sh    # rehearsal script

rollback: scripts/rollback.sh  # rollback script

requires\_approval: true        # whether human approval is required

Idempotency is especially important, because an agent may retry in all sorts of situations: network jitter, tool timeouts, replanning after context compression. If a Skill isn't idempotent and you don't state it, a single retry can turn into a double deployment.

5.3 Failure strategies should be tiered

Blindly "retry three times on failure" is not a good failure-handling strategy.

retry:

       max: 1

       conditions: \[network-timeout]        # only retry transient failures

escalate:

       conditions: \[insufficient-permission, data-corruption-risk]   # structural errors go up immediately

human\_approval:

       conditions: \[production-write, rollback-operation]

never\_skip: \[preflight, threshold-triggered-rollback]  # these steps are never skipped

Retries apply only to problems provably transient; insufficient permissions, data-corruption risk, and unknown states must stop or escalate — retrying does nothing useful there.

5.4 One hidden runtime constraint

If a Skill has any stage that waits for human input, authorization confirmation, or an external callback, you must declare whether it can be used inside a sub-agent.

I once hit an example: when the Skill was invoked in a sub-agent, the interactive authorization prompt was returned verbatim to the orchestrator — but at the time there was no primitive that let the orchestrator inject a reply and resume the sub-agent session. The flow dead-locked.

So before publishing, you must identify whether the Skill depends on interactive gating and declare it as either "top-level session only" or "usable in sub-agents".


6. When to Split, When to Merge

6.1 Split criterion: look at the "reason for change", not the length

The most common mistake is splitting by file length — e.g., cutting SKILL.md whenever it exceeds 500 lines — and that's almost always wrong.

Better question: would these two parts change for different reasons?

Judgment dimensionConclusion
Different trigger conditionsSplit
Different verification standardsSplit
Different permission/approval requirementsSplit
Different input/output contractsSplit
Just longer stepsDon't split
Only different tools called, same flowParameterize
Different output format but same flowAdd a template

In short: whether to split depends on whether the "reasons for change" — trigger, verification, permissions — differ, not on length.

6.2 The boundary between Skill / tool / RAG

Here's a table I've put together:

AssetCore responsibilityTypical failureGovernance measure
SkillSteps, constraints, decisions, acceptanceWrong trigger, step drift, missing acceptancedescription, manifest, eval
tool/functionOperation interface & side effectsParameter errors, insufficient permission, exceptionsschema, error codes, idempotency, logging
RAG/knowledge baseVolatile factsRetrieval errors, stale versionsSource metadata, citation validation
MemoryUser & task stateOverreach, staleness, pollutionScoping, TTL, write approval
  • "Can it be called" problems → look at the tool,

  • "Are the facts right" problems → look at RAG,

  • "Is the behavior stable" problems → look at the Skill.

A word on MCP while I'm at it: MCP solves "where to connect"; Skills solve "how to use it well". They complement each other — they're not rivals.


7. No Eval, No Skill Iteration

Most people finish writing a Skill, run it twice, feel fine, and submit. Three months later, nobody dares to touch it — because they don't know whether a change will break something.

7.1 The four metrics must be tested separately

Evaluating only the final answer mixes four kinds of problems together. I recommend splitting them:

MetricDefinitionWhat it diagnoses
Routing accuracySamples where the correct first choice or candidate was selected ÷ routing samplesTrigger & boundaries
Step-following rateSamples where mandatory steps were followed ÷ samples correctly routed to this SkillSOP expression
Checkpoint pass rateTrials where automatic checkpoints passed ÷ total trialsHard constraints
Task success rateTrials satisfying final environment assertions ÷ total trialsOverall result

A drop in the metrics points directly at what to fix:

  • Routing accuracy down → fix the description;

  • Step-following rate down → fix the body instructions;

  • Checkpoint pass rate down → fix the scripts or validation logic;

  • Everything down and unfixable no matter what → the Skill needs a redesign.

7.2 A minimal usable routing test set

Routing is the easiest to test and the first thing you should test. I suggest maintaining 20 samples per Skill:

TypeCountPurpose
Typical positive examples5Verify the core task can trigger
Near-synonym rewrites5Verify robustness against natural expression drift
Strong negatives5Verify exclusion clauses actually take effect
Adjacent-skill boundary5Verify discrimination between candidates

The "strong negatives" group is especially valuable — it tests exactly whether what you wrote under "DO NOT USE FOR" actually works.

7.3 Don't just look at the final output

Anthropic stressed a key point when discussing agent eval: the model saying "booking confirmed" doesn't mean a booking record actually exists in the environment.

So assert against environment state, not model output:

 ❌ Anti-example: only checks what the model said

assert "released" in final\_output

 ✅ Good example: checks the real state of the environment

assert query\_deployment("payments-api").version == "1.42.0"

assert query\_slo("payments-api").error\_budget\_percent > 5

Also, run each group at least 3 trials. Agent output fluctuates; a single run proves nothing.

7.4 Move from "tweaking the prompt" to "reading the trace"

When you don't know where a Skill is going wrong, read the trace first — don't rewrite the wording on a hunch.

A useful trace should contain at least:

  • the Skill's id and version number;

  • all candidates at trigger time and the selection reason;

  • which part of the body was loaded;

  • script calls, arguments, and output;

  • checkpoint results;

  • rollback events.

Debugging flow:

  • Reproduce: pin the prompt, model, tool state, and environment variables;

  • Layer replay: first mock the tools to verify step logic, then hook up the real environment to verify side effects;

  • Diff comparison: put a successful and a failed trace side by side to locate where the divergence is — routing, arguments, branch, or checkpoint;

  • Minimal fixes: change only one category at a time (description, body, script, data), then re-run the same eval set.

If you change four things at once, even a good result won't tell you which one earned it — and a bad result is worse, because you'll have to change them back.


8. Versioning: Making Skills Maintainable

Once you're in a team, a Skill is no longer a "conversation attachment" — it's a code asset.

Recommended directory structure:

skills/

       release-canary/

         SKILL.md              # metadata + instructions + examples

         CHANGELOG.md

         metadata.schema.json

         scripts/

           preflight.sh        # preflight

           dry\_run.sh          # rehearsal

           rollback.sh         # rollback

         tests/

           smoke.yaml

           eval/

             001-canary-normal-path.yaml

             002-slo-exceeded-triggers-rollback.yaml

         references/

           release-policy.md   # release policy details

Version numbers should follow major.minor.patch:

Change typeVersion ruleExample
Trigger condition or contract changemajorRemoving auto-scale, changing required inputs
Added step or checkminorAdding an SLO budget check
Copy or example revisionpatchAdjusting the description, adding examples

Mark breaking changes explicitly in the CHANGELOG:

v2.0.0 - 2026-08-20

\- BREAKING: removed auto-scale; callers must explicitly approve

\- description: added exclusion clause for "ad-hoc troubleshooting/debugging" scenarios

\- checks: require SLO budget validation before scaling up

Without versions and a CHANGELOG, you can't attribute a failure to "this change broke it". And attribution is the prerequisite for continuous iteration.


9. A Checklist You Can Copy Directly

Run through the following before publishing a Skill:

Trigger (L1)

  • [ ] name follows the platform naming convention (lowercase letters, digits, hyphens; matches the directory name)

  • [ ] description states all of "what it does, when to use, when not to use"

  • [ ] Exclusion clauses point to "who to go to", not just "don't come to me"

  • [ ] "USE WHEN" contains the user's real phrasings, not bookish terminology

  • [ ] Confirmed the Skill is actually scanned at runtime (not just present on disk)

Body (L2)

  • [ ] Has explicit "non-goals", delineating responsibility boundaries

  • [ ] "Prerequisites" make clear "stop and ask when conditions aren't met"

  • [ ] Steps are numbered, each with a stop condition

  • [ ] Examples and counter-examples come in pairs; counter-examples cover the most easily confused neighboring Skill

  • [ ] No piling of strong imperative words; key rules carry a "why"

  • [ ] Body is under 500 lines; long material already split into references/

Execution & guardrails (L3)

  • [ ] At least 30% of constraints turned into machine-verifiable checkpoints

  • [ ] Side effects, idempotency, rehearsal script, and rollback script are declared

  • [ ] Failure strategy is tiered: transient failures retry, structural errors escalate

  • [ ] If it depends on interactive gating, declared whether sub-agents are supported

Evaluation

  • [ ] Has 20 routing samples (including 5 strong negatives)

  • [ ] Assertions hit environment state, not model output

  • [ ] At least 3 trials per group, with baseline comparison

  • [ ] Trace is replayable; can locate exactly which step failed

  • [ ] Has version number and CHANGELOG; breaking changes marked

Honestly, I think all of the above (even this whole article) could itself be written as a Skill for checking Skills — haha!

Android utilities we built — take a look

Kuaiyatu compresses images locally (batch supported); Kuailuping records your screen locally. No upload, no sign-in.

↑ Back to top