How the Craft Agents Skills System Works: Implementation and Custom Instruction Storage

Custom instructions in Craft Agents are stored in SKILL.md files within skill directories, and the system implements a three-tier loading hierarchy (project > workspace > global) with 5-minute caching.

The skills system in lukilabs/craft-agents-oss provides a modular way to extend agent capabilities through declarative configuration files. Each skill encapsulates custom instructions, metadata, and tool permissions that agents load dynamically at runtime.

Skill Definition and Directory Structure

The SKILL.md File Format

Every skill requires a SKILL.md file located at …/skills/{slug}/SKILL.md. This file uses a dual-section format:

  1. YAML front-matter containing structured metadata (name, description, globs, always-allow tools, icon, required sources)
  2. Plain-text body containing the custom instructions that agents execute

The parseSkillFile function in packages/shared/src/skills/storage.ts handles this parsing, stripping the front-matter and returning the body as the instruction text【/cache/repos/github.com/lukilabs/craft-agents-oss/main/packages/shared/src/skills/storage.ts#L68-L90】.

Directory Locations: Global, Workspace, and Project Levels

The system searches for skills in three distinct locations, merged in priority order:

  • Global: ~/.agents/skills/{slug}/SKILL.md (defined by GLOBAL_AGENT_SKILLS_DIR)
  • Workspace: <root>/skills/{slug}/SKILL.md (returned by getWorkspaceSkillsPath)
  • Project: <projectRoot>/.agents/skills/{slug}/SKILL.md

The loadAllSkills function implements the merge logic, giving priority to project over workspace over global configurations【/cache/repos/github.com/lukilabs/craft-agents-oss/main/packages/shared/src/skills/storage.ts#L16-L30】.

Metadata Model and Type Definitions

The SkillMetadata interface in packages/shared/src/skills/types.ts defines the structured fields available in the YAML front-matter:

interface SkillMetadata {
  name: string;
  description?: string;
  globs?: string[];           // File patterns the skill applies to
  alwaysAllow?: string[];     // Tools always permitted for this skill
  icon?: string;
  requiredSources?: string[]; // Required data sources
}

This metadata drives the LoadedSkill type, which combines the metadata with the instruction body and file system path【/cache/repos/github.com/lukilabs/craft-agents-oss/main/packages/shared/src/skills/types.ts#L9-L28】.

Loading and Caching Skills

loadSkill and loadAllSkills Functions

The storage layer exposes two primary loading functions in packages/shared/src/skills/storage.ts:

  • loadSkill(workspaceRoot, slug): Loads a single skill from the workspace directory, returning a LoadedSkill object or undefined if not found【/cache/repos/github.com/lukilabs/craft-agents-oss/main/packages/shared/src/skills/storage.ts#L78-L81】.

  • loadAllSkills(workspaceRoot, projectRoot): Loads and merges skills from all three tiers (global, workspace, project), implementing the priority hierarchy【/cache/repos/github.com/lukilabs/craft-agents-oss/main/packages/shared/src/skills/storage.ts#L16-L30】.

Priority and Merge Strategy

When loading all skills, the system resolves conflicts using a cascading priority:

  1. Project-level skills override workspace and global definitions
  2. Workspace-level skills override global definitions
  3. Global skills serve as the base layer

This merge strategy ensures that project-specific customizations take precedence while maintaining shared defaults at the workspace or user level.

To optimize performance, results are cached for 5 minutes to avoid repeated disk scans across the three directory locations【/cache/repos/github.com/lukilabs/craft-agents-oss/main/packages/shared/src/skills/storage.ts#L92-L104】.

Runtime Skill Discovery and Prerequisite Enforcement

Parsing Skill Mentions in Chat

When users reference skills in messages using the [skill:slug] syntax, the BaseAgent class in packages/shared/src/agent/base-agent.ts handles detection and resolution:

The extractSkillPaths method parses the user message, identifies all skill mentions, resolves them against loaded skills, and returns:

  • A Map<string, string> mapping skill slugs to absolute SKILL.md file paths
  • The cleaned message with mentions removed【/cache/repos/github.com/lukilabs/craft-agents-oss/main/packages/shared/src/agent/base-agent.ts#L82-L106】

The PrerequisiteManager and Read Directives

Before executing any tools, the system enforces that the agent has read the required skill instructions. The formatSkillDirective method generates a directive instructing the model to read specific SKILL.md files:

// From base-agent.ts
private formatSkillDirective(skillPaths: Map<string, string>): string {
  // Generates: "Please read the following files before proceeding: ..."
}

The PrerequisiteManager registers these files as dependencies that must be satisfied before tool execution, ensuring the model has context from all referenced skills【/cache/repos/github.com/lukilabs/craft-agents-oss/main/packages/shared/src/agent/base-agent.ts#L42-L48】.

Where Custom Instructions Are Stored

Custom instructions reside in the body section of SKILL.md files, located after the YAML front-matter separator (---).

The parseSkillFile function in packages/shared/src/skills/storage.ts handles the extraction:

  • Parses the file content to separate metadata from instructions
  • Returns the front-matter as structured SkillMetadata
  • Returns everything below the second --- as the body string containing the raw instructions【/cache/repos/github.com/lukilabs/craft-agents-oss/main/packages/shared/src/skills/storage.ts#L68-L90】

The agent does not store these instructions in memory or copy them to other locations; it reads them on-demand via the Read tool or cat command when the prerequisite directive instructs the model to do so.

Implementation Example: Loading Skills in Practice

Loading a Single Workspace Skill

import { loadSkill } from '@craft-agent/shared/skills/storage';

// workspaceRoot is the absolute path to your workspace
const slug = 'my-awesome-skill';
const skill = loadSkill(workspaceRoot, slug);

if (skill) {
  console.log('Skill metadata:', skill.metadata);
  console.log('Instruction body:', skill.content);
}

Source: loadSkill in storage.ts【/cache/repos/github.com/lukilabs/craft-agents-oss/main/packages/shared/src/skills/storage.ts#L78-L81】

Loading All Skills with Merge Priority

import { loadAllSkills } from '@craft-agent/shared/skills/storage';

const all = loadAllSkills(workspaceRoot, projectRoot);
all.forEach(s => console.log(`${s.slug}: ${s.metadata.name}`));

Source: loadAllSkills implementation【/cache/repos/github.com/lukilabs/craft-agents-oss/main/packages/shared/src/skills/storage.ts#L16-L30】

Agent Message Processing with Skill Directives

// Inside a subclass of BaseAgent, the chat() entry point runs:
const { skillPaths, cleanMessage } = this.extractSkillPaths(userMessage);

// skillPaths is a Map<string, string>(slug → absolute SKILL.md path)
const directive = this.formatSkillDirective(skillPaths);
const effectiveMessage = `${directive}\n\n${cleanMessage}`;

// The backend then sends `effectiveMessage` to the LLM.

Source: extractSkillPaths and formatSkillDirective in base-agent.ts【/cache/repos/github.com/lukilabs/craft-agents-oss/main/packages/shared/src/agent/base-agent.ts#L82-L106】【/cache/repos/github.com/lukilabs/craft-agents-oss/main/packages/shared/src/agent/base-agent.ts#L42-L48】

Sample SKILL.md File Structure

---
name: "Git Helper"
description: "Provides git commit, branch, and diff utilities."
globs: ["src/**/*.ts"]
alwaysAllow: ["Read", "Write"]
icon: "🔧"
requiredSources: ["git"]
---

You are a git assistant. When invoked, you should:
1. List the current branch.
2. Show the diff of staged changes.
3. Offer to commit with a conventional commit message.

The front-matter populates SkillMetadata; everything below the --- separator becomes the instruction body that the model reads at runtime.

Summary

  • Custom instructions reside in the body of SKILL.md files, parsed by parseSkillFile in packages/shared/src/skills/storage.ts.
  • Storage locations follow a three-tier hierarchy: project (.<project>/.agents/skills), workspace (<root>/skills), and global (~/.agents/skills), merged by loadAllSkills with project taking precedence.
  • Runtime integration occurs through BaseAgent in packages/shared/src/agent/base-agent.ts, which detects [skill:slug] mentions via extractSkillPaths and enforces prerequisites using formatSkillDirective.
  • Caching prevents redundant disk operations by caching skill loads for 5 minutes.
  • Metadata structure is defined by SkillMetadata in packages/shared/src/skills/types.ts, supporting fields like alwaysAllow, globs, and requiredSources.

Frequently Asked Questions

Where are custom instructions stored in the Craft Agents skills system?

Custom instructions are stored in the body section of SKILL.md files, located after the YAML front-matter. The parseSkillFile function in packages/shared/src/skills/storage.ts extracts this content by separating the metadata from the instruction text, returning the body as a raw string that agents read at runtime.

How does the skill loading priority work when multiple locations define the same skill?

The loadAllSkills function implements a cascading merge strategy where project-level skills override workspace and global definitions, and workspace-level skills override global ones. Specifically, the system loads from ~/.agents/skills (global), <root>/skills (workspace), and .<project>/.agents/skills (project), merging them in that order with later sources taking precedence.

What is the SKILL.md file format and what metadata does it support?

SKILL.md uses a dual-section format with YAML front-matter followed by free-form instructions. The front-matter conforms to the SkillMetadata interface defined in packages/shared/src/skills/types.ts, supporting fields including name, description, globs (file patterns), alwaysAllow (permitted tools), icon, and requiredSources. Everything below the second --- separator becomes the instruction body.

How do I reference a skill in a chat message and how does the agent process it?

Users reference skills using the [skill:slug] syntax in chat messages. The BaseAgent class in packages/shared/src/agent/base-agent.ts processes these mentions through the extractSkillPaths method, which returns a map of slugs to absolute SKILL.md paths. The agent then uses formatSkillDirective to generate a prerequisite instruction forcing the model to read those files before executing any tools, ensuring the custom instructions are available in context.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →