Agent Skills: read, check and announce SKILL.md skill directories

README.md

agent-skills #

Read, check, measure and announce Agent Skills in OCaml.

An agent skill is a directory that gives a coding agent instructions for one kind of task. It holds a SKILL.md file, made of YAML frontmatter that names and describes the skill and a Markdown body with the instructions, and may hold scripts/, references/ and assets/ the agent reads on demand. An agent lists the name and description of every skill it has in its system prompt, and loads a skill's body only when the model picks it.

This package reads a SKILL.md and checks it against the specification: the six fields name, description, license, compatibility, metadata and allowed-tools, the sort and length of each, the shape of a name, and a name that is the name of its directory. It measures what each skill costs an agent against the specification's budgets, renders the <available_skills> block that announces skills in a prompt, and the agent-skills command does each of these from the shell.

Installation #

opam install agent-skills

If opam cannot find the package, add the overlay repository first:

opam repo add samoht https://tangled.org/gazagnaire.org/opam-overlay.git
opam update
opam install agent-skills

Libraries #

  • agent-skills, module Agent_skills, reads a SKILL.md given as a string and renders the prompt block. It does no I/O.
  • agent-skills.eio, module Agent_skills_eio, finds the skills of a directory, reads the SKILL.md of each with Eio and checks its name against its directory, and lists the other files a skill holds.

Usage #

Agent_skills.of_string reads the frontmatter through a YAML codec, so a field the specification does not name, a missing one or a value of the wrong sort is an error that says where it is:

# Agent_skills.of_string "---\nname: pdf\ndescription: Read PDFs.\nauthor: me\n---\n";;
- : (Agent_skills.t, [> `Msg of string ]) result =
Error (`Msg "File \"-\", line 4, characters 0-6:\nunexpected key \"author\"")

A valid file gives the fields, and Agent_skills.prompt the block that lists the skill with the location of its SKILL.md:

# let skill =
    Agent_skills.of_string
      "---\nname: pdf\ndescription: Read PDFs & fill forms.\n---\n# PDF\n"
    |> Result.get_ok;;
val skill : Agent_skills.t = <abstr>
# Agent_skills.body skill;;
- : string = "# PDF\n"
# Agent_skills.prompt [ (skill, Fpath.v "/skills/pdf/SKILL.md") ]
  |> Result.get_ok |> print_endline;;
<available_skills>
<skill>
<name>
pdf
</name>
<description>
Read PDFs &amp; fill forms.
</description>
<location>
/skills/pdf/SKILL.md
</location>
</skill>
</available_skills>
- : unit = ()

Budgets #

The specification has an agent load a skill in three steps: the name and description of every skill at startup (about 100 tokens each), the body of the skill the model picks (under 5000 tokens, in a SKILL.md under 500 lines), then the skill's other files on demand. Agent_skills.advice says which of these budgets a skill goes over, with tokens estimated as one per four bytes:

# let long =
    Agent_skills.of_string
      ("---\nname: long\ndescription: A long skill.\n---\n"
       ^ String.make 24_000 'x')
    |> Result.get_ok;;
val long : Agent_skills.t = <abstr>
# List.map (Fmt.str "%a" Agent_skills.pp_advice) (Agent_skills.advice long);;
- : string list =
["its instructions are ~6000 tokens, over the 5000 advised"]

The agent-skills command #

Without a command, agent-skills lists the skills of .claude/skills and ~/.claude/skills, or of the directories it is given, with what each costs:

$ agent-skills --sort instructions
╭───┬────────────────┬──────────┬──────────────┬────────────┬───────┬─────────╮
│   │ Skill          │ Metadata │ Instructions │ of 5000    │ Lines │   Files │
├───┼────────────────┼──────────┼──────────────┼────────────┼───────┼─────────┤
│ ! │ long           │        5 │         6000 │ ██████████ │   604 │       - │
│ ✓ │ pdf-processing │       21 │            5 │ ░░░░░░░░░░ │    10 │ 1 (15B) │
│ ✓ │ code-review    │       11 │            0 │ ░░░░░░░░░░ │     4 │       - │
╰───┴────────────────┴──────────┴──────────────┴────────────┴───────┴─────────╯

3 skills, 1 over a budget.
Every conversation pays ~37 tokens to be offered them;
loading them all would add ~6005 tokens of instructions.
agent-skills list --json skills/      # the fields, sizes and advice as JSON
agent-skills validate skills/         # exits 1 on an invalid skill
agent-skills validate --strict -q .   # and on one over a budget, quietly
agent-skills prompt skills/           # the <available_skills> block

Specification notes #

  • A name is 1 to 64 characters from a-z, 0-9 and -, as the specification's field table lists them. skills-ref also accepts lowercase letters outside ASCII; this package does not.
  • metadata maps strings to strings, so an unquoted version: 1.0 is a float and is refused; quote it as the specification's example does.
  • The description and compatibility limits count Unicode characters, not bytes.
  • SKILL.md is looked up before the lowercase skill.md, which skills-ref also accepts.
  • prompt escapes &, < and > in each value, the location included, and refuses a value holding a character XML 1.0 forbids.
  • skills-ref is the specification's Python reference library and command. Its validate and to-prompt are validate and prompt here, and its read-properties is list --json; its prompt block has the same layout. It does not measure skills against their budgets.
  • claude-skills installs Claude Code skills from marketplaces and finds a skill's SKILL.md with this package.

License #

ISC. See LICENSE.md.