Skip to main content

Skills

Skills are reusable instruction modules that extend what an agent can do. Each skill is a directory containing a SKILL.md file with YAML frontmatter and markdown instructions. The agent discovers available skills at startup, advertises their descriptions to the LLM, and loads full skill content on demand via tool call. The skill system follows the Agent Skills Open Standard, a portable specification adopted by 30+ AI tools including Claude Code, OpenAI Codex, Cursor, GitHub Copilot, and others. Skills written for this framework are compatible with those tools and vice versa.

Directory Structure

Each skill lives in its own directory under a skills root:
skills/
├── code-review/
│   ├── SKILL.md           # Main instructions (required)
│   ├── examples/
│   │   └── sample.md      # Example output
│   └── scripts/
│       └── lint.sh         # Helper script
├── deploy/
│   └── SKILL.md
└── api-conventions/
    ├── SKILL.md
    └── references/
        └── openapi.yaml
// @doctest id="b0cf"
Resource folders (scripts/, references/, assets/, examples/) are automatically discovered and listed in the skill’s resources property.

SKILL.md Format

Every skill needs a SKILL.md file with optional YAML frontmatter between --- markers, followed by markdown content:
---
name: code-review
description: Review code for quality, bugs, and best practices
argument-hint: "[file-or-directory]"
license: MIT
---

When reviewing code, check for:

1. Logic errors and edge cases
2. Security vulnerabilities
3. Performance issues
4. Style consistency

Focus on $ARGUMENTS if provided.
# @doctest id="7333"

Frontmatter Fields

Agent Skills Open Standard (portable)

FieldTypeDefaultDescription
namestringdirectory nameSkill name (lowercase, hyphens, max 64 chars)
descriptionstring''What the skill does and when to use it
licensestringnullLicense (e.g. MIT, Apache-2.0)
compatibilitystringnullEnvironment requirements
metadatamap[]Arbitrary key-value pairs
allowed-toolsstring/list[]Space/comma-delimited or YAML list of allowed tools

Cross-platform Extensions

FieldTypeDefaultDescription
disable-model-invocationboolfalsePrevent the model from auto-loading this skill
user-invocablebooltrueWhether to show in user-facing skill listings
argument-hintstringnullHint for expected arguments (e.g. [issue-number])

Execution Context Extensions

FieldTypeDefaultDescription
modelstringnullOverride model when skill is active
contextstringnullSet to fork for subagent execution
agentstringnullSubagent type when context: fork
Unknown frontmatter fields are silently ignored, ensuring forward compatibility.

Setting Up Skills

Creating a SkillLibrary

The SkillLibrary scans a directory for skill subdirectories:
use Cognesy\Agents\Capability\Skills\SkillLibrary;

$library = SkillLibrary::inDirectory(__DIR__ . '/skills');

// List all skills (name + description)
$skills = $library->listSkills();

// Check and load a specific skill
if ($library->hasSkill('code-review')) {
    $skill = $library->getSkill('code-review');
}
// @doctest id="2ca6"
Skills are lazy-loaded: only frontmatter is read during discovery, full content is loaded on first getSkill() call and cached thereafter.

Wiring Into an Agent

The UseSkills capability registers the load_skill tool and injects skill metadata via a hook:
use Cognesy\Agents\Builder\AgentBuilder;
use Cognesy\Agents\Capability\Skills\SkillLibrary;
use Cognesy\Agents\Capability\Skills\UseSkills;

$library = SkillLibrary::inDirectory(__DIR__ . '/skills');

$agent = AgentBuilder::base()
    ->withCapability(new UseSkills($library))
    ->build();
// @doctest id="0caf"
This does two things:
  1. Registers load_skill tool — the LLM can call load_skill(skill_name: "code-review") to load full skill content, or load_skill(list_skills: true) to see available skills.
  2. Injects metadata hookAppendSkillMetadataHook prepends a system message listing skill names and descriptions so the LLM knows what’s available.

Argument Substitution

When loading a skill with arguments, placeholders in the body are replaced:
PlaceholderReplaced with
$ARGUMENTSFull argument string
$ARGUMENTS[N]Nth argument (0-based)
$NShorthand for $ARGUMENTS[N]
If no placeholder is present, arguments are appended as ARGUMENTS: <value>.
---
name: fix-issue
description: Fix a GitHub issue
argument-hint: "[issue-number]"
---

Fix GitHub issue $ARGUMENTS following our coding standards.
# @doctest id="4fdd"
When loaded with load_skill(skill_name: "fix-issue", arguments: "123"), the body becomes “Fix GitHub issue 123 following our coding standards.”

Invocation Control

Two flags control who can invoke a skill:
ConfigurationModel sees itUser sees itUse case
(default)YesYesGeneral-purpose skills
disable-model-invocation: trueNoYesSide-effect workflows (deploy, commit)
user-invocable: falseYesNoBackground knowledge (legacy system context)
---
name: deploy
description: Deploy to production
disable-model-invocation: true
---

Deploy the application:
1. Run tests
2. Build
3. Push to production
# @doctest id="9431"

Components

Skill

Immutable value object holding parsed skill data:
$skill->name;                  // string
$skill->description;           // string
$skill->body;                  // string (markdown content)
$skill->path;                  // string (absolute path to SKILL)
$skill->license;               // ?string
$skill->compatibility;         // ?string
$skill->metadata;              // array<string, string>
$skill->allowedTools;          // list<string>
$skill->disableModelInvocation; // bool
$skill->userInvocable;         // bool
$skill->argumentHint;          // ?string
$skill->model;                 // ?string
$skill->context;               // ?string
$skill->agent;                 // ?string
$skill->resources;             // list<string>

$skill->render();              // Full skill content with XML tags
$skill->render('arg1 arg2');   // With argument substitution
$skill->renderMetadata();      // "[name]: description"
$skill->toArray();             // All non-null fields as array
// @doctest id="bef6"

SkillLibrary

Discovery and lazy-loading of skills from a directory:
$library = SkillLibrary::inDirectory($path);
$library->listSkills();                           // All skills
$library->listSkills(modelInvocable: true);       // Exclude disabled
$library->listSkills(userInvocable: true);        // Exclude background
$library->hasSkill('name');                       // bool
$library->getSkill('name');                       // ?Skill
$library->renderSkillList();                      // Formatted list
// @doctest id="a73b"

LoadSkillTool

Tool exposed to the LLM for loading skills:
load_skill(skill_name: "code-review")              // Load a skill
load_skill(skill_name: "fix-issue", arguments: "123")  // With args
load_skill(list_skills: true)                      // List available
// @doctest id="6830"

AppendSkillMetadataHook

Fires on BeforeStep. Before the first agent step, injects a system message listing available model-invocable skills with their descriptions and argument hints. Skips subsequent steps if already injected.

TrackActiveSkillHook

Fires on AfterToolUse. When load_skill completes successfully, updates the agent state metadata with the loaded skill’s allowed-tools list and model override. Clears these values when a skill without them is loaded.

SkillToolFilterHook

Fires on BeforeToolUse. Enforces allowed-tools restrictions when a skill with an allowed-tools field is active. If the tool being called is not in the list, blocks execution. The load_skill tool itself is never blocked, allowing the agent to switch skills.

SkillModelOverrideHook

Fires on BeforeStep. Checks agent state metadata for an active skill’s model override and applies it by creating a new LLMConfig with the specified model. This allows skills to target specific models (e.g., a coding skill that requires a more capable model).

Shell Preprocessing

Skills can embed shell commands using the !`command` syntax. When a SkillPreprocessor is configured, these patterns are executed and replaced with their output before argument substitution occurs.
---
name: project-info
description: Show project context
---

Project version: !`cat VERSION`
Current branch: !`git branch --show-current`
Recent changes: !`git log --oneline -5`

Review the code in $ARGUMENTS.
# @doctest id="7f7e"
When loaded, the !`...` patterns are replaced with live command output, giving the LLM up-to-date context.

Enabling Preprocessing

Pass a SkillPreprocessor to UseSkills:
use Cognesy\Agents\Capability\Skills\SkillLibrary;
use Cognesy\Agents\Capability\Skills\SkillPreprocessor;
use Cognesy\Agents\Capability\Skills\UseSkills;

$library = SkillLibrary::inDirectory(__DIR__ . '/skills');
$preprocessor = new SkillPreprocessor(
    workingDirectory: getcwd(),  // optional, defaults to cwd
    timeoutSeconds: 10,          // optional, default 10s
);

$agent = AgentBuilder::base()
    ->withCapability(new UseSkills($library, $preprocessor))
    ->build();
// @doctest id="29d0"
Commands that fail or time out are replaced with [error: ...] markers instead of crashing the skill load.

Cross-Platform Compatibility

The portable subset that works across all Agent Skills-compatible tools:
  • name and description in frontmatter
  • Markdown instructions in the body
  • Directory-per-skill layout with SKILL.md entry point
Extension fields (disable-model-invocation, context, model, etc.) are tool-specific. Unknown fields are ignored gracefully by all compliant tools, so skills with extensions remain portable — the extensions simply don’t activate in tools that don’t support them.