Documentation
¶
Overview ¶
Package skills provides types and utilities for loading, parsing, and managing PromptKit skills defined via the AgentSkills.io SKILL.md format.
Index ¶
- Constants
- Variables
- func BuildSkillActivateDescriptor() *tools.ToolDescriptor
- func BuildSkillActivateDescriptorWithIndex(index string) *tools.ToolDescriptor
- func BuildSkillDeactivateDescriptor() *tools.ToolDescriptor
- func BuildSkillReadResourceDescriptor() *tools.ToolDescriptor
- func DefaultSkillsProjectDir() (string, error)
- func DefaultSkillsUserDir() (string, error)
- func IsLocalPath(arg string) bool
- func SkillFilterFromContext(ctx context.Context) stringdeprecated
- func WithActiveSet(ctx context.Context, set *ActiveSet) context.Context
- func WithSkillFilter(ctx context.Context, filter string) context.Contextdeprecated
- type Activation
- type ActiveSet
- type Executor
- func (e *Executor) Activate(name string) (instructions string, addedTools []string, retErr error)deprecated
- func (e *Executor) ActivateIn(set *ActiveSet, name string) (Activation, error)
- func (e *Executor) ActivateWithFilter(name, filter string) (instructions string, addedTools []string, retErr error)deprecated
- func (e *Executor) ActiveSkills() []stringdeprecated
- func (e *Executor) ActiveTools() []stringdeprecated
- func (e *Executor) Deactivate(name string) (removedTools []string, retErr error)deprecated
- func (e *Executor) DeactivateIn(set *ActiveSet, name string) (removedTools []string, retErr error)
- func (e *Executor) OwnActiveSet() *ActiveSetdeprecated
- func (e *Executor) ReadResource(skillName, path string) ([]byte, error)
- func (e *Executor) SetFilter(glob string) []stringdeprecated
- func (e *Executor) SetNewSelector(sel selection.Selector)
- func (e *Executor) SkillIndex(skillsDir string) string
- func (e *Executor) SkillIndexFiltered(ctx context.Context, query, skillsDir string) string
- func (e *Executor) SkillsIn(set *ActiveSet) []string
- func (e *Executor) ToolsFor(set *ActiveSet) []string
- type ExecutorConfig
- type InstalledSkill
- type Installer
- func (inst *Installer) Install(ref SkillRef, projectLevel bool) (string, error)
- func (inst *Installer) InstallInto(ref SkillRef, targetDir string) (string, error)
- func (inst *Installer) InstallLocal(srcPath string, projectLevel bool) (string, error)
- func (inst *Installer) InstallLocalInto(srcPath, targetDir string) (string, error)
- func (inst *Installer) List() ([]InstalledSkill, error)
- func (inst *Installer) Remove(ref SkillRef) error
- func (inst *Installer) Resolve(ref SkillRef) (string, error)
- type ModelDrivenSelector
- type Registry
- func (r *Registry) Discover(sources []SkillSource) error
- func (r *Registry) Has(name string) bool
- func (r *Registry) List() []SkillMetadata
- func (r *Registry) ListForDir(dir string) []SkillMetadata
- func (r *Registry) Load(name string) (*Skill, error)
- func (r *Registry) PreloadedSkills() []*Skill
- func (r *Registry) ReadResource(name, resourcePath string) ([]byte, error)
- func (r *Registry) ResolveRef(ref SkillRef) (string, error)
- type Skill
- type SkillMetadata
- type SkillRef
- type SkillSelector
- type SkillSource
- type TagSelector
- type ToolExecutor
Constants ¶
const ( SkillActivateTool = "skill__activate" SkillDeactivateTool = "skill__deactivate" SkillReadResourceTool = "skill__read_resource" SkillNamespace = "skill" SkillExecutorName = "skill" )
Tool name constants for the skill__ namespace.
Variables ¶
var ErrGitNotFound = errors.New("git executable not found")
ErrGitNotFound reports that no usable git executable could be resolved. Callers can match it with errors.Is to tell "git is missing or untrustworthy" apart from "the git command ran and failed".
var ErrPathTraversal = errors.New("resource path escapes skill directory")
ErrPathTraversal is returned when a resource path attempts to escape the skill directory.
Functions ¶
func BuildSkillActivateDescriptor ¶
func BuildSkillActivateDescriptor() *tools.ToolDescriptor
BuildSkillActivateDescriptor returns the tool descriptor for skill__activate.
func BuildSkillActivateDescriptorWithIndex ¶
func BuildSkillActivateDescriptorWithIndex(index string) *tools.ToolDescriptor
BuildSkillActivateDescriptorWithIndex returns a skill__activate descriptor whose Description includes the available-skills index so the LLM can discover which skills are available.
func BuildSkillDeactivateDescriptor ¶
func BuildSkillDeactivateDescriptor() *tools.ToolDescriptor
BuildSkillDeactivateDescriptor returns the tool descriptor for skill__deactivate.
func BuildSkillReadResourceDescriptor ¶
func BuildSkillReadResourceDescriptor() *tools.ToolDescriptor
BuildSkillReadResourceDescriptor returns the tool descriptor for skill__read_resource.
func DefaultSkillsProjectDir ¶
DefaultSkillsProjectDir returns the project-level skills directory.
func DefaultSkillsUserDir ¶
DefaultSkillsUserDir returns the XDG-compliant user-level skills directory.
func IsLocalPath ¶
IsLocalPath returns true if the argument looks like a local filesystem path rather than a skill reference (starts with "./", "../", or "/").
func SkillFilterFromContext
deprecated
SkillFilterFromContext returns the skill filter from context, or "" if not set.
Deprecated: use ActiveSetFromContext and ActiveSet.Filter. See AltairaLabs/PromptKit#2011.
func WithActiveSet ¶ added in v2.3.0
WithActiveSet attaches a conversation's ActiveSet to the context. The ToolExecutor reads it on each Execute and activates into it, falling back to the Executor's own set when absent.
This is what lets a single skill executor, registered once into a shared tools.Registry, serve concurrent conversations without their activations reaching each other. It mirrors tools.WithMCPRegistry, which exists for the same reason. See AltairaLabs/PromptKit#2011.
func WithSkillFilter
deprecated
WithSkillFilter returns a context with the given skill filter glob pattern.
Deprecated: use WithActiveSet and ActiveSet.SetFilter. A filter is one half of a conversation's skill state and belongs with the other half; this pair only ever covered the filter, and never had a producer anywhere. It is retained so existing callers keep compiling and will be removed in v3. See AltairaLabs/PromptKit#2011.
Types ¶
type Activation ¶ added in v2.3.0
Activation is the result of activating a skill: the instructions to hand the model, and the pack tools the skill adds beyond the prompt's baseline.
type ActiveSet ¶ added in v2.3.0
type ActiveSet struct {
// contains filtered or unexported fields
}
ActiveSet is one conversation's set of active skills, plus the filter restricting what that conversation may activate.
It is the *caller's* state. An Executor is a catalog and a policy — what skills exist, what the pack ceiling is, what activating one would grant — and holds no conversation state of its own, because a single Executor is registered by name into a tools.Registry that keeps exactly one executor per name. A host running concurrent conversations over one registry gives each conversation its own ActiveSet and passes it per call; see WithActiveSet for the tool-dispatch path. See AltairaLabs/PromptKit#2011.
An ActiveSet is safe for concurrent use.
func ActiveSetFromContext ¶ added in v2.3.0
ActiveSetFromContext returns the ActiveSet attached by WithActiveSet, or nil when the context carries none.
func NewActiveSet ¶ added in v2.3.0
func NewActiveSet() *ActiveSet
NewActiveSet returns an empty, unfiltered ActiveSet.
func (*ActiveSet) Filter ¶ added in v2.3.0
Filter returns the glob restricting which skills this set may activate. Empty means all.
func (*ActiveSet) SetFilter ¶ added in v2.3.0
SetFilter restricts which skills this set may activate. Empty means all; "none" (case-insensitive) disables all. Skills already active that no longer match are deactivated, and their names returned, sorted.
Only this set is affected — a workflow state narrowing one conversation's skills cannot narrow another's.
type Executor ¶
type Executor struct {
// contains filtered or unexported fields
}
Executor is the skill catalog and the activation policy: what skills exist, what the pack ceiling is, whether a filter admits a given skill, and what activating one grants.
It holds no per-conversation state. Activation state lives in an ActiveSet the caller owns and passes in — see Executor.ActivateIn — because one Executor is registered by name into a tools.Registry that keeps exactly one executor per name, so a host running concurrent conversations over a shared registry would otherwise share their active skills. The `own` set below backs the deprecated stateful methods only.
func NewExecutor ¶
func NewExecutor(cfg ExecutorConfig) *Executor
NewExecutor creates a new Executor from the given configuration. If Selector is nil, a ModelDrivenSelector is used.
func (*Executor) Activate
deprecated
Activate loads a skill's instructions and returns them.
Deprecated: use Executor.ActivateIn with the conversation's own ActiveSet. This form activates into state held on the Executor, which is shared by every conversation dispatching through the same tools.Registry entry. It will be removed in v3. See AltairaLabs/PromptKit#2011.
func (*Executor) ActivateIn ¶ added in v2.3.0
func (e *Executor) ActivateIn(set *ActiveSet, name string) (Activation, error)
ActivateIn activates a skill into the caller's ActiveSet and returns its instructions and the tools it grants (capped by the pack's tools).
Activation is idempotent: activating an already-active skill returns its instructions again. It fails when the skill does not exist, when the set's filter does not admit it, or when the set is already at the configured max-active limit — a limit that applies per set, so one conversation filling its quota cannot lock another out.
A nil set activates into the Executor's own set, which is the deprecated stateful behavior; pass a real set.
func (*Executor) ActivateWithFilter
deprecated
func (e *Executor) ActivateWithFilter( name, filter string, ) (instructions string, addedTools []string, retErr error)
ActivateWithFilter is like Activate but applies the given filter instead of the executor's default filter.
Deprecated: use Executor.ActivateIn with an ActiveSet whose filter was set by ActiveSet.SetFilter. Passing a filter per call covered only half of the per-conversation state and left the active set shared. It will be removed in v3. See AltairaLabs/PromptKit#2011.
func (*Executor) ActiveSkills
deprecated
ActiveSkills returns the names of currently active skills, sorted.
Deprecated: use Executor.SkillsIn with the conversation's own ActiveSet. It will be removed in v3. See AltairaLabs/PromptKit#2011.
func (*Executor) ActiveTools
deprecated
ActiveTools returns the aggregate set of tools added by all active skills (each capped by pack tools). The result is deduplicated and sorted.
Deprecated: use Executor.ToolsFor with the conversation's own ActiveSet. Wiring this into ProviderConfig.ToolGrants from a shared executor grants one conversation's skill tools to every other conversation — a permission leak, which is what made this the urgent half of AltairaLabs/PromptKit#2011. It will be removed in v3.
func (*Executor) Deactivate
deprecated
func (*Executor) DeactivateIn ¶ added in v2.3.0
DeactivateIn removes a skill from the caller's ActiveSet and returns the tools to drop. A tool is only dropped when no skill still active in that same set needs it.
func (*Executor) OwnActiveSet
deprecated
added in
v2.3.0
OwnActiveSet returns the set backing the deprecated stateful methods.
Deprecated: exists so a host migrating off Executor.Activate can adopt the set it was already implicitly using. New code should construct its own with NewActiveSet. It will be removed in v3.
func (*Executor) ReadResource ¶
ReadResource reads a file from within a skill's directory. Delegates to the underlying registry.
func (*Executor) SetFilter
deprecated
SetFilter sets a glob pattern that restricts which skills can be activated.
Deprecated: use ActiveSet.SetFilter on the conversation's own set. This form mutates state shared by every conversation dispatching through the same tools.Registry entry. It will be removed in v3. See AltairaLabs/PromptKit#2011.
func (*Executor) SetNewSelector ¶
SetNewSelector replaces the external selector after construction. It is safe to call between Send()s; callers typically wire this at capability-registration time based on RuntimeConfig.
func (*Executor) SkillIndex ¶
SkillIndex returns the Phase 1 skill index string for inclusion in the skill__activate tool description. It is a thin wrapper over SkillIndexFiltered with no query, so it preserves historical behavior when no external selector is configured.
func (*Executor) SkillIndexFiltered ¶
SkillIndexFiltered returns the skill index string, optionally narrowed by the configured external Selector. When no selector is configured, or the selector returns an error or an empty result, the full eligible set is returned — PromptKit never crashes a conversation because selection failed.
query is the current-turn context the selector may rank against; when empty, the selector is skipped entirely (there's nothing to rank against, so all eligible skills surface).
type ExecutorConfig ¶
type ExecutorConfig struct {
Registry *Registry
Selector SkillSelector // nil = ModelDrivenSelector
NewSelector selection.Selector // optional external selector for narrowing the skill index
PackTools []string // all tools declared in the pack
MaxActive int // 0 = unlimited
ConfigDir string // base directory for computing relative skill paths in filters
}
ExecutorConfig configures the skill executor.
type InstalledSkill ¶
type InstalledSkill struct {
Org string // e.g., "anthropic"
Name string // e.g., "pdf-processing"
Location string // "user" or "project"
Path string // filesystem path to the skill directory
}
InstalledSkill describes a skill found in user-level or project-level directories.
type Installer ¶
type Installer struct {
UserDir string // ~/.config/promptkit/skills/
ProjectDir string // .promptkit/skills/
GitCloneFunc func(url, dest string) error // injectable for testing
GitCheckout func(dir, ref string) error // injectable for testing
CopyDirFunc func(src, dest string) error // injectable for testing
}
Installer manages installing, removing, listing, and resolving shared skills.
func NewInstaller ¶
NewInstaller creates an Installer with default XDG-compliant paths.
func (*Installer) Install ¶
Install clones a skill from its Git repository into the appropriate directory. If projectLevel is true, installs to .promptkit/skills/; otherwise to user-level. Returns the installation path.
func (*Installer) InstallInto ¶
InstallInto clones a skill from its Git repository directly into a target directory (e.g., a workflow stage directory like ./skills/billing/). The skill is placed at <targetDir>/<name>/. Returns the installation path.
func (*Installer) InstallLocal ¶
InstallLocal copies a skill from a local path into the appropriate directory. Returns the installation path.
func (*Installer) InstallLocalInto ¶
InstallLocalInto copies a skill from a local path directly into a target directory. Returns the installation path.
func (*Installer) List ¶
func (inst *Installer) List() ([]InstalledSkill, error)
List returns all installed skills from both user and project directories.
type ModelDrivenSelector ¶
type ModelDrivenSelector struct{}
ModelDrivenSelector is the default selector — it returns all available skills, letting the model decide which to activate via skill__activate. Effective for up to ~50 skills. Safe for concurrent use.
func NewModelDrivenSelector ¶
func NewModelDrivenSelector() *ModelDrivenSelector
NewModelDrivenSelector creates a new ModelDrivenSelector.
func (*ModelDrivenSelector) Select ¶
func (s *ModelDrivenSelector) Select( _ context.Context, _ string, available []SkillMetadata, ) ([]string, error)
Select returns all skill names from available.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry holds discovered skills and provides access by name and directory.
func (*Registry) Discover ¶
func (r *Registry) Discover(sources []SkillSource) error
Discover scans skill sources and registers all found skills. Sources are processed in order: the first source to register a skill name wins for path/metadata, but a later source may upgrade the preload flag from false to true. This supports the common pattern of a broad `path: skills/` entry followed by narrower per-directory entries that mark specific skills as preload.
func (*Registry) List ¶
func (r *Registry) List() []SkillMetadata
List returns metadata for all registered skills, sorted by name.
func (*Registry) ListForDir ¶
func (r *Registry) ListForDir(dir string) []SkillMetadata
ListForDir returns metadata for skills whose virtual path is under the given directory. If dir is empty, returns all skills. Results are sorted by name.
Matching uses the virtual path (i.e. honoring MountAs), which is what the workflow filter and consumers see. Inline skills are skipped.
func (*Registry) Load ¶
Load returns the full skill by name, reading instructions from disk on demand.
func (*Registry) PreloadedSkills ¶
PreloadedSkills returns fully loaded skills marked with preload: true.
func (*Registry) ReadResource ¶
ReadResource reads a file from within a skill's directory. Returns error if the path escapes the skill directory (path traversal prevention).
type Skill ¶
type Skill struct {
SkillMetadata
Instructions string `json:"instructions"` // Markdown body from SKILL.md
// Path is the consumer-facing skill directory. For sources without
// MountAs this is the real on-disk path. For sources with MountAs it
// is the *virtual* path (MountAs prefix + relative subpath under the
// source dir) — workflow glob filters and ListForDir match against
// this. To read files from the skill, always go through
// Registry.ReadResource; do not treat Path as a filesystem path.
Path string `json:"path"`
}
Skill holds a fully loaded skill — metadata + instructions + path. This is the Phase 2 data — loaded on activation.
func ParseSkillFile ¶
ParseSkillFile parses a SKILL.md file at the given path into a Skill. It extracts YAML frontmatter and markdown body.
type SkillMetadata ¶
type SkillMetadata struct {
Name string `yaml:"name" json:"name"`
Description string `yaml:"description" json:"description"`
License string `yaml:"license,omitempty" json:"license,omitempty"`
Compatibility string `yaml:"compatibility,omitempty" json:"compatibility,omitempty"`
Metadata map[string]string `yaml:"metadata,omitempty" json:"metadata,omitempty"`
AllowedTools []string `yaml:"allowed-tools,omitempty" json:"allowed_tools,omitempty"`
}
SkillMetadata holds the YAML frontmatter from a SKILL.md file. This is the Phase 1 data — loaded at discovery time (~50 tokens per skill).
func ParseSkillContent ¶
func ParseSkillContent(content []byte) (*SkillMetadata, string, error)
ParseSkillContent parses SKILL.md content from bytes. It returns the parsed metadata, the markdown body, and any error.
func ParseSkillMetadata ¶
func ParseSkillMetadata(path string) (*SkillMetadata, error)
ParseSkillMetadata parses only the YAML frontmatter from a SKILL.md file. Used for Phase 1 discovery — avoids loading the full body.
type SkillRef ¶
type SkillRef struct {
Org string // e.g., "anthropic"
Name string // e.g., "pdf-processing"
Version string // e.g., "v1.0.0" (empty = latest)
}
SkillRef represents a parsed skill reference like @org/skill-name@v1.0.0.
func ParseSkillRef ¶
ParseSkillRef parses "@org/name[@version]" into a SkillRef.
type SkillSelector ¶
type SkillSelector interface {
// Select returns the names of skills that should appear in the Phase 1 index.
// The query is typically the latest user message or conversation context.
// Available contains all skills that passed directory filtering for the current state.
Select(ctx context.Context, query string, available []SkillMetadata) ([]string, error)
}
SkillSelector determines which skills from the available set should be presented to the model in the Phase 1 index. Implementations can filter, rank, or transform the available skill list.
type SkillSource ¶
type SkillSource struct {
// For directory-based skills.
// Dir is the real on-disk directory to walk for SKILL.md files. Path is a
// YAML/JSON alias accepted for legacy schemas; if both are set Dir wins.
Dir string `yaml:"dir,omitempty" json:"dir,omitempty"`
Path string `yaml:"path,omitempty" json:"path,omitempty"`
// For inline skills — Name is required and uniquely identifies the
// skill in the registry. Description and Instructions populate the
// in-memory Skill directly; no SKILL.md is read.
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Instructions string `yaml:"instructions,omitempty" json:"instructions,omitempty"`
// MountAs, when set on a directory-based source, presents discovered
// skills to the registry, workflow filters, and Skill.Path consumers
// as if they lived under this virtual directory prefix instead of
// their real on-disk location. ReadResource always uses the real
// path — MountAs only affects display and glob matching, never IO
// or path-containment checks.
//
// Subdirectory structure under Dir is preserved: a skill at
// /foo/agentskills/billing/payments/SKILL.md discovered with
// Dir: "/foo/agentskills" and MountAs: "skills" is exposed as
// virtual path "skills/billing/payments", which means a workflow
// state with `skills: "skills/billing/*"` will match it.
//
// MountAs must be a literal directory prefix — glob metacharacters
// (* ? [) and ".." segments are rejected at Discover time. Setting
// MountAs on an inline source (Name set, Dir empty) is also rejected.
MountAs string `yaml:"mount_as,omitempty" json:"mount_as,omitempty"`
// Preload, when true, causes the skill's instructions to be loaded
// into the system prompt at session start instead of being activated
// on demand via skill__activate. Use sparingly — preloaded skills
// consume context budget every turn. Across duplicate sources Preload
// is OR-combined: a later source can upgrade preload from false to
// true, but never the reverse.
Preload bool `yaml:"preload,omitempty" json:"preload,omitempty"`
}
SkillSource describes one source of skills to register with a Registry.
A source is either:
- directory-based — set Dir (or Path as an alias). The registry walks the directory for SKILL.md files and registers each one found. MountAs and Preload apply.
- inline — set Name, Description, and Instructions. The skill is registered as if it had been read from disk. MountAs is rejected here.
Multiple SkillSources can be passed to Registry.Discover. They are processed in order; the first source to register a given skill name wins for path and metadata. Preload is OR-combined: a later source can upgrade preload from false to true but never the reverse.
func (*SkillSource) EffectiveDir ¶
func (s *SkillSource) EffectiveDir() string
EffectiveDir returns the real on-disk source directory, preferring Dir over the legacy Path alias. Returns the empty string for inline sources. This is always the *real* path; MountAs has no effect here.
type TagSelector ¶
type TagSelector struct {
// contains filtered or unexported fields
}
TagSelector pre-filters skills based on metadata tags. Skills whose Metadata map contains a "tags" key with a comma-separated list are matched against the selector's required tags. A skill is included if it has at least one tag in common with the selector's required tags. If a skill has no "tags" metadata key, it is excluded. Safe for concurrent use.
func NewTagSelector ¶
func NewTagSelector(tags []string) *TagSelector
NewTagSelector creates a TagSelector that matches skills having at least one of the specified tags. Duplicate tags are deduplicated automatically.
func (*TagSelector) Select ¶
func (s *TagSelector) Select( _ context.Context, _ string, available []SkillMetadata, ) ([]string, error)
Select returns the names of skills whose metadata["tags"] contains at least one tag matching the selector's required tags.
type ToolExecutor ¶
type ToolExecutor struct {
// contains filtered or unexported fields
}
ToolExecutor handles execution of skill__ tools by delegating to the Executor.
func NewToolExecutor ¶
func NewToolExecutor(exec *Executor) *ToolExecutor
NewToolExecutor creates a new ToolExecutor wrapping the given skills Executor.
func (*ToolExecutor) Execute ¶
func (e *ToolExecutor) Execute( ctx context.Context, tool *tools.ToolDescriptor, args json.RawMessage, ) (json.RawMessage, error)
Execute dispatches a skill tool call to the appropriate executor method.
Activation and deactivation land in the ActiveSet the caller attached with WithActiveSet, so one ToolExecutor -- which is all a tools.Registry holds per name -- serves concurrent conversations without their active skills reaching each other (#2011).
func (*ToolExecutor) Name ¶
func (e *ToolExecutor) Name() string
Name returns the executor name used for mode matching.