Skills
Skills are file-backed capability packs discovered at startup and exposed to the model as:
- lightweight metadata in the system prompt (name + description)
- on-demand content via
read skill://... - optional interactive
/skill:<name>commands
This document covers current runtime behavior in src/extensibility/skills.ts, src/discovery/builtin.ts, src/internal-urls/skill-protocol.ts, and src/discovery/agents-md.ts.
What a skill is in this codebase
A discovered skill is represented as:
namedescriptionfilePath(theSKILL.mdpath)baseDir(skill directory)- source metadata (
provider,level, path)
The runtime only requires name and path for validity. In practice, matching quality depends on description being meaningful.
Required layout and SKILL.md expectations
Directory layout
For provider-based discovery (native/Claude/Codex/Agents/plugin providers), skills are discovered as one level under skills/:
<skills-root>/<skill-name>/SKILL.md
Nested patterns like <skills-root>/group/<skill>/SKILL.md are not discovered by provider loaders.
For skills.customDirectories, scanning uses the same non-recursive layout (*/SKILL.md).
Provider-discovered layout (non-recursive under skills/):
<root>/skills/
├─ postgres/
│ └─ SKILL.md ✅ discovered
├─ pdf/
│ └─ SKILL.md ✅ discovered
└─ team/
└─ internal/
└─ SKILL.md ❌ not discovered by provider loaders
Custom-directory scanning is also non-recursive, so nested paths are ignored unless you point `customDirectories` at that nested parent.SKILL.md frontmatter
Supported frontmatter fields on the skill type:
name?: stringdescription?: stringglobs?: string[]alwaysApply?: boolean- additional keys are preserved as unknown metadata
Current runtime behavior:
namedefaults to the skill directory namedescriptionis required for:- native
.piscesprovider skill discovery (requireDescription: true) skills.customDirectoriesscans viascanSkillsFromDirinsrc/discovery/helpers.ts(non-recursive)
- native
- non-native providers can load skills without description
Discovery pipeline
discoverSkills() in src/extensibility/skills.ts does two passes:
- Capability providers via
loadCapability("skills") - Custom directories via
scanSkillsFromDir(..., { requireDescription: true })(one-level directory enumeration)
If skills.enabled is false, discovery returns no skills.
Built-in skill providers and precedence
Provider ordering is priority-first (higher wins), then registration order for ties.
Current registered skill providers:
native(priority 100) —.piscesuser/project skills viasrc/discovery/builtin.tsclaude(priority 80)- priority 70 group (in registration order):
claude-pluginsagentscodex
Dedup key is skill name. First item with a given name wins.
Source toggles and filtering
discoverSkills() applies these controls:
- source toggles:
enableCodexUser,enableClaudeUser,enableClaudeProject,enablePiscesUser,enablePiscesProject - glob filters on skill name:
ignoredSkills(exclude)includeSkills(include allowlist; empty means include all)
Filter order is:
- source enabled
- not ignored
- included (if include list present)
For providers other than codex/claude/native (for example agents, claude-plugins), enablement currently falls back to: enabled if any built-in source toggle is enabled.
Collision and duplicate handling
- Capability dedup already keeps first skill per name (highest-precedence provider)
extensibility/skills.tsadditionally:- de-duplicates identical files by
realpath(symlink-safe) - emits collision warnings when a later skill name conflicts
- keeps the convenience
discoverSkillsFromDir({ dir, source })API as a thin adapter overscanSkillsFromDir
- de-duplicates identical files by
- Custom-directory skills are merged after provider skills and follow the same collision behavior
Runtime usage behavior
System prompt exposure
System prompt construction (src/system-prompt.ts) uses discovered skills as follows:
- if
readtool is available:- include discovered skills list in prompt
- otherwise:
- omit discovered list
Task tool subagents receive the session's discovered/provided skills list via normal session creation; there is no per-task skill pinning override.
Interactive /skill:<name> commands
If skills.enableSkillCommands is true, interactive mode registers one slash command per discovered skill.
/skill:<name> [args] behavior:
- reads the skill file directly from
filePath - strips frontmatter
- injects skill body as a follow-up custom message
- appends metadata (
Skill: <path>, optionalUser: <args>)
skill:// URL behavior
src/internal-urls/skill-protocol.ts supports:
skill://<name>→ resolves to that skill'sSKILL.mdskill://<name>/<relative-path>→ resolves inside that skill directory
skill:// URL resolution
skill://pdf
-> <pdf-base>/SKILL.md
skill://pdf/references/tables.md
-> <pdf-base>/references/tables.md
Guards:
- reject absolute paths
- reject `..` traversal
- reject any resolved path escaping <pdf-base>Resolution details:
- skill name must match exactly
- relative paths are URL-decoded
- absolute paths are rejected
- path traversal (
..) is rejected - resolved path must remain within
baseDir - missing files return an explicit
File not founderror
Content type:
.md=>text/markdown- everything else =>
text/plain
No fallback search is performed for missing assets.
Skills vs AGENTS.md, commands, tools, hooks
Skills vs AGENTS.md
- Skills: named, optional capability packs selected by task context or explicitly requested
- AGENTS.md/context files: persistent instruction files loaded as context-file capability and merged by level/depth rules
src/discovery/agents-md.ts specifically walks ancestor directories from cwd to discover standalone AGENTS.md files (up to depth 20), excluding hidden-directory segments.
Skills vs slash commands
- Skills: model-readable knowledge/workflow content
- Slash commands: user-invoked command entry points
/skill:<name>is a convenience wrapper that injects skill text; it does not change skill discovery semantics
Skills vs custom tools
- Skills: documentation/workflow content loaded through prompt context and
read - Custom tools: executable tool APIs callable by the model with schemas and runtime side effects
Skills vs hooks
- Skills: passive content
- Hooks: event-driven runtime interceptors that can block/modify behavior during execution
Practical authoring guidance tied to discovery logic
- Put each skill in its own directory:
<skills-root>/<skill-name>/SKILL.md - Always include explicit
nameanddescriptionfrontmatter - Keep referenced assets under the same skill directory and access with
skill://<name>/... - For nested taxonomy (
team/domain/skill), pointskills.customDirectoriesto the nested parent directory; scanning itself remains non-recursive - Avoid duplicate skill names across sources; first match wins by provider precedence