Claude Skills Integration Guide
What is a Claude Skill?
An on-demand capability folder with three loading levels — metadata, body, bundled resources.
Level 1: name+description (~100 tokens) live in the system prompt. Level 2: the SKILL.md body is read via bash when the description matches. Level 3: reference files are read only when linked, and scripts are executed (their source never enters context — only stdout costs tokens). The same folder works in Claude Code (.claude/skills/), via the API (/v1/skills + container), and on claude.ai. For codeAmani, skills encode fiddly house conventions (M-Pesa phone normalisation, KES formatting, callback idempotency) once and reuse them across every project.
Six things you reach for first
Author one SKILL.md, tune the description, let progressive disclosure keep context cheap.
██████╗██╗ █████╗ ██╗ ██╗██████╗ ███████╗ ███████╗██╗ ██╗██╗██╗ ██╗ ███████╗
██╔════╝██║ ██╔══██╗██║ ██║██╔══██╗██╔════╝ ██╔════╝██║ ██╔╝██║██║ ██║ ██╔════╝
██║ ██║ ███████║██║ ██║██║ ██║█████╗ ███████╗█████╔╝ ██║██║ ██║ ███████╗
██║ ██║ ██╔══██║██║ ██║██║ ██║██╔══╝ ╚════██║██╔═██╗ ██║██║ ██║ ╚════██║
╚██████╗███████╗██║ ██║╚██████╔╝██████╔╝███████╗ ███████║██║ ██╗██║███████╗███████╗███████║
╚═════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚══════╝╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚══════╝Claude Skills Integration Guide
Focus: how to author an Agent Skill — a
SKILL.mdfolder Claude loads on demand. The whole system is one idea: a tiny always-loadeddescriptionadvertises the skill, and the full instructions (plus any scripts and reference files) load only when a request matches. This is the Agent Skills format, distinct from building an agent loop — here you are packaging reusable expertise, not wiring tools into a runtime.
Overview
A Skill is a folder containing a SKILL.md file: YAML frontmatter (name + description) followed by markdown instructions. Optionally it bundles extra markdown reference files and executable scripts. Claude discovers skills automatically and pulls each one into context only when relevant — so you can install dozens of skills for roughly 100 tokens each until one actually fires.
The mechanism that makes this cheap is progressive disclosure, three levels of loading:
- Metadata (always loaded) — the
nameanddescriptionfrom every skill's frontmatter sit in the system prompt. This is all Claude knows by default: that the skill exists and when to use it. - Instructions (loaded when triggered) — when a request matches a skill's
description, Claude reads theSKILL.mdbody off the filesystem (via bash). Only now do the procedures enter context. Keep this under ~500 lines. - Resources (loaded as needed) — bundled reference files (
REFERENCE.md,EXAMPLES.md) are read only when the body points to them; bundled scripts are executed, never read into context (only their output costs tokens). There is no practical limit on bundled content because it costs zero until accessed.
The single highest-leverage thing you write is the description. Claude uses it to choose among potentially 100+ skills, so it must say both what the skill does and when to reach for it — phrased in the third person with the trigger words a user would actually type.
Official Documentation
| Source | URL | What it covers |
|---|---|---|
| Agent Skills overview | https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview | What a Skill is, frontmatter fields, the 3 progressive-disclosure levels, where Skills run |
| Authoring best practices | https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices | Writing descriptions, degrees of freedom, bundling scripts, anti-patterns, the authoring checklist |
| Skills in Claude Code | https://code.claude.com/docs/en/skills | .claude/skills/ discovery, full frontmatter reference (allowed-tools, disable-model-invocation, model, paths, …), $ARGUMENTS, dynamic context |
| Skills in the Agent SDK | https://code.claude.com/docs/en/agent-sdk/skills | The skills option ("all" / list / []), settingSources loading, dispatch by /name, confirming load via the init message |
| Use Skills with the API | https://platform.claude.com/docs/en/build-with-claude/skills-guide | The /v1/skills endpoints, the container.skills + code-execution wiring (GA — no beta header) |
| Agent Skills standard | https://agentskills.io | The open cross-tool spec Claude Code now follows; the six portable frontmatter fields |
No package to install. Agent Skills are a file format, not a library — there is nothing to
npm installto author one. Claude Code, the Claude Agent SDK, claude.ai, and the Claude API each readSKILL.mdnatively, and the format is now an open cross-tool standard (agentskills.io). In Claude Code, custom/commandshave merged into skills: a legacy.claude/commands/deploy.mdand a.claude/skills/deploy/SKILL.mdboth create/deployand behave the same way (skills just add a folder for supporting files, richer frontmatter, and automatic model invocation). Loading skills from your own agent runtime is the Claude Agent SDK — see How the Agent SDK loads skills below and the ai-agents guide.
Frontmatter fields
Every SKILL.md opens with YAML between --- markers. Only description is strictly needed for Claude to know when to use a skill; every other field is optional. Six fields are part of the portable Agent Skills standard and work everywhere; the rest are Claude Code extensions.
| Field | Where | Notes |
|---|---|---|
name | portable | Lowercase letters, numbers, hyphens only. Max 64 chars. Cannot contain anthropic, claude, or XML tags (reserved). In Claude Code name is optional — it defaults to the directory name and only sets the display label; the slash command always comes from the directory. On claude.ai / the API it identifies the skill. |
description | portable | Third person, what it does + when to use it — this is the trigger. Max 1024 chars in the portable spec; Claude Code truncates the combined description + when_to_use at 1,536 chars in the skill listing, so put the key use case first. |
allowed-tools | portable | Tools pre-approved (no per-use prompt) for the turn that invokes the skill — the grant clears on your next message. Space- or comma-separated, or a YAML list, e.g. Bash(git add *) Bash(git commit *). Grants, never restricts the pool. |
license / metadata / compatibility | portable | Spec metadata: SPDX license; a free-form metadata map for your own tooling; a compatibility string (≤500 chars). Claude Code accepts but doesn't act on them. |
disable-model-invocation | Claude Code | true = only the human can run it via /name, and its description leaves Claude's context (use for side-effecting workflows like deploy/commit). |
user-invocable | Claude Code | false = only Claude can load it (background knowledge, hidden from the / menu). |
context: fork + agent | Claude Code | context: fork runs the skill in an isolated subagent (background by default; set background: false to block the turn). The separate agent: field picks the subagent type (Explore, Plan, general-purpose, or a custom one). |
disallowed-tools | Claude Code | Tools removed from the pool while the skill is active (e.g. block AskUserQuestion in a background loop). Clears on your next message. |
model / effort | Claude Code | Override the model / effort level for the turn the skill is active (inherit keeps the current model). |
paths | Claude Code | Glob patterns that gate auto-activation — Claude loads the skill only when working on matching files. |
arguments / argument-hint | Claude Code | Declare named positional args for $name substitution and an autocomplete hint. $ARGUMENTS, $0, $1 work without declaration. |
hooks / shell | Claude Code | Register hooks for the session when the skill fires; shell: powershell runs !`command` injection via PowerShell instead of bash. |
Portable six. Outside Claude Code — claude.ai uploads, the Skills API, and
package_skill.pyfrom anthropics/skills — onlyname,description,license,compatibility,metadata, andallowed-toolsare accepted; any Claude Code-only field makes packaging/upload hard-fail with an "unexpected key" error. Keep a skill you intend to publish to those surfaces on the portable six.
The reserved-word rule matters here: a skill about Claude Skills themselves cannot be named claude-skills. Name it after the activity instead — see the worked example below.
Build your first skill — a worked example
We'll build a real codeAmani-relevant skill: normalising Kenyan phone numbers to the 2547XXXXXXXX format Daraja requires. This is a perfect skill candidate — it's a fiddly, deterministic rule you'd otherwise paste into chat every time, and it has a clear "when to use" trigger.
(a) Directory layout
A skill is a directory; SKILL.md is the entrypoint. We bundle one helper script and one reference file to demonstrate progressive disclosure:
.claude/skills/normalising-mpesa-phones/
├── SKILL.md # always-discoverable metadata + concise instructions
├── REFERENCE.md # the full edge-case table (loaded only when needed)
└── scripts/
└── normalise.py # deterministic normaliser (executed, never read)Use gerund-form names (
normalising-mpesa-phones), forward slashes always, and descriptive filenames — neverdoc2.md.
(b) SKILL.md frontmatter — the description is the trigger
The description is the only thing loaded until the skill fires, so phrase it with when-to-use cues (the words a teammate would actually say):
---
name: normalising-mpesa-phones
description: >-
Normalises Kenyan phone numbers to the 2547XXXXXXXX / 2541XXXXXXXX format the
Daraja (M-Pesa) API requires. Use when formatting a phone number for an STK Push,
a B2C payout, an SMS, or whenever a number arrives as 07.., +2547.., or 2547...
allowed-tools: Bash(python3 *)
---Compare a bad description — description: Helps with phone numbers — which gives Claude no trigger to match on and no idea what "help" means. Be specific; include key terms (Daraja, STK Push, 07.., +254).
(c) The body — concise instructions
Assume Claude is already smart. State the rule and the canonical path; don't explain what a phone number is:
# Normalising M-Pesa phone numbers
Daraja rejects anything that is not `254` followed by 9 digits (e.g. `254712345678`).
Normalise every number to that shape **before** calling any Daraja endpoint.
## Rules
- Strip spaces, hyphens, and a leading `+`.
- `07XXXXXXXX` or `01XXXXXXXX` → replace the leading `0` with `254`.
- `7XXXXXXXX` / `1XXXXXXXX` (9 digits, no prefix) → prepend `254`.
- Already `2547…` / `2541…` (12 digits) → leave as-is.
- Anything else → reject; do not guess.
## Use the bundled script (preferred — deterministic)
Run it rather than reimplementing the rule:
python3 ${CLAUDE_SKILL_DIR}/scripts/normalise.py "0712 345 678"
# → 254712345678
For the full edge-case table (Safaricom vs Airtel prefixes, invalid lengths),
see [REFERENCE.md](REFERENCE.md).Note the two progressive-disclosure links: REFERENCE.md is read only if Claude needs the edge cases, and normalise.py is executed (its source never enters context). Keep references one level deep — link every supporting file directly from SKILL.md, never a chain of a.md → b.md → c.md.
(d) The bundled script — deterministic, self-contained
A pre-made script is more reliable than asking Claude to regenerate the regex each time, and it costs zero context until run:
#!/usr/bin/env python3
"""Normalise a Kenyan phone number to Daraja's 2547XXXXXXXX format."""
import re, sys
def normalise(raw: str) -> str:
s = re.sub(r"[\s\-]", "", raw).lstrip("+")
if re.fullmatch(r"0[17]\d{8}", s): # 07.. / 01..
return "254" + s[1:]
if re.fullmatch(r"[17]\d{8}", s): # bare 9-digit
return "254" + s
if re.fullmatch(r"254[17]\d{8}", s): # already canonical
return s
raise ValueError(f"Not a valid Kenyan mobile number: {raw!r}")
if __name__ == "__main__":
print(normalise(sys.argv[1]))(e) allowed-tools — pre-approve just enough
The frontmatter line allowed-tools: Bash(python3 *) lets Claude run the helper without a permission prompt during the turn that fires the skill, while leaving every other tool governed by your normal permission settings. The grant is turn-scoped — it clears when you send your next message, then re-applies each time the skill is invoked again. Grant narrowly — Bash(python3 *), not bare Bash — and use disallowed-tools to remove a tool from the pool for a locked-down skill. For a side-effecting skill (deploy, send money) add disable-model-invocation: true so only a human can fire it. (In an Agent SDK session this frontmatter field is ignored for project/personal skills — pre-approve via the SDK's allowedTools option instead.)
(f) Where it lives and how it's discovered
The same SKILL.md works across every surface; only the install path changes:
| Surface | How to install | Sharing scope |
|---|---|---|
| Claude Code (personal) | ~/.claude/skills/<name>/SKILL.md | all your projects |
| Claude Code (project) | .claude/skills/<name>/SKILL.md (commit it) | this repo (loads from cwd + every parent to the repo root) |
| Claude Code (enterprise) | .claude/skills/<name>/ in the managed-settings directory | every user in the org; overrides personal and project |
| Claude Code (plugin) | <plugin>/skills/<name>/SKILL.md | wherever the plugin is enabled; namespaced plugin:name |
| Claude Agent SDK | the same filesystem folders, gated by settingSources + the skills option | whatever the SDK session's setting sources load |
| Claude API | upload via the /v1/skills endpoints, then reference the skill in container.skills (type/skill_id/version) alongside the code-execution tool | workspace-wide |
| claude.ai | upload a .zip under Settings → Features (code execution must be enabled) | per-user only |
In Claude Code the directory name becomes the slash command (/normalising-mpesa-phones) and the project skill is picked up automatically from .claude/skills/ in the cwd and every parent up to the repo root. A same-named skill at a higher level wins (enterprise > personal > project) and also overrides a bundled skill of that name. Note that a project skill's allowed-tools grant is not gated by workspace trust — Claude Code applies it even in an untrusted -p run — so review the allowed-tools of any skill checked into a repo before running Claude Code there. (Adding a .claude-plugin/plugin.json to a skill folder does require accepting the workspace-trust dialog first.) Custom Skills do not sync across surfaces — a skill uploaded to the API is not on claude.ai, and Claude Code skills are filesystem-only; you can optionally pull skills you enabled on claude.ai into ~/.claude/skills/synced/ with CLAUDE_CODE_SYNC_SKILLS=1.
How progressive disclosure loads a skill
The cost ladder is the whole point: thousands of words of edge-case docs and a dozen scripts sit on disk at zero token cost until the one file a task needs is actually opened. One nuance to design around: once the body loads it stays in context for the rest of the session (Claude Code doesn't re-read the file each turn), so write it as standing guidance, keep it lean, and expect a large skill to be trimmed by auto-compaction. The allowed-tools grant is the exception — that resets every message.
How Claude decides to load a skill
If a skill never triggers, the fix is almost always the description: add the keywords users actually say. If it triggers too eagerly, make the description more specific or set disable-model-invocation: true.
How the Agent SDK loads skills
The Claude Agent SDK reads the same filesystem skills as the CLI — there is no programmatic registration API for skills (unlike subagents, which you can define inline via the agents option). Two knobs govern them:
settingSources/setting_sourcesmust include'user'and/or'project'for skills to load at all. With defaultquery()options both are loaded, so~/.claude/skills/,<cwd>/.claude/skills/, and every parent.claude/skills/up to the repo root are discovered. If you setsettingSourcesexplicitly and omit those sources, no skills load — a common gotcha.skillsscopes which discovered skills Claude may auto-invoke:"all"(default when omitted) enables everything, a list of exact names allows only those, and[]disables auto-invocation. Setting it auto-adds theSkilltool toallowedTools; if you pass an explicittoolslist, include"Skill"yourself.
const options = {
cwd: process.cwd(), // .claude/skills/ here or in a parent
settingSources: ["user", "project"], // required — load skills from disk
skills: "all", // or ["formatting-kes", "normalising-mpesa-phones"]
allowedTools: ["Read", "Write", "Bash"],
};Dispatch a skill directly by putting /<name> in the prompt string — this works even if the name is not in your skills allowlist. Confirm what loaded by reading the init system message: its skills array lists user-invocable skills, and slash_commands lists every dispatchable command (built-ins, bundled skills, your skills, .claude/commands/ files). One SDK-only caveat: for project/personal skills the allowed-tools frontmatter field is ignored — grant those tools through the SDK's allowedTools / allowed_tools option instead. In non-interactive (-p / SDK) runs a context: fork skill always blocks for its result rather than backgrounding.
Authoring checklist
-
descriptionis third-person and states what it does + when to use it, with real trigger keywords. -
nameis lowercase-hyphen, ≤64 chars, and avoids the reserved wordsanthropic/claudeand XML tags (or is omitted in Claude Code to inherit the directory name). -
SKILL.mdbody is under ~500 lines; long material is split into bundled files. - Supporting files are referenced one level deep from
SKILL.md. - Reference files >100 lines start with a table of contents.
- Scripts handle their own errors (don't punt back to Claude) and document any constants.
- File paths use forward slashes; no time-sensitive "before August 2025" text.
-
allowed-toolsgrants narrowly; side-effecting skills setdisable-model-invocation: true. - Tested with the models you'll run it on (Haiku needs more guidance than Opus).
codeAmani notes
- Secrets stay server-side. A skill bundles instructions and scripts, never credentials. The phone-normaliser script takes a number, not a key. If a skill must call Daraja or Supabase, it reads
DARAJA_CONSUMER_SECRET/SUPABASE_SERVICE_ROLE_KEYfrom the environment at runtime — theSKILL.mdand its scripts go in git, so they must contain zero secrets. Audit any third-party skill before trusting it; a malicious one can run code with your permissions. - AI routing stays Claude-primary. Skills are an Anthropic-native capability — there's no provider choice to make. They make Claude a specialist for a repeated task, complementing (not replacing) the house routing: Claude for reasoning/coding, OpenAI for structured output, HuggingFace for open models.
- High-leverage codeAmani skills to build first: the
normalising-mpesa-phonesskill above; aformatting-kesskill (integer KES,Ksh 1,234display, no decimals to Daraja); averifying-mpesa-callbacksskill that encodes the idempotency-on-CheckoutRequestID+ reconciliation rule (see daraja-api and webhooks); and a Swahili-tone copy skill for support replies. Each is a fiddly house convention you currently re-explain — exactly what a skill is for. - Ship them as a project skill. Commit
.claude/skills/<name>/to the repo so every teammate (and every agent in the repo) inherits the convention. For org-wide reuse, package them into a Claude Code plugin'sskills/directory — the same pattern this very tech-stack repo uses to publish its guides.