Skip to content
REFERENCE · PUBLIC CONTRACT

Manifest ​

plugin/plugin.yml is the authored source of truth. Schema versions 1 and 2 are closed contracts: every required property must be present, and unknown properties are rejected. Version 1 is frozen for existing skill-only plugins; version 2 adds portable lifecycle hooks and plugin configuration and is the default for new plugins.

Where it lives ​

The manifest sits at the root of the plugin/ directory. Skill source directories may appear at any depth below plugin/skills/:

text
plugin/
├── plugin.yml
├── skills/
│   ├── <flat-skill-id>/
│   │   └── SKILL.md
│   └── projects/
│       └── health-connector/
│           └── read-health/
│               ├── SKILL.md
│               └── references/
│                   └── api.md
└── hooks/
    └── optional-grouping-directory/
        ├── request.mjs
        ├── response.mjs
        └── optional-internal-resources

Skill source layout ​

A directory is a skill root exactly when it directly contains a regular SKILL.md. Its final directory name must equal the id of one skill declared in the manifest. Directories without a direct SKILL.md are transparent grouping directories; empty groups are ignored, and non-empty groups may contain only directories that lead to skills.

Everything below a discovered skill root belongs to that skill. Supporting files therefore keep paths relative to the root, such as references/api.md for read-health above. One skill root cannot sit inside another, and two roots in different groups cannot share a final directory name. Symbolic links remain unsupported.

Grouping is authoring-only. The complete discovered root is retained as the skill's source path, but identity, dependencies, catalogs, provider manifests, and generated output continue to use only the manifest ID. The example above therefore generates skills/read-health/**, not skills/projects/health-connector/read-health/**.

Source discovery reports complete repository-relative paths. Recover from its fatal errors as follows, then rerun validate, check, or compile:

  • Missing source: add a regular SKILL.md below plugin/skills/ in a directory whose final name matches the declared skill ID.
  • Undeclared source: declare the ID matching the discovered directory name, rename the directory to a declared ID, or remove the unintended SKILL.md.
  • Ambiguous source: rename or remove one of the roots that share a final directory name so exactly one root maps to the manifest ID.
  • Overlapping sources: remove or relocate the nested skill root so no directory with SKILL.md sits below another skill root.
  • Unowned source: move the file into a discovered skill root, or replace it with a directory leading to one; grouping directories cannot own files.

The skills/ directory inside every discovered skill root is reserved for the compiler, and an authored one is rejected. See Generated output for what compiling produces from these files.

Identifiers ​

name, every category or skill id, and every reference to them (category_id, skill_id, replacement_skill_id) share one identifier type: lowercase kebab-case matching ^[a-z0-9]+(?:-[a-z0-9]+)*$, at most 64 characters. Underscores and uppercase letters are rejected.

Top-level fields ​

FieldTypeRules
schema_versioninteger1 for the frozen skill-only contract; otherwise 2
providersunique string listEmpty is allowed; IDs match ^[a-z][a-z0-9-]*$
configobjectOptional in v2; validation policy described below
nameidentifierLowercase kebab-case, at most 64 characters
descriptionnon-empty stringDescribes the complete plugin
versionstringSemantic Versioning
authorobjectname required; email and url optional
homepagenon-empty stringPlugin homepage
repositorynon-empty stringSource repository
licensenon-empty stringProject license identifier
keywordsunique string listAt least one item
categoriescategory listAt least one category
skillsskill listAt least one skill
hooksevent-keyed objectOptional in v2; rejected by v1

The schema validates homepage and repository as non-empty strings. Use full HTTPS URLs so generated provider manifests are useful to consumers.

Config ​

Schema v2 accepts one closed, optional plugin-wide configuration object. Schema v1 rejects config.

yaml
config:
  skill_dependency_depth_limit: 3

skill_dependency_depth_limit accepts null or a positive integer. Omitted config, an empty object, an omitted property, and explicit null all normalize to the same immutable unlimited value. An integer is an exclusive boundary: a limit of 3 accepts dependency depths 0, 1, and 2, then rejects a path when its third required_skills edge is reached. The Skill graph guide explains path selection and failures.

