Documentation
¶
Overview ¶
Package skill loads invokable playbooks ("skills") from Markdown files. A skill is a named, described prompt body the model can invoke via the run_skill tool (or the user via "/<name>"): an "inline" skill folds its body into the turn as a tool result, a "subagent" skill runs in an isolated child loop and returns only its final answer. Project scope wins over global; only names+descriptions enter the cache-stable system-prompt index (see index.go) — bodies load on demand. Discovery scans several conventions (.fairpeer / .agents / .agent / .claude under the project root and the home dir — see config.ConventionDirs) so skills authored for other agent tools migrate in unchanged, and follows symlinks, so a linked skill directory or flat <name>.md is picked up like a real one.
Index ¶
- Constants
- func ApplyIndex(basePrompt string, skills []Skill) string
- func BuiltinNames() []string
- func BuiltinSubagentTools(store *Store, runner SubagentRunner, profileResolver ...ProfileResolver) []tool.Tool
- func IsValidName(name string) bool
- func NewInstallSkillTool(store *Store, onInstalled InstalledHook) tool.Tool
- func NewReadSkillTool(store *Store) tool.Tool
- func NewRunSkillTool(store *Store, runner SubagentRunner, profileResolver ...ProfileResolver) tool.Tool
- func NewRunSkillToolWithIndex(store, allStore *Store, runner SubagentRunner, ...) tool.Tool
- func Render(sk Skill, args string) string
- func SetExtraReadTools(names []string)
- type InstalledHook
- type Options
- type PathStatus
- type ProfileResolver
- type Root
- type RunAs
- type Scope
- type Skill
- type Store
- func (s *Store) Create(name string, scope Scope) (string, error)
- func (s *Store) CreateWithContent(name string, scope Scope, content string) (string, error)
- func (s *Store) HasProjectScope() bool
- func (s *Store) List() []Skill
- func (s *Store) Read(name string) (Skill, bool)
- func (s *Store) Roots() []Root
- func (s *Store) Usage() *UsageTracker
- type SubagentRunOptions
- type SubagentRunner
- type UsageTracker
Constants ¶
const ( // SkillsDirname is the directory under each root that holds skills. SkillsDirname = "skills" // SkillFile is the canonical filename inside a directory-layout skill. SkillFile = "SKILL.md" )
const IndexMaxChars = 4000
IndexMaxChars caps the pinned skills-index block so it can't bloat the cache-stable system-prompt prefix; bodies never enter the prefix. (Some providers currently do not report cache tokens; the prefix stability still helps.)
Variables ¶
This section is empty.
Functions ¶
func ApplyIndex ¶
ApplyIndex appends the skills index to basePrompt, or returns it unchanged when there are no skills. Only names + descriptions (+ a subagent tag) are listed; bodies load on demand via run_skill.
func BuiltinNames ¶
func BuiltinNames() []string
BuiltinNames returns the built-in skill names, used by callers that wire dedicated subagent tools for the subagent built-ins.
func BuiltinSubagentTools ¶
func BuiltinSubagentTools(store *Store, runner SubagentRunner, profileResolver ...ProfileResolver) []tool.Tool
BuiltinSubagentTools returns top-level wrapper tools for the built-in subagent skills, named after the verb so the model picks them naturally (affordance > prompt rules). Each is skipped when its underlying skill isn't present (e.g. a user disabled it), so the tool set never advertises a phantom skill.
func IsValidName ¶
IsValidName reports whether name is a usable skill identifier.
func NewInstallSkillTool ¶
func NewInstallSkillTool(store *Store, onInstalled InstalledHook) tool.Tool
NewInstallSkillTool builds the skill-authoring tool. onInstalled may be nil.
func NewReadSkillTool ¶
NewReadSkillTool builds a read-only inline-skill loader. Unlike run_skill it stays available in plan mode, so a plan can consult inline playbooks.
func NewRunSkillTool ¶
func NewRunSkillTool(store *Store, runner SubagentRunner, profileResolver ...ProfileResolver) tool.Tool
NewRunSkillTool builds the general skill-invocation tool. runner may be nil (subagent skills then error).
func NewRunSkillToolWithIndex ¶
func NewRunSkillToolWithIndex(store, allStore *Store, runner SubagentRunner, profileResolver ...ProfileResolver) tool.Tool
RunSkillToolWithIndex is the Option pattern entrypoint; see NewRunSkillTool. allStore may be nil (disables the "disabled vs unknown" hint).
func Render ¶
Render builds a skill's invocation text: a header (name, description, source) followed by the body and any arguments. Used directly when a user invokes a skill via "/<name>" (sent as a turn); the run_skill tool wraps the same text in a skill-pin sentinel (see renderInline).
func SetExtraReadTools ¶
func SetExtraReadTools(names []string)
SetExtraReadTools registers additional read-only tool names that subagent skills (explore, research, review, security-review) are allowed to use. Call from boot after plugin tools are registered.
Types ¶
type InstalledHook ¶
InstalledHook fires after install_skill writes a new file, so a host can refresh UI (e.g. a skills sidebar) without a reload. nil is fine.
type Options ¶
type Options struct {
HomeDir string
ProjectRoot string
CustomPaths []string
ExcludedPaths []string
DisabledNames []string
MaxDepth int
DisableBuiltins bool // suppress shipped built-ins (test-only knob)
// Stderr is the writer for diagnostic warnings. When nil, defaults to
// os.Stderr. Set to io.Discard to suppress output (e.g. during model
// switch inside a bubbletea session).
Stderr io.Writer
// StateDir is the directory holding skill_usage.json (cross-session usage
// tracking for cold-skill retirement). Empty disables usage tracking —
// Store.Usage() then returns a no-op tracker.
StateDir string
// LegacyStatePath is a pre-profile-partition fallback location read when
// StateDir's file is absent, so usage recorded before the session dir moved
// isn't lost. Empty = no fallback.
LegacyStatePath string
}
Options configure a Store. ProjectRoot "" reads only the global + custom scopes. HomeDir "" resolves to the OS home dir (tests point it at a tmpdir).
type PathStatus ¶
type PathStatus string
PathStatus describes a root directory's readability, surfaced by `/skill paths`.
const ( StatusOK PathStatus = "ok" StatusMissing PathStatus = "missing" StatusNotDirectory PathStatus = "not-directory" StatusUnreadable PathStatus = "unreadable" )
type ProfileResolver ¶
ProfileResolver returns the model/effort profile a subagent skill will use. It is optional; without one, skill frontmatter still supplies display metadata.
type Root ¶
type Root struct {
Dir string
Scope Scope
Priority int
Status PathStatus
}
Root is one discovery directory with its scope, priority, and status.
type RunAs ¶
type RunAs string
RunAs selects how an invoked skill executes. Inline folds the body into the parent turn; subagent spawns an isolated child loop and returns only the final answer (its tool calls and reasoning never enter the parent context).
type Scope ¶
type Scope string
Scope records where a skill was loaded from. Higher-priority scopes win on a name collision: project > custom > global > builtin.
type Skill ¶
type Skill struct {
Name string // canonical identifier; matches the directory / filename stem
Description string // one-liner shown in the pinned index
Body string // full markdown body (post-frontmatter), loaded eagerly
Scope Scope // where it came from
Path string // absolute path to the SKILL.md / <name>.md, or "(builtin)"
// AllowedTools, when non-empty, scopes a subagent skill's tool registry to
// these literal tool names (from the `allowed-tools` frontmatter).
AllowedTools []string
RunAs RunAs // inline | subagent
Model string // optional model override for runAs=subagent (frontmatter `model:`)
Effort string // optional effort for runAs=subagent (frontmatter `effort:`)
// Disabled marks a skill the user turned off. It stays in the pinned skills
// index (so the model knows it exists and can suggest re-enabling) but is
// not callable via run_skill until re-enabled. Set by callers building the
// index from the full (unfiltered) store; the filtered store still omits
// disabled skills entirely, keeping them uncallable.
Disabled bool
// ProfileHidden marks a skill the active product profile's whitelist hides
// (e.g. the dev/coding profile hides office skills), NOT a user choice.
// Unlike Disabled, a profile-hidden skill is fully OMITTED from the pinned
// index: the model neither sees it nor suggests re-enabling it, since the
// user didn't turn it off — switching profile brings it back automatically.
// This keeps the coding model's prompt free of office-skill descriptions the
// user never asked to see. It is still uncallable (the live store filters it
// via the profile-disabled name set). Set by callers (boot) when applying a
// profile whitelist; mutually exclusive with Disabled for the same skill.
ProfileHidden bool
// Cold marks a skill whose last use is older than the configured retirement
// threshold, so the index tags it [休眠]. Set by callers (boot) from a
// UsageTracker; built-in skills are never marked cold. Cosmetic only — a
// cold skill is still callable, just de-emphasized in the prompt index.
Cold bool
}
Skill is a loaded playbook.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store resolves skills across the configured roots.
func New ¶
New builds a Store. Relative custom paths and a relative project root are made absolute; "~" in a custom path expands to the home dir.
func (*Store) CreateWithContent ¶
CreateWithContent writes caller-supplied file contents as a canonical <name>/SKILL.md skill, refusing to clobber an existing directory-layout or legacy flat skill of the same name. Returns the written path.
func (*Store) HasProjectScope ¶
HasProjectScope reports whether the store was configured with a project root.
func (*Store) List ¶
List returns every discoverable skill, deduped by name (first/highest-priority root wins) with built-ins folded in last, sorted by name so the prefix index stays stable and cacheable.
func (*Store) Read ¶
Read resolves one skill by name, scanning the roots in priority order then the built-ins. ok is false when no such skill exists or the file is unreadable.
func (*Store) Usage ¶
func (s *Store) Usage() *UsageTracker
Usage returns the skill usage tracker backed by this store's StateDir, or a no-op tracker when StateDir is empty. boot.go uses it to detect cold (long- unused) skills for retirement from the prompt index.
type SubagentRunOptions ¶
SubagentRunner runs a runAs=subagent skill: it spawns an isolated child loop with the skill body as system prompt and `task` as its only input, returning the final answer. boot wires this over the agent's sub-agent machinery; nil means subagent skills are unavailable in this session (they error rather than silently inlining, which would lose the isolation the author asked for).
type SubagentRunner ¶
type UsageTracker ¶
type UsageTracker struct {
// contains filtered or unexported fields
}
UsageTracker records the last-used timestamp of each invoked skill, so long-unused skills can be retired from the prompt index (mirroring memory's dormant/archive decay). It is best-effort throughout: a missing or corrupt state file is not an error — the tracker simply reports "never used", so no skill is retired until real usage data accumulates.
The zero value (path == "") is a no-op: Record/LastUsed/ColdSkillNames all return without touching disk, so callers that don't pass a StateDir (tests, older code paths) are unaffected.
func NewUsageTracker ¶
func NewUsageTracker(stateDir string, legacyPath ...string) *UsageTracker
NewUsageTracker builds a tracker backed by <stateDir>/skill_usage.json. A "" stateDir returns a no-op tracker (all methods are safe but do nothing). legacyPath, when non-empty, is read as a fallback when the primary file is absent — used to recover pre-profile-partition skill usage (the old file sat at <userDir>/skill_usage.json; sessions then moved under <profile>/sessions, shifting the state dir by one level).
func (*UsageTracker) ColdSkillNames ¶
func (u *UsageTracker) ColdSkillNames(threshold time.Duration, includeNeverUsed bool, known []string) []string
ColdSkillNames returns the names of skills whose last use is older than threshold (or that have never been recorded, when includeNeverUsed is true). A skill used within the threshold is excluded. The never-used policy is configurable: built-in skills (explore/research/…) should not be retired just because they haven't been called yet, so callers pass includeNeverUsed only for user-authored skills.
func (*UsageTracker) LastUsed ¶
func (u *UsageTracker) LastUsed(name string) time.Time
LastUsed returns the most recent invocation time of name, or the zero Time when the skill has never been recorded (or tracking is disabled).
func (*UsageTracker) Record ¶
func (u *UsageTracker) Record(name string)
Record logs one invocation of name: bumps the count and stamps LastUsed to now, then writes the file. Best-effort — a write failure is swallowed because losing one usage sample must never break the skill call that triggered it.