Agent Skill is a personal variant of the upstream Agent Skills specification: frontmatter-free, XML-mixed Markdown wrapped in a single <skill> element with three fixed-order section tags (<purpose> → <guidelines> → <implementation>). Metadata lives on the root tag as XML attributes instead of YAML frontmatter.
This repository ships two artifacts:
- The specification (
SPECIFICATION.md) - authoritative for format requirements. - A reference parser (
src/, published as@neabyte/agent-skill) - a text-to-data function with no runtime dependencies and no filesystem I/O.
Keep the format small. Every new requirement adds work for every implementation and every skill author. Prefer progressive disclosure: keep details in their natural source (spec, README, parser) instead of duplicating rules here.
SPECIFICATION.md is authoritative for format requirements. Explanatory documentation, examples, tests, and the reference parser do not add requirements to the format. When these surfaces disagree, surface the discrepancy and resolve it at the spec level rather than treating existing parser behavior as normative.
Preserve the distinction between:
- The format - what a valid
SKILL.mdmust contain. - Author choices - content inside
<purpose>,<guidelines>,<implementation>. - Client / tooling choices - how a parser or agent consumes the parsed result.
The reference parser under src/ is small on purpose. It exposes one class Skill with one static method Skill.parse(source: string): SkillResult[]. Refactors that keep behavior identical while improving readability, error messages, or type inference are welcome. Feature additions belong in a Discussion before code.
For agents and CLI tooling that need to load, index, or dispatch skills at runtime, this variant aims to reduce integration cost by removing common sources of friction:
- No frontmatter to handle. No YAML parser, no dual-format edge cases (frontmatter vs. body drift), no
---fence detection. A skill is one XML-mixed Markdown document from start to finish. - Metadata and prompt live together. Root attributes on
<skill>(name,self-invoked,license,version,compatibility, plus custom kebab-case keys) sit inside the same element as the body, so metadata is less likely to drift out of sync with the prompt. Renaming a skill means editing one attribute in the same file the model reads. - Fixed section order.
<purpose>→<guidelines>→<implementation>is required by the spec and enforced by the parser. Downstream tooling can index sections positionally without heuristics. - Word-count guidance (recommended 9-13 words for
<purpose>body, 6-9 words forhintattributes; not enforced by the parser) keeps discovery-time payloads small when fanning out across many skills. - Progressive disclosure. Root attributes +
<purpose>are usually enough to decide whether to activate a skill; the full body loads on activation;assets/,references/,scripts/load on demand. - No runtime dependencies, no filesystem access. The parser is a synchronous string-in / object-out function, so it should run without changes on current versions of Node, Deno, and Bun, and inside sandboxed agent runtimes.
If you are building an agent framework, IDE extension, or CLI orchestrator that needs to enumerate skills and route model calls to the right one, this format is intended to let you do it with a single regex-and-slice pass over each SKILL.md.
npm install @neabyte/agent-skillDeno / Bun / browsers can import directly from a CDN without installing:
import Skill from 'https://esm.sh/@neabyte/agent-skill'
// or
import Skill from 'https://cdn.jsdelivr.net/npm/@neabyte/agent-skill/dist/index.mjs'For CLI integration, ad-hoc validation, or shell pipelines, the parser can be driven inline without a project scaffold. The examples below read from stdin end-to-end.
Node (>=24) - validate a SKILL.md from stdin and print JSON:
cat path/to/SKILL.md | node --input-type=module --eval "
import Skill from '@neabyte/agent-skill'
const chunks = []
for await (const c of process.stdin) chunks.push(c)
const src = Buffer.concat(chunks).toString('utf8')
console.log(JSON.stringify(Skill.parse(src), null, 2))
"Deno - same flow, no install step required:
cat path/to/SKILL.md | deno eval "
import Skill from 'npm:@neabyte/agent-skill'
const src = new TextDecoder().decode(await new Response(Deno.stdin.readable).arrayBuffer())
console.log(JSON.stringify(Skill.parse(src), null, 2))
"Bun - one command, no build step:
cat path/to/SKILL.md | bun -e "
import Skill from '@neabyte/agent-skill'
const src = await Bun.stdin.text()
console.log(JSON.stringify(Skill.parse(src), null, 2))
"Extract only root attributes (fast discovery pass):
cat path/to/SKILL.md | deno eval "
import Skill from 'npm:@neabyte/agent-skill'
const src = new TextDecoder().decode(await new Response(Deno.stdin.readable).arrayBuffer())
for (const entry of Skill.parse(src)) console.log(entry.skill)
"Exit non-zero on invalid skill (CI gate):
node --input-type=module --eval "
import Skill from '@neabyte/agent-skill'
import { readFileSync } from 'node:fs'
try { Skill.parse(readFileSync(process.argv[1], 'utf8')); process.exit(0) }
catch (e) { console.error(e.message); process.exit(1) }
" path/to/SKILL.mdThese snippets are usually enough to wire the parser into a Makefile, pre-commit hook, GitHub Action, or agent bootstrap script without adding a permanent dependency on a build system.
When embedding the parser inside a larger tool:
- Cache parse results by file mtime and size so
Skill.parseruns only once per revision instead of on every model call, since a single regex-and-slice pass is already the discovery-time cost. - Route requests on
skill.namefirst and then onself-invoked, remembering that a skill declared withself-invoked="false"must run only when the user explicitly names it. - Treat every root attribute besides
name,self-invoked,license,compatibility, andversionas an opaque kebab-case string owned by tooling, and refrain from enforcing schemas that the specification does not define. - Preserve the whitespace inside
<purpose>,<guidelines>, and<implementation>exactly as parsed, because the parser only trims one leading and one trailing newline and any further reflow will corrupt the prompt. - Match errors on the tag name embedded in each
SyntaxErrormessage (missing <tag> element,unclosed <tag> element, ornested <skill> element) rather than the full string when rendering user-facing diagnostics. - Assume the parser is synchronous and side-effect free, which means it is safe to invoke inside a worker, an eval sandbox, or a hot request handler without additional isolation.
README.md covers user-facing usage. SPECIFICATION.md is the format contract. CONTRIBUTING.md owns contribution scope, disclosure policy for AI-assisted contributions, and the local development commands (deno task check, deno task test, npm run build). Read CONTRIBUTING.md before opening a PR or Discussion.
Follow CONTRIBUTING.md. Highlights:
- Small, focused PRs. One logical change per PR.
- Verify locally with
deno task checkanddeno task testbefore opening a PR. - Disclose AI assistance in the PR or issue description (trivial fixes exempt).
- Out of scope right now: community skill submissions, frontmatter support, sweeping architectural rewrites.