Category ​

id, name, and description are all required:

yaml
- id: engineering
  name: Engineering
  description: Skills for repository work.

Every skill category_id must reference a declared category.

Hook ​

Hooks require schema_version: 2. To migrate a valid v1 manifest, change only schema_version to 2; its existing fields remain valid and an omitted hooks field normalizes to an empty object.

The hooks object keys handler lists directly by universal event. Handler paths are relative to plugin/hooks/:

yaml
hooks:
  sessionStart:
    - handler: observability/audit.mjs
  userPromptSubmit:
    - handler: observability/audit.mjs
    - handler: simple-logger/request.mjs
  preToolUse:
    - handler: observability/audit.mjs
  postToolUse:
    - handler: observability/audit.mjs
  permissionDenied:
    - handler: observability/audit.mjs
  subagentStart:
    - handler: observability/audit.mjs
  preCompact:
    - handler: observability/audit.mjs
  stop:
    - handler: simple-logger/response.mjs
    - handler: observability/audit.mjs
  fileChanged:
    - handler: observability/audit.mjs

Each declared event requires a non-empty list of handler objects. Every object contains one normalized .mjs path, every handler must exist below plugin/hooks/, and handlers run in declaration order. Provider-specific matcher configuration is intentionally outside this provider-neutral contract.

CategoryUniversal events
SessionsessionStart, sessionEnd
PromptuserPromptSubmit, userPromptExpansion
ToolpreToolUse, postToolUse, postToolUseFailure
PermissionpermissionRequest, permissionDenied
SubagentsubagentStart, subagentStop
ContextpreCompact, postCompact
Lifecyclestop, stopFailure, notification, setup
FilefileChanged, cwdChanged

Other files inside that directory are loaded as internal resources and copied with the handlers. They are not separate manifest entries. In particular, the hook shape has no required or policies property: per-event provider capability decides whether native output is generated, while policies remain private to handler implementation.

plugin/hooks/.runtime/ is reserved for the compiler-managed dispatcher and cannot contain authored resources.

Skill ​

FieldContract
idIdentifier; needs one matching SKILL.md root
descriptionNon-empty public description
disable_model_invocationOptional v2 boolean; defaults to false
category_idID of a declared category
visibilityinternal or public
statusdraft, active, deprecated, or archived
compilationOptional v2 skill compilation policy
required_skillsDependency list; may be empty

The original six fields remain required. Schema v2 additionally accepts disable_model_invocation and compilation; schema v1 rejects them. deprecation and archive remain lifecycle-specific optional properties.

Set disable_model_invocation: true when a supported host should expose the skill for explicit user invocation without letting its model select the skill automatically. The compiler maps it to disable-model-invocation: true in every generated copy of that skill. See Providers for current host behavior.

Set compilation.markdown_references to inline to merge Markdown files below the skill's references/ directory into generated SKILL.md. The default preserve policy copies them as separate files. The optional <!-- PLUGIN-COMPILER:MARKDOWN-REFERENCES --> marker selects the merge point; without it, reference content is appended in deterministic relative-path order. All non-Markdown references and resources outside references/ remain separate.

Required skill ​

yaml
required_skills:
  - skill_id: inspect-repository
    reason: The plan must reflect verified repository facts.
    instructions: Inspect the repository and pass the facts forward.

skill_id, reason, and instructions are all required. The compiler validates the complete dependency graph, not just the YAML shape; Skill graph lists what it rejects.

Lifecycle detail blocks ​

Status decides which block a skill must carry. The schema rejects the block that does not belong to the declared status.

StatusRequired blockRequired keysOptional key
draft, activenone——
deprecateddeprecationreason, instructionsreplacement_skill_id
archivedarchivereasonreplacement_skill_id
yaml
status: deprecated
deprecation:
  reason: A narrower skill replaces this workflow.
  instructions: Use prepare-focused-change instead.
  replacement_skill_id: prepare-focused-change

A replacement_skill_id must name a declared skill other than the skill itself, and that skill must be both active and public.

Next: apply visibility and status rules, see how fields map into provider output, or run the CLI.

Released under the MIT License.