If you've started using Claude Code, you've probably run into two terms that sound similar but do very different jobs: agents and skills. This article breaks both down from scratch, shows how they connect, and β most usefully β shows what actually changes in the output depending on how you set things up.
Here's a scenario to keep in mind as we go: imagine you're working on a project that already has a CLAUDE.md file with your general project rules. Now you need to add several new API endpoints to the codebase, each expected to follow the same conventions β validation, response shape, naming, rate limiting. This is exactly the kind of recurring, rule-heavy task where skills and subagents start to earn their keep, and we'll use it as the running example throughout.
Start with the concrete thing: a subagent is a separate worker that Claude Code launches to do one focused job on its own β in its own isolated context window β and then reports back just a summary, without cluttering your main conversation.
Think of it like handing a task off to a contractor: they go do their own research, their own work, in their own space, and hand back a finished report. The important part isn't secrecy β it's isolation: your main conversation doesn't need to hold the subagent's entire working process, only its result, which is what keeps the main session lean.
A subagent works in a loop to get that job done: plan β act β observe β repeat until it's finished. It reads files, runs commands, checks the results, and keeps going until the task is complete β not a one-shot guess from memory.
flowchart LR
P["Plan
decide the next step"] --> Ac["Act
read a file, run a command"]
Ac --> O["Observe
check the result"]
O -->|not done yet| P
O -->|task complete| Done(["Report back"])
One more useful property: subagents can run in parallel. If a task naturally splits into independent pieces β say, reviewing three separate API modules β Claude Code can run multiple subagents concurrently instead of one after another, and collect their results once they finish.
That loop β plan, act, observe, repeat β is actually the general definition of an agent. Claude Code itself is an agent; a subagent is just a second agent instance, launched by and subordinate to the main one (hence "sub"). This article uses "Claude Code" for the main system and "subagent" for the launched-worker feature, to keep things unambiguous.
A subagent answers "who does the work?" A skill answers "how should this kind of work be done?" They're not alternatives you choose between β the most useful setups combine them: a subagent gives you isolation and specialization, and a skill preloaded into it gives that specialist your team's actual playbook. You can use a skill without a subagent, a subagent without a skill, or both together β which is exactly what the running example below does.
A skill is a folder with a SKILL.md file: plain-language instructions (plus optional scripts or templates) that teach Claude Code how to do a specific recurring task your way.
your-project/
βββ CLAUDE.md # always-loaded project rules
βββ .claude/
β βββ skills/
β β βββ api-conventions/
β β βββ SKILL.md # "how we write endpoints here"
β βββ agents/
β βββ code-reviewer.md # a subagent definition
Here's what that SKILL.md file actually looks like, for our running example β a set of conventions for writing API endpoints:
---
name: api-conventions
description: "Use when writing, editing, or reviewing REST API endpoint code (routes, controllers, request handlers) in this repo β covers response format, validation, naming, and rate limiting rules."
---
- All endpoints return { data, error } shape
- Use zod for validation
- Endpoint names are kebab-case
- Every endpoint must have a rate limiter
The description field, in the frontmatter at the top, is the key mechanic: it's short, always-available metadata that Claude uses to judge when this skill is relevant to what you're doing. The content below it β the actual rules β only gets pulled into context once Claude decides the skill applies. That's how a project can have dozens of skills sitting around without bloating every conversation with all of them at once.
Here's what tends to happen without one. Say a developer is adding endpoints one at a time, over several sessions, without an api-conventions skill in place:
| Endpoint 1 β Day 1 | Endpoint 2 β Day 2 | Endpoint 3 β New session | Endpoint 4 β New dev |
|---|---|---|---|
| Rules typed in prompt | Forgot the rate limiter | Reworded the shape rule | Recalled rules from memory |
| β Followed | β οΈ Drifted | β οΈ Drifted | β οΈ Drifted |
None of this is a "bug" in Claude β it did exactly what it was told each time. The problem is there was never one single source of truth being consulted; every endpoint's standard is only as good as whatever the developer happened to type that day. A skill fixes exactly this: the conventions live in one file, get pulled in the same way every time, and nobody has to remember or retype them.
The important point isn't just persistence across sessions, either β it's that the team's procedure becomes an explicit, reusable artifact instead of knowledge that only ever lived inside someone's prompt.
Now let's wire the skill into a subagent. Here's a code-reviewer subagent that reviews endpoint code β notice it explicitly lists the api-conventions skill we just defined above, via the skills: field:
---
name: code-reviewer
description: Reviews code changes for correctness, style, and API convention compliance. Use after writing or editing API endpoints.
tools: Read, Grep, Glob
skills: [api-conventions]
model: sonnet
permissionMode: default
---
You are a careful, no-nonsense code reviewer for this repository.
When invoked:
1. Identify what files were just changed or created.
2. Check the code against the preloaded `api-conventions` skill.
3. Also check for general issues: error handling gaps, missing validation, unclear naming.
4. Do NOT rewrite the code yourself β report findings only.
Output format:
- β
What's correct / follows convention
- β οΈ Convention violations
- π Bugs or risks
- One-line verdict: Approve / Needs changes
That skills: [api-conventions] line matters more than it looks. A subagent runs in its own isolated context and doesn't automatically inherit whatever the main session happens to know β if you forget to list a skill there, the subagent simply won't load it. It'll still check for generic issues, but it won't know your project's response shape, validation library, naming rule, or rate-limiter requirement even exist. It can end up approving code that quietly breaks your own conventions, simply because nobody told it those conventions were relevant to its job.
Let's see the skill actually do its job. Suppose the developer types:
"make an endpoint for GET /books"
Claude Code checks the descriptions of the skills available in the project, notices api-conventions matches ("writing... REST API endpoint code"), pulls in the full skill, and writes the endpoint using those exact rules:
// routes/get-books.js
import { z } from 'zod';
import rateLimit from '../middleware/rate-limit.js';
const querySchema = z.object({
limit: z.number().int().positive().max(100).optional(),
offset: z.number().int().min(0).optional(),
});
router.get('/get-books', rateLimit(), async (req, res) => {
const parsed = querySchema.safeParse(req.query);
if (!parsed.success) {
return res.status(400).json({ data: null, error: parsed.error.message });
}
try {
const books = await Book.find(parsed.data);
return res.json({ data: books, error: null });
} catch (err) {
return res.status(500).json({ data: null, error: 'Failed to fetch books' });
}
});
Every rule from api-conventions shows up here: the response is always { data, error }, zod handles validation, the route follows kebab-case, and there's a rate limiter on the handler. None of that was restated in the command β it came entirely from the skill.
Same command β "review my codebase" β run two different ways.
| Without subagent | With subagent | |
|---|---|---|
| Skill | Claude decides to use a relevant skill if the description matches | Explicitly included via the subagent's skills: field |
| Where it runs | Main conversation | Isolated context window |
| What you see | Tool activity and intermediate progress as part of the main session | Just a clean summary/verdict |
| Tool access | Whatever your main session has | Can be restricted (e.g. read-only) |
| Best for | Quick one-off checks | Isolated, specialized, parallelizable, or permission-sensitive reviews |
Without a subagent, the work happens in the main session, so file reads and intermediate steps are part of that same conversation.
With the subagent, your main conversation just receives something like this β notice how each line maps directly back to a bullet in the Output format section of the subagent file above: the generic "β
What's correct / follows convention" instruction becomes a concrete, specific finding.
β
Response shape matches convention on all 3 new endpoints
β οΈ POST /orders is missing a rate limiter (api-conventions rule #4)
π No input validation on the `quantity` field in POST /orders
Verdict: Needs changes
GET /books example shows.