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.
Choosing skill sources
Skill files become model instructions, so keep configured roots under application control and non-user-writable. The allowed-tools field tells the model what a skill expects; the Agent’s Tool list and each Tool’s application checks still decide what can run. For a workspace:// resource root, the Catalog verifies the named read-only grant and rejects direct writable aliases it can identify. LittleGhost cannot identify every alias created by an outer container or mount namespace, so the application must not expose the same files through another writable bind mount.
Public Class Methods
# File lib/little_ghost/skills/catalog.rb, line 50 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, workspace: nil, sandbox: nil, prompt_renderer: 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 = ResourceRoot.normalize(resource_root) @prompt_renderer = prompt_renderer @framework_prompts = FrameworkPrompts.new validate_workspace_resource_root!(workspace, sandbox) @skills = load_skills validate_workspace_resource_aliases!(sandbox) end
Loads valid skills immediately using the supplied safety limits. resource_root may be an absolute process-visible path. A workspace://name reference also requires workspace and sandbox; it must resolve to every configured skill root through a read-only file-tool grant.
Public Instance Methods
Source
# File lib/little_ghost/skills/catalog.rb, line 93 def discovery_prompt return "" if @skills.empty? framework_prompt( "skills/discovery/instructions", skills: @skills.each_value.map do |skill| {name: skill.name, description: skill.description, location: skill.path} end ) end
Produces the Markdown, metadata-only prompt used for discovery.
Source
# File lib/little_ghost/skills/catalog.rb, line 75 def each(&block) @skills.each_value(&block) end
Yields each Skill in lookup order.
Source
# File lib/little_ghost/skills/catalog.rb, line 81 def fetch(name) @skills.fetch(name.to_s) do raise ConfigurationError, framework_prompt("skills/feedback/unknown", name: name.to_s, available: names) end end
Finds the named Skill. When no Skill matches, the ConfigurationError names the available skills so the caller can correct the lookup.
Source
# File lib/little_ghost/skills/catalog.rb, line 130 def format(skill) framework_prompt( "skills/tools/activate/content", instructions: skill.instructions, allowed_tools: skill.allowed_tools, compatibility: skill.compatibility, location: skill.path, resources: skill_resources(skill) ) end
Formats one Skill, including allowed tools, compatibility, and bounded resource paths.
Source
# File lib/little_ghost/skills/catalog.rb, line 88 def names @skills.keys.freeze end
Lists immutable skill names in lookup order.
Source
# File lib/little_ghost/skills/catalog.rb, line 105 def tool catalog = self Tool.define( name: "skills", description: framework_prompt("skills/tools/activate/description"), input_schema: { type: "object", properties: { skill_name: { type: "string", description: framework_prompt("skills/tools/activate/inputs/name/description") } }, 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.