AI agents are becoming the backbone of modern automation. But an agent is only as useful as its skills — the modular capabilities that let it interact with the world, solve problems, and take action on behalf of users.
In this guide, we'll walk through how to build an agent skill from scratch: what it is, how to structure it, and how to make it reusable and reliable.
What Is an Agent Skill?
A skill is a self-contained unit of capability that an agent can invoke. Think of it like a plugin. Instead of hardcoding every behavior into the agent itself, you package specific functionality into skills that the agent can discover, understand, and use.
Examples of skills:
- Weather lookup — fetch current weather for a given location
- Web search — query a search engine and return results
- Database query — run SQL against a connected database
- Email sender — compose and send emails on behalf of the user
- Code executor — run code snippets in a sandboxed environment
Each skill has a clear purpose, defined inputs and outputs, and works independently of other skills.
Anatomy of a Skill
A well-structured skill typically includes:
my-skill/
├── SKILL.md # Description, instructions, and usage guide
├── index.js # Main skill logic
├── schema.json # Input/output schema
└── README.md # Documentation for developers
Let's break down each piece.
1. The Skill Manifest (SKILL.md)
This is the most important file. It tells the agent what the skill does, when to use it, and how to use it. Write it in plain language — the agent reads this to decide whether to invoke your skill.
# Weather Skill
Get current weather and forecasts for any location worldwide.
## When to Use
- User asks about weather, temperature, or forecasts
- User wants to know if they need an umbrella or jacket
## When NOT to Use
- Historical weather data requests
- Severe weather alerts or emergency info
## Usage
Call the `getWeather` function with a location string.
Returns temperature, conditions, humidity, and wind speed.
Be specific about boundaries. Telling the agent when not to use a skill is just as important as telling it when to use one.
2. Input/Output Schema
Define a clear contract for what your skill expects and returns. JSON Schema works well for this:
{
"name": "getWeather",
"description": "Get current weather for a location",
"input": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name or coordinates (e.g., 'London' or '51.5,-0.1')"
},
"units": {
"type": "string",
"enum": ["metric", "imperial"],
"default": "metric"
}
},
"required": ["location"]
},
"output": {
"type": "object",
"properties": {
"temperature": { "type": "number" },
"conditions": { "type": "string" },
"humidity": { "type": "number" },
"windSpeed": { "type": "number" }
}
}
}
Strict schemas prevent ambiguity. The agent knows exactly what to pass in and what to expect back.
3. The Skill Logic
Keep the implementation focused and defensive. Handle errors gracefully — the agent needs useful error messages to recover or inform the user.
async function getWeather({ location, units = "metric" }) {
if (!location || location.trim() === "") {
return { error: "Location is required" };
}
try {
const response = await fetch(
`https://api.weatherservice.com/v1/current?q=${encodeURIComponent(location)}&units=${units}`
);
if (!response.ok) {
return { error: `Weather API returned ${response.status}` };
}
const data = await response.json();
return {
temperature: data.main.temp,
conditions: data.weather[0].description,
humidity: data.main.humidity,
windSpeed: data.wind.speed,
location: data.name,
};
} catch (err) {
return { error: `Failed to fetch weather: ${err.message}` };
}
}
module.exports = { getWeather };
Key principles:
- Validate inputs before doing anything
- Return structured errors instead of throwing exceptions
- Keep responses concise — agents work better with focused data
- Don't include unnecessary information — return what the user needs
Design Principles for Great Skills
Single Responsibility
One skill, one job. A weather skill shouldn't also handle calendar events. If you're tempted to add unrelated functionality, make a separate skill.
Idempotency Where Possible
Skills that read data should be safe to call multiple times. For skills that write data (sending emails, creating records), make this clear in the manifest so the agent doesn't accidentally duplicate actions.
Meaningful Error Messages
// Bad
return { error: "Something went wrong" };
// Good
return { error: "Location 'Xyzzy' not found. Try a city name like 'New York' or coordinates like '40.7,-74.0'" };
The agent uses error messages to decide what to do next. Vague errors lead to vague recovery attempts.
Rate Limiting and Caching
If your skill calls external APIs, build in rate limiting and caching:
const cache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
async function getWeatherCached({ location, units }) {
const key = `${location}:${units}`;
const cached = cache.get(key);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return cached.data;
}
const result = await getWeather({ location, units });
cache.set(key, { data: result, timestamp: Date.now() });
return result;
}
This protects both the external API and your costs.
Security
- Never expose raw API keys in skill responses
- Sanitize inputs to prevent injection attacks
- Limit scope — a skill should only access what it needs
- Log sensitive operations for audit trails
Testing Your Skill
Test skills independently before plugging them into an agent:
// test.js
const { getWeather } = require("./index");
async function runTests() {
// Happy path
const result = await getWeather({ location: "London" });
console.assert(result.temperature !== undefined, "Should return temperature");
// Missing input
const error = await getWeather({});
console.assert(error.error !== undefined, "Should return error for missing location");
// Invalid location
const notFound = await getWeather({ location: "NotARealPlace12345" });
console.assert(notFound.error !== undefined, "Should handle unknown locations");
console.log("All tests passed");
}
runTests();
Test edge cases: empty strings, very long inputs, special characters, network failures, and API rate limits.
Registering the Skill with Your Agent
How you register a skill depends on your agent framework. The general pattern is:
- Discovery — the agent scans available skills and reads their manifests
- Selection — when a user request comes in, the agent matches it against skill descriptions
- Invocation — the agent calls the skill with structured inputs
- Response — the agent incorporates the skill's output into its reply
Most frameworks support a simple registration pattern:
agent.registerSkill({
name: "weather",
description: "Get current weather and forecasts for any location",
manifest: "./skills/weather/SKILL.md",
handler: require("./skills/weather"),
});
Common Pitfalls
Over-engineering the first version. Start simple. A skill that does one thing well beats a complex skill that does many things poorly.
Vague descriptions. If the agent can't tell when to use your skill, it won't use it correctly. Be precise in your SKILL.md.
Ignoring failure modes. External APIs go down. Networks fail. Always handle errors and give the agent enough context to recover gracefully.
Returning too much data. Agents have context limits. Return only what's needed for the user's request, not entire API responses.
No timeout handling. Always set timeouts on external calls. An agent waiting 30 seconds for a response creates a terrible user experience.
Wrapping Up
Building agent skills is about creating focused, reliable, well-documented modules that an agent can confidently use. The best skills are:
- Clear — the agent knows exactly when and how to use them
- Robust — they handle errors gracefully and fail safely
- Focused — they do one thing and do it well
- Documented — both the agent and human developers can understand them
Start with a simple skill, get it working end-to-end, then iterate. The modular nature of skills means you can always improve or replace them without touching the rest of your system.
Happy building! 🛠️
Top comments (1)
Good breakdown of the skill structure. The SKILL.md → schema.json → implementation pattern maps well to how real agent systems work.
One thing I'd emphasize more: the SKILL.md file is doing double duty. It's documentation for humans AND instructions for the agent. Those two audiences need different things. Humans need context and rationale. Agents need precise trigger conditions and output formats. When you write a SKILL.md that tries to serve both, it ends up serving neither well.
A pattern that works better in practice: SKILL.md for the agent (concise, structured, includes "when NOT to use this skill") and README.md for the human developer (context, examples, troubleshooting). The agent reads SKILL.md on every invocation — keeping it tight saves tokens and reduces confusion.