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/:
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-resourcesSkill 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.mdbelowplugin/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.mdsits 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
| Field | Type | Rules |
|---|---|---|
schema_version | integer | 1 for the frozen skill-only contract; otherwise 2 |
providers | unique string list | Empty is allowed; IDs match ^[a-z][a-z0-9-]*$ |
config | object | Optional in v2; validation policy described below |
name | identifier | Lowercase kebab-case, at most 64 characters |
description | non-empty string | Describes the complete plugin |
version | string | Semantic Versioning |
author | object | name required; email and url optional |
homepage | non-empty string | Plugin homepage |
repository | non-empty string | Source repository |
license | non-empty string | Project license identifier |
keywords | unique string list | At least one item |
categories | category list | At least one category |
skills | skill list | At least one skill |
hooks | event-keyed object | Optional 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.
config:
skill_dependency_depth_limit: 3skill_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:
- 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/:
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.mjsEach 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.
| Category | Universal events |
|---|---|
| Session | sessionStart, sessionEnd |
| Prompt | userPromptSubmit, userPromptExpansion |
| Tool | preToolUse, postToolUse, postToolUseFailure |
| Permission | permissionRequest, permissionDenied |
| Subagent | subagentStart, subagentStop |
| Context | preCompact, postCompact |
| Lifecycle | stop, stopFailure, notification, setup |
| File | fileChanged, 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
| Field | Contract |
|---|---|
id | Identifier; needs one matching SKILL.md root |
description | Non-empty public description |
disable_model_invocation | Optional v2 boolean; defaults to false |
category_id | ID of a declared category |
visibility | internal or public |
status | draft, active, deprecated, or archived |
compilation | Optional v2 skill compilation policy |
required_skills | Dependency 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
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.
| Status | Required block | Required keys | Optional key |
|---|---|---|---|
draft, active | none | — | — |
deprecated | deprecation | reason, instructions | replacement_skill_id |
archived | archive | reason | replacement_skill_id |
status: deprecated
deprecation:
reason: A narrower skill replaces this workflow.
instructions: Use prepare-focused-change instead.
replacement_skill_id: prepare-focused-changeA 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.