class LittleGhost::Skills::Catalog
A Catalog lets an agent discover focused instructions without putting every skill in its prompt. The model sees short descriptions first and can load a skill’s full instructions when the task calls for them.
catalog = LittleGhost::Skills::Catalog.new(paths: ["app/skills"]) catalog.names # => ["refund_policy", "search_orders"] catalog.discovery_prompt.include?("refund_policy") # => true catalog.tool # a LittleGhost::Tool that loads full instructions on demand
Each immediate child directory may contain one SKILL.md with YAML front matter. Symbolic-link escapes, unsafe names, oversized files, and invalid YAML are rejected or skipped before instructions reach a model. Optional resource listings are limited by count and depth.
Security and trust
Configured roots and their contents are fully trusted instruction sources. The allowed-tools field is metadata shown to the model, not an authorization boundary. Applications must enforce tool access separately and keep skill roots non-user-writable.
Public Class Methods
# File lib/little_ghost/skills/catalog.rb, line 43 def initialize( paths:, max_skills: DEFAULT_MAX_SKILLS, max_file_bytes: DEFAULT_MAX_FILE_BYTES, max_resource_files: DEFAULT_MAX_RESOURCE_FILES, only: nil, resource_root: nil ) @paths = PathSet.new(paths) @max_skills = positive_integer(max_skills, :max_skills) @max_file_bytes = positive_integer(max_file_bytes, :max_file_bytes) @max_resource_files = positive_integer(max_resource_files, :max_resource_files) @only = Array(only).map(&:to_s).freeze if only @resource_root = canonical_resource_root(resource_root) @skills = load_skills end
Loads valid skills immediately using the supplied safety limits.
Public Instance Methods
Source
# File lib/little_ghost/skills/catalog.rb, line 76 def discovery_prompt return "" if @skills.empty? lines = ["<available_skills>"] @skills.each_value do |skill| lines.concat([ "<skill>", "<name>#{ERB::Util.html_escape(skill.name)}</name>", "<description>#{ERB::Util.html_escape(skill.description)}</description>", "<location>#{ERB::Util.html_escape(skill.path)}</location>", "</skill>" ]) end lines << "</available_skills>" lines.join("\n") end
Produces the escaped, metadata-only prompt used for discovery.
Source
# File lib/little_ghost/skills/catalog.rb, line 61 def each(&block) @skills.each_value(&block) end
Yields each Skill in lookup order.
Source
# File lib/little_ghost/skills/catalog.rb, line 66 def fetch(name) @skills.fetch(name.to_s) { raise ConfigurationError, "Unknown skill: #{name}" } end
Finds the named Skill or raises ConfigurationError.
Source
# File lib/little_ghost/skills/catalog.rb, line 119 def format(skill) parts = [skill.instructions] metadata = [] metadata << "Allowed tools: #{skill.allowed_tools.join(", ")}" unless skill.allowed_tools.empty? metadata << "Compatibility: #{skill.compatibility}" if skill.compatibility metadata << "Location: #{skill.path}" parts << "\n---\n#{metadata.join("\n")}" unless metadata.empty? resources = skill_resources(skill) unless resources.empty? parts << "\nAvailable resources:\n#{resources.map { |path| " #{path}" }.join("\n")}" end parts.join("\n") end
Formats one Skill, including allowed tools, compatibility, and bounded resource paths.
Source
# File lib/little_ghost/skills/catalog.rb, line 71 def names @skills.keys.freeze end
Lists immutable skill names in lookup order.
Source
# File lib/little_ghost/skills/catalog.rb, line 94 def tool catalog = self Tool.define( name: "skills", description: <<~DESCRIPTION.strip, Activate a skill to load its full instructions. Use this tool to load the complete instructions for a skill listed in the available_skills section of your system prompt. DESCRIPTION input_schema: { type: "object", properties: {skill_name: {type: "string", description: "Name of the skill to activate."}}, required: ["skill_name"], additionalProperties: false } ) do |input| catalog.format(catalog.fetch(input.fetch("skill_name"))) rescue ConfigurationError => error raise ToolError, error.message end end
Exposes full instructions on demand through a skills Tool.