Agent Skills Are Code. Treat Them Like It.

You can read this article on Medium as well if you wish!
The first instinct when building an LLM skill is often to write more documentation. Explain the architecture. Add examples. Describe edge cases. Include some background so the model understands why things work the way they do.
That instinct is usually wrong.
A skill is not documentation for a developer who is trying to learn your system. It is context injected into an agent while that agent is trying to get something done. That distinction changes how the skill should be designed.
The useful mental model is not:
"How do I explain this system to the model?"
It is:
"What information does the agent need to make the next correct decision?"
Once you look at skills this way, several design choices become much easier:
- Keep instructions procedural.
- Make skill activation specific.
- Load information progressively.
- Move deterministic work into scripts.
- Test skills against actual tasks.
A skill is procedural knowledge
Modern coding agents already know how to do a surprising amount of generic software engineering. They know Git. They know npm. They understand Angular, React, TypeScript, SQL, REST APIs, testing frameworks, CI configuration and thousands of other technologies.
So a skill like this has contributed almost nothing:
When implementing an Angular component, use TypeScript.
Write maintainable code.
Follow Angular best practices.
Add tests.
You have consumed context without supplying knowledge the model was missing.
The useful skills are usually about knowledge that is specific to your environment:
When modifying an Angular application in this repository:
1. Locate the closest project.json belonging to the affected application.
2. Check whether the project uses Vitest or Karma before generating tests.
3. For applications under apps/trading/, do not import directly from apps/shared/.
4. Run the affected project's test target rather than the workspace-wide test command.
5. Before changing a public interface in libs/order-entry/, search for consumers in
apps/trading-terminal and apps/blotter.
Now the agent has learned something it could not reliably infer from Angular documentation. That is what a skill is good at: capturing procedural knowledge about how work should happen in a particular environment.
Agent Skills formalize this as directories containing a SKILL.md plus optional scripts and additional resources. The skill metadata is available for discovery, while the full instructions and supporting files load only when needed.
The important part isn't the file format. It is the separation between general model capability and knowledge your environment needs to provide.
Context is a budget
Every capability we give an agent has a cost.
Tool descriptions consume context. MCP tool schemas consume context. Repository instructions consume context. Conversation history consumes context. Skill descriptions consume context. Once a skill is activated, its body consumes even more.
So the question is not just whether some information is useful. The question is whether it is useful for this particular task.
Suppose I have these skills installed:
angular-testing
angular-migration
jira-workflow
postgres-debugging
release-process
accessibility-review
storybook
incident-response
performance-profilingIf I'm fixing a failing Angular unit test, there is little reason for the agent to know anything about our release process or PostgreSQL debugging procedure. This is why skill discovery matters almost as much as skill content.
Consider this description:
---
name: testing
description: Helps run tests in our repository.
---
It is extremely easy to trigger. Compare it with:
---
name: angular-unit-testing
description: >
Run and debug Angular unit tests in projects using Karma or Vitest.
Use for Angular unit-test failures and test creation.
Do not use for Playwright E2E tests, React projects, or backend tests.
---
The second description does more than describe the skill. It establishes a routing boundary, and that boundary keeps irrelevant instructions out of the working context.
This is one of the useful design points from Minko Gechev's talk Skill Design for LLM Agents. Skill metadata is not administrative decoration. Bad activation rules can pull useful instructions into the wrong tasks, while overly narrow rules can prevent the skill from being discovered at all.
Progressive disclosure beats giant SKILL.md files
A common failure mode is turning SKILL.md into a miniature internal wiki.
You start with twenty lines. Then somebody adds deployment instructions. Then troubleshooting. Then examples. Then configuration options. Three months later the agent needs to read 1,500 lines before changing one file.
The better structure is closer to how we structure software:
angular-workflow/
├── SKILL.md
├── testing.md
├── migrations.md
├── architecture.md
└── scripts/
├── affected-projects.ts
└── verify-boundaries.tsSKILL.md contains the routing logic:
# Angular repository workflow
When modifying an Angular project:
1. Identify the affected project.
2. Read `architecture.md` when changing dependencies between projects.
3. Read `testing.md` when creating or modifying tests.
4. Read `migrations.md` when changing Angular versions or framework APIs.
5. Run `scripts/verify-boundaries.ts` after changing imports between projects.
Now the agent only pulls detailed testing instructions into context when the task involves testing.
Anthropic calls this model progressive disclosure. The agent loads the metadata first, reads the main skill body once the skill becomes relevant, and discovers referenced files later, only as it needs them.
This matters because the goal isn't to give the model as much knowledge as possible. The goal is to give it the right knowledge at the right time.
Don't ask an LLM to interpret something a script can enforce
There is another useful boundary when designing skills: instructions are for decisions; scripts are for deterministic work.
Imagine a skill containing this:
Find all applications under apps/.
Check their Angular versions.
Ignore archived applications.
Compare their versions with the root package.json.
Report applications that are behind.
An agent can execute that. But it has to repeatedly inspect directories, interpret files, decide what counts as archived, keep state and potentially make several tool calls.
If the operation can be expressed deterministically, write it once:
node scripts/check-angular-versions.mjs
The skill can then say:
When checking Angular version consistency, run `node scripts/check-angular-versions.mjs`.
If it reports mismatches, inspect only the projects listed in the output.
This is better for several reasons:
- The script can be unit-tested.
- It behaves consistently.
- It needs fewer model decisions.
- It often requires fewer tool calls.
- When something goes wrong, you can reproduce the failure without asking an LLM to perform the same reasoning again.
This does not mean "replace agents with scripts." It means choosing the correct abstraction:
- Use the model when the task involves judgment, such as "Which projects are likely affected by this architectural change?"
- Use code when the task has an exact algorithm, such as "Which projects import package X?"
The agent can orchestrate both. That combination is more useful than trying to encode the whole workflow as English.
Tools and skills solve different problems
This distinction also helps explain the relationship between tools and skills. A tool gives the agent an action. A skill teaches it when and how to use actions.
For example, a CLI might expose:
tw project affected --base main
The tool gives the agent the ability to compute affected projects. The corresponding skill contains the organizational knowledge:
Before modifying a shared library:
1. Run `tw project affected --base main`.
2. If either `trading-terminal` or `blotter` is affected, run their integration suites.
3. If more than five applications are affected, stop and inspect the dependency change
before proceeding.
The CLI doesn't know why five affected applications are suspicious. The skill doesn't need to know how dependency traversal is implemented. That is a clean boundary.
The same principle applies whether the underlying capability comes from a shell command, an API, an MCP server or another tool interface:
- Don't put policy into low-level tools unless it belongs there.
- Don't put deterministic algorithms into prose unless you have to.
More skills are not automatically better
There is a tempting workflow when adopting agentic development. Find a collection of useful-looking skills, install all of them, and assume the agent has become more capable.
The evidence so far suggests being much more selective.
SkillsBench evaluated skills on 86 tasks across 11 domains. In the first version of the paper, curated skills raised the average pass rate by 16.2 percentage points, but the effect varied heavily. Software engineering gained only 4.5 points, and 16 of 84 tasks got worse with skills. Skills that models generated for themselves produced no average improvement.
Software-engineering-specific evidence is even more interesting.
SWE-Skills-Bench evaluated 49 public skills against real software-engineering tasks. Thirty-nine of them produced no pass-rate improvement, and the average gain was only 1.2 percentage points. Seven specialized skills produced gains of up to 30 points. Three made things worse, by up to 10 points, because their version-mismatched guidance conflicted with the project context.
That result matches what I would expect from the architecture. A generic skill competes with knowledge the model already has. A highly specific skill can provide exactly the missing information:
When our generated API clients change, regenerate schemas before running typecheck.
That is valuable. This is much less valuable:
Always think carefully before modifying APIs.
The practical rule is simple:
Don't create a skill because a task exists. Create a skill because there is procedural knowledge the agent is repeatedly missing.
Skills need tests
This is probably the biggest conceptual shift. A SKILL.md file looks like documentation, but changing it changes program behavior:
- Delete one instruction and an agent may stop performing a required verification.
- Broaden the description and the skill may start activating during unrelated tasks.
- Add a large reference document and token usage may increase without improving outcomes.
- Rewrite a procedure and success rates may go down.
That sounds much closer to code than documentation. So test it.
For a skill that controls Angular migrations, an evaluation set might contain tasks such as:
Upgrade application A from Angular 20 to Angular 21.
Expected:
- correct migration command executed
- generated migration changes preserved
- application tests executed
- unrelated applications untouchedAnother task:
Upgrade a shared library consumed by applications A and B.
Expected:
- consumers discovered
- both affected applications typechecked
- dependency constraints respectedAnd another:
Modify a React application in the same monorepo.
Expected:
- Angular migration skill is NOT activatedThat last case matters just as much as the successful invocation. You want to test both should activate and should not activate.
Gechev built SkillGrade around this idea. You define tasks, run the agent several times, and grade every run with deterministic checks, an LLM rubric that reads the session transcript, or both.
You don't need SkillGrade to apply the principle. What matters is treating the skill as something whose behavior can regress.
If you change a skill and cannot tell whether the new version performs better than the previous one, you are editing production logic without tests.
Start from failures, not from a skill marketplace
The easiest way to build useless skills is to start by asking "What skills should my coding agent have?"
You'll quickly get a list containing testing, debugging, code review, documentation, architecture, Git workflows, accessibility, security and another twenty things the model already understands reasonably well.
A better starting point is your agent history. Look for repeated failures. Maybe the agent:
- keeps running every test in a huge monorepo instead of identifying the affected project;
- repeatedly violates a repository dependency boundary;
- uses the technically valid migration path but misses an internal compatibility step;
- changes generated files instead of their source schemas;
- spends five minutes rediscovering the same debugging procedure every time a particular service fails.
Those are skill candidates. The workflow then becomes:
Notice the last option. Delete it.
A skill that doesn't improve agent behavior is not harmless. It is more context, more maintenance and another instruction that can conflict with something else later.
The real abstraction is controlled context
Skills are often described as a way to extend agents. That's true, but I think it misses the more interesting engineering problem.
The hard part is not giving an agent information. We already have plenty of ways to do that. The hard part is controlling which information becomes relevant during which execution.
A well-designed agent environment therefore starts to look less like one giant prompt and more like a software system:
Each layer has a different responsibility. Once those responsibilities are blurred, context grows and behavior becomes difficult to reason about. Once they are separated, skills become surprisingly boring.
And boring is good.
A good skill is not an impressive prompt. It is a small piece of procedural knowledge, activated in the right situation, backed by deterministic tools where possible, and tested against the failures it is supposed to prevent.