Documentation
¶
Overview ¶
Package skills is a read-only repository over Agent Skills (https://agentskills.io) — directories that each hold a SKILL.md (YAML frontmatter + Markdown instructions) plus optional bundled resources under references/, assets/, and scripts/.
It exposes Source for List/Lookup/Load and ResourceSource for bundled files read on demand. NewRepository wraps any fs.FS; NewDirectoryRepository confines a real directory. ResourceSource owns skill validation and regular-file checks. ErrSkillNotFound distinguishes an absent skill from a missing resource, so Overlay selects a resource source without loading the winning skill a second time. List and Lookup read bounded metadata; a complete document may still exceed Load's limit or fail its validation. Overlay preserves these disclosure levels.
The package is deliberately minimal: it parses, validates, and serves skill content. It does NOT execute scripts — an agent runs those with its own shell/file tools — and it does NOT know about chat models or tools. The LLM-callable wrapper lives in tools/skills, a thin adapter over ResourceSource.
Index ¶
- Constants
- Variables
- func ReadResource(ctx context.Context, src ResourceSource, name string, resource string, ...) ([]byte, bool, error)
- func ValidateName(name string) error
- type Frontmatter
- type Repository
- func (r *Repository) List(ctx context.Context) (summaries []Summary, err error)
- func (r *Repository) Load(ctx context.Context, name string) (*Skill, error)
- func (r *Repository) Lookup(ctx context.Context, name string) (Summary, error)
- func (r *Repository) OpenResource(ctx context.Context, name, resource string) (fs.File, error)
- type RepositoryConfig
- type ResourceSource
- type Skill
- type Source
- type Summary
Examples ¶
Constants ¶
const ( DefaultMaxRepositoryEntries = 512 DefaultMaxFrontmatterBytes = int64(64 * 1024) DefaultMaxSkillBytes = int64(1024 * 1024) DefaultMaxResourceBytes = int64(1024 * 1024) )
Exported defaults keep constructor behavior visible and overridable.
const SkillFile = "SKILL.md"
SkillFile is the required metadata file at the root of every skill directory.
Variables ¶
var ( ErrInvalidSkill = errors.New("skills: invalid skill") // ErrSkillNotFound identifies an absent skill, separately from a missing // resource in an existing skill. It also matches fs.ErrNotExist. ErrSkillNotFound = fmt.Errorf("skills: skill not found: %w", fs.ErrNotExist) ErrNilSkill = errors.New("skills: skill must not be nil") ErrNilFilesystem = errors.New("skills: filesystem must not be nil") ErrNilSource = errors.New("skills: source must not be nil") ErrNilResourceFile = errors.New("skills: resource source returned a nil file without an error") ErrResourceNotRegular = errors.New("skills: resource must be a regular file") ErrInvalidLimit = errors.New("skills: invalid limit") ErrContentTooLarge = errors.New("skills: content exceeds configured limit") ErrRepositoryLarge = errors.New("skills: repository exceeds configured entry limit") ErrNoFrontmatter = errors.New("skills: SKILL.md must open with a YAML frontmatter block delimited by ---") ErrNameEmpty = errors.New("skills: name must not be empty") ErrNameTooLong = errors.New("skills: name exceeds 64 characters") ErrNameInvalid = errors.New("skills: name must be lowercase alphanumerics joined by single hyphens (no leading, trailing, or consecutive hyphens)") ErrNameMismatch = errors.New("skills: frontmatter name must match the skill directory name") ErrDescriptionEmpty = errors.New("skills: description must not be empty") ErrDescriptionTooLong = errors.New("skills: description exceeds 1024 characters") ErrCompatibilityTooLong = errors.New("skills: compatibility exceeds 500 characters") ErrResourcePath = errors.New("skills: resource path escapes the skill directory") )
Functions ¶
func ReadResource ¶
func ReadResource( ctx context.Context, src ResourceSource, name string, resource string, maxBytes int64, ) ([]byte, bool, error)
ReadResource reads at most maxBytes from a bundled skill resource. The truncated result is valid content but must not be treated as the complete resource. maxBytes must be positive.
func ValidateName ¶
ValidateName reports whether name satisfies the Agent Skills specification. It is useful at boundaries that only carry a skill identifier and should not need to fabricate a Frontmatter value to validate it.
Types ¶
type Frontmatter ¶
type Frontmatter struct {
// Name is the unique skill identifier; it must match the skill's parent
// directory name. Required.
Name string `yaml:"name"`
// Description states what the skill does and when to use it — the text an
// agent reads to decide relevance. Required.
Description string `yaml:"description"`
// License names the license, or a bundled license file. Optional.
License string `yaml:"license,omitempty"`
// Compatibility states environment requirements (target product, system
// packages, network access, ...). Optional.
Compatibility string `yaml:"compatibility,omitempty"`
// Metadata is an arbitrary string map for client-defined properties.
Metadata map[string]string `yaml:"metadata,omitempty"`
// AllowedTools is a space-separated list of pre-approved tools. Optional
// and experimental; this package parses but does not enforce it.
AllowedTools string `yaml:"allowed-tools,omitempty"`
}
Frontmatter is the YAML metadata block at the head of a SKILL.md file, as defined by the Agent Skills specification.
func (Frontmatter) AllowedToolList ¶
func (f Frontmatter) AllowedToolList() []string
AllowedToolList splits the space-separated allowed-tools field into its entries. The field is experimental and advisory — this package neither interprets nor enforces it; the splitter is offered for callers that do.
func (Frontmatter) Validate ¶
func (f Frontmatter) Validate() error
type Repository ¶
type Repository struct {
// contains filtered or unexported fields
}
Repository is a read-only Agent Skills repository backed by an fs.FS. Reads are lazy and per-call, so changes to the backing filesystem are visible without a refresh operation.
func NewDirectoryRepository ¶
func NewDirectoryRepository(root string, config RepositoryConfig) (*Repository, error)
NewDirectoryRepository roots the filesystem at a directory so a skill cannot escape it through a relative path, which matters because skill names reach this layer from untrusted bundles.
func NewRepository ¶
func NewRepository(fsys fs.FS, config RepositoryConfig) (*Repository, error)
NewRepository takes an fs.FS rather than a path so a skill set can come from an embedded bundle, an archive, or a test fixture without a temporary directory. Limits are resolved here because an unbounded repository would let a malformed skill exhaust memory during discovery, before any skill runs.
Example ¶
package main
import (
"context"
"fmt"
"testing/fstest"
"github.com/Tangerg/scope/skills"
)
func main() {
repository, err := skills.NewRepository(fstest.MapFS{
"review/SKILL.md": {Data: []byte("---\nname: review\ndescription: Review code.\n---\nRead the code before suggesting changes.")},
}, skills.RepositoryConfig{})
if err != nil {
panic(err)
}
// Lookup checks metadata for an already known name without reading its body.
if _, lookupErr := repository.Lookup(context.Background(), "review"); lookupErr != nil {
panic(lookupErr)
}
summaries, err := repository.List(context.Background())
if err != nil {
panic(err)
}
skill, err := repository.Load(context.Background(), summaries[0].Name)
if err != nil {
panic(err)
}
fmt.Println(skill.Name, skill.Instructions)
}
Output: review Read the code before suggesting changes.
func (*Repository) List ¶
func (r *Repository) List(ctx context.Context) (summaries []Summary, err error)
List returns a summary for every valid skill directory, sorted by name. Invalid skill entries are skipped. Repository access failures are returned. A missing root directory is treated as an empty repository.
func (*Repository) Lookup ¶ added in v0.19.0
Lookup reads only bounded frontmatter. The named directory owns the bundle even when SKILL.md is missing or its metadata is malformed.
func (*Repository) OpenResource ¶
OpenResource opens a file bundled under a skill. The resource path is resolved relative to the skill directory. Lexical traversal is rejected; repositories returned by NewDirectoryRepository also reject symlink escapes.
type RepositoryConfig ¶
RepositoryConfig bounds repository discovery and skill-document reads. Zero fields select the package defaults.
type ResourceSource ¶
type ResourceSource interface {
Source
// OpenResource opens one bundled resource beneath the exact skill root. It
// must reject absolute paths, traversal, and symlink escape according to the
// source's trust boundary and return a non-nil regular file on success.
// The source validates the owning skill in this same operation. An absent
// skill returns ErrSkillNotFound; a missing resource in an existing skill
// returns fs.ErrNotExist without ErrSkillNotFound. Failed opens close any
// acquired file; successful callers own and close the returned file.
OpenResource(ctx context.Context, name, resource string) (fs.File, error)
}
ResourceSource extends Source with progressive-disclosure level 3: opening a resource bundled under a skill directory.
func Overlay ¶ added in v0.21.0
func Overlay(sources ...ResourceSource) ResourceSource
Overlay layers several resource sources into one. Earlier sources take precedence: on a name collision the first source that has the skill wins, so callers express precedence by order (e.g. a project source before a global one). The winning source owns the complete skill bundle; missing resources do not fall through to a lower-precedence copy with the same name. Discovery resolves name ownership through bounded metadata lookups: invalid higher-precedence metadata never advertises a lower-precedence copy.
Nil and typed-nil sources are dropped. Overlay of none yields an empty source (List returns nothing, Load reports not found).
type Skill ¶
type Skill struct {
Frontmatter
Instructions string
}
Skill is a fully loaded skill: its frontmatter metadata plus the Markdown instruction body. Bundled resource files (references/, assets/, scripts/) are not loaded here — they are opened on demand via ResourceSource, the third level of progressive disclosure.
type Source ¶
type Source interface {
// List returns detached, valid summaries in the implementation's stable
// discovery order. Invalid skill bundles may be skipped, but repository I/O,
// permission, and context failures must be returned rather than disguised as
// an empty source.
List(ctx context.Context) ([]Summary, error)
// Lookup checks one name's ownership and metadata without loading its
// instructions or resources. An absent bundle returns ErrSkillNotFound;
// invalid metadata returns ErrInvalidSkill. Access and cancellation errors
// remain distinct from both. A valid summary does not guarantee that the
// full document fits Load's limits or passes its validation.
Lookup(ctx context.Context, name string) (Summary, error)
// Load validates and returns one complete skill by exact name. The caller owns
// the returned value. An absent skill returns ErrSkillNotFound; malformed
// bundles, I/O errors, and cancellation must not be classified as absence.
Load(ctx context.Context, name string) (*Skill, error)
}
Source is the read-only repository that discovers and loads skills. Its operations preserve progressive disclosure, so a consumer pulls in only as much as a task needs:
- List — name + description for every skill (level 1)
- Lookup — name + description for one exact skill (level 1)
- Load — one skill's full instructions (level 2)
Implementations must return valid Summary and Skill models and honor ctx cancellation. Cancellation errors preserve both context.Canceled or context.DeadlineExceeded and any custom cancellation cause through errors.Is. Concurrent I/O and cleanup failures remain identifiable through errors.Is.