Help us shape the future of Go! Take the 2026 Go Developer Survey

skill

package
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 31, 2026 License: MIT Imports: 32 Imported by: 0

Documentation

Overview

Package skill implements Agent Skills: folders of instructions that a model discovers by name and description and loads on demand.

A skill is a directory containing a SKILL.md file with YAML frontmatter:

---
name: pdf-tools
description: Extract text and tables from PDF files. Use when the user asks about PDFs.
allowed-tools: read_file, bash
---
# PDF tools
...

Only the name and description are placed in the system prompt. The body is returned when the model invokes the skill tool, which keeps the prompt small however many skills are installed.

The frontmatter fields are those of Claude Code (https://code.claude.com/docs/en/skills#frontmatter-reference), a superset of the Agent Skills specification (https://agentskills.io/specification). A Manager gives them effect: arguments and substitutions, optional command injection, context: fork sub-agents, pre-approved and removed tools, model and effort overrides, skill hooks and path-scoped skills.

Index

Constants

View Source
const (
	DefaultShellTimeout = 2 * time.Minute
)

Defaults of command injection, as in Claude Code, whose injected commands share the Bash tool's 2-minute timeout.

View Source
const FileName = "SKILL.md"

FileName is the name of the file that defines a skill.

View Source
const ToolName = "skill"

ToolName is the name of the tool that loads skills.

Variables

View Source
var DefaultFolders = []string{".claude/skills", ".agents/skills"}

DefaultFolders are the skill folders Discover searches in each directory, in precedence order: Claude Code's .claude/skills (https://code.claude.com/docs/en/skills#choose-where-skills-load), then .agents/skills, the interoperable location of Codex (https://developers.openai.com/codex/skills) and Gemini CLI (https://geminicli.com/docs/cli/skills/).

Functions

func Applies

func Applies(s Skill, root string, files []string) bool

Applies reports whether a conditional skill applies to one of files, by its paths, relative to root, and its scope directory. An unconditional skill always applies.

func Conditional

func Conditional(s Skill) bool

Conditional reports whether s is offered to the model only once it works with matching files: it has paths, or it is Scoped.

func FilesOf

func FilesOf(call *model.ToolCall) []string

FilesOf returns the files a tool call reads or edits: the path argument of agentx's read_file, write_file and edit_file tools and the file_path or notebook_path argument of Claude Code's Read, Write, Edit, MultiEdit and NotebookEdit tools, and the files an apply_patch call touches. It is the default Config.Files.

func MatchTool

func MatchTool(root, rule string, call *model.ToolCall) bool

MatchTool reports whether a tool rule of allowed-tools or disallowed-tools, in Claude Code's permission rule syntax (https://code.claude.com/docs/en/permissions), matches a tool call. It is the default Config.MatchTool.

A rule is a tool name, matching every call of the tool, or a name with a specifier in parentheses. Claude Code names select the agentx built-ins with the same role (Bash is bash, Read is read_file, and so on), and a trailing "*" in the name, or an MCP server prefix such as "mcp__github", matches several tools. Specifiers are supported for:

  • Bash: the command, where "*" matches any text and a legacy "prefix:*" any command starting with prefix. A command with shell operators such as &&, ;, | or $( matches no specifier, so a rule cannot approve more than the command it describes.
  • Read, Write, Edit and MultiEdit: a glob of the file path, relative to root unless it starts with "//" (absolute) or "~/" (home).
  • WebFetch: "domain:example.com", which also matches subdomains.

Other specifiers match nothing.

func SkillHooks

func SkillHooks(meta Metadata, opts hook.SettingsOptions) (*hook.Manager, []string, error)

SkillHooks returns a manager with the hooks of a skill's frontmatter, built with opts, and warnings about handlers that cannot run; see hook.SettingsOptions.Parse. Handlers with "once": true run until their first run that neither fails nor blocks.

func Substitute

func Substitute(content string, meta Metadata, dir string, v Vars) string

Substitute expands the placeholders of skill content as Claude Code does (https://code.claude.com/docs/en/skills#available-string-substitutions):

  • $ARGUMENTS is the whole argument string.
  • $ARGUMENTS[N] and $N are the argument at 0-based index N; arguments are split shell-style, so quotes group words. Without an argument at that index the placeholder stays as written.
  • $name is the argument at the position of name in meta.Arguments, or empty.
  • ${CLAUDE_SESSION_ID}, ${CLAUDE_EFFORT}, ${CLAUDE_SKILL_DIR} (dir) and ${CLAUDE_PROJECT_DIR} are replaced when their value is known, also inside inserted arguments, which are not expanded otherwise.

A single backslash before an argument placeholder, as in \$1.00, makes it literal. When arguments were given but no placeholder received them, "ARGUMENTS: <arguments>" is appended.

func WithActivation

func WithActivation(ctx context.Context, s Skill) context.Context

WithActivation returns a context that makes the first runs started in it, such as the sub-agent of a context: fork skill invoked by the current tool call, apply the allowed-tools, disallowed-tools and effort of s, if their agent has the Middleware of a Manager. Their own sub-agents do not. A Forker passes it to the sub-agent's run.

func WithCall

func WithCall(ctx context.Context, c Call) context.Context

WithCall returns a context in which the next run of an agent with the Middleware of a Manager starts with an invocation of a skill by the user, as if the message began with /name arguments in Claude Code: the skill's rendered content is added in front of the run's input and the skill is active for the run. A context: fork skill is instead permitted for the run, and the model is told to invoke it. The run is blocked if the skill is unknown or has user-invocable: false. agent.RunSkill uses it.

Types

type Call

type Call struct {
	Name      string `json:"name"`
	Arguments string `json:"arguments,omitempty"`
}

Call is an invocation of a skill by name, with the arguments a user would type after the name.

type Config

type Config struct {
	// ProjectDir is the project root: the base of paths patterns, of
	// relative file paths and of file rules, the working directory of
	// injected commands, and ${CLAUDE_PROJECT_DIR}. Empty means the
	// process's working directory.
	ProjectDir string

	// Shell enables Claude Code's dynamic context injection: !`command`
	// and “`! blocks in a skill run when the skill is invoked and their
	// output replaces them. It runs shell commands written in skill files
	// with the process's privileges, so enable it only for skills you
	// trust. Without it the placeholders are left as written.
	//
	// Commands run with bash, or sh if bash is missing (cmd.exe on
	// Windows), or with PowerShell for skills with "shell: powershell". A
	// command that exits with a non-zero status, except 1 from search and
	// comparison commands such as grep and diff, fails the invocation.
	// Arguments reach commands through the environment variables
	// ARGUMENTS, ARGUMENTS_0, ARGUMENTS_1, ... and those of declared
	// argument names, and with sh or bash also as $0, $1, ..., so they are
	// never parsed as shell code.
	Shell bool
	// ShellTimeout bounds each injected command; zero means
	// DefaultShellTimeout.
	ShellTimeout time.Duration
	// ShellOutputLimit bounds the output of each injected command in
	// bytes; zero means 30000.
	ShellOutputLimit int

	// Effort is the effort level ${CLAUDE_EFFORT} expands to when no
	// active skill sets one.
	Effort string
	// Fork runs context: fork skills in a sub-agent. Without it they run
	// inline, like other skills.
	Fork Forker
	// Models resolves the model field of an inline skill. A nil result, or
	// a nil Models, sends the name as model.Request.Model to the agent's
	// model instead. "inherit" keeps the current model.
	Models func(name string) model.Model
	// HookOptions builds the hooks of skills; see hook.SettingsOptions.
	HookOptions hook.SettingsOptions
	// Files returns the files a tool call reads or edits, which make
	// skills with paths available; nil means FilesOf.
	Files func(call *model.ToolCall) []string
	// MatchTool reports whether a rule of allowed-tools or disallowed-tools
	// matches a tool call; nil means MatchTool with ProjectDir.
	MatchTool func(rule string, call *model.ToolCall) bool
}

Config configures a Manager. The zero value renders skills with their arguments and substitutions, without command injection, and runs context: fork skills inline.

type Conflict

type Conflict struct {
	Name string
	// Kept and Shadowed are the directories of the skill that is used and
	// of the one that is not.
	Kept, Shadowed string
}

Conflict reports a skill hidden by one of the same name with higher precedence.

type DiscoverConfig

type DiscoverConfig struct {
	// Dir is the working directory; empty means the process's.
	Dir string
	// Root is the project root: project skills load from Dir and each of
	// its parents up to Root. Empty means the nearest directory, from Dir
	// upwards, that contains .git (a repository or worktree root), or Dir
	// if there is none.
	Root string
	// Home, when set, enables the user's skills in Home/.claude/skills and
	// Home/.agents/skills, for example with os.UserHomeDir. They are off
	// by default, so a program does not pick up skills from the home
	// directory of whoever runs it unless it asks to.
	Home string
	// Managed, when set, is a managed settings directory whose skills take
	// precedence over all others, like Claude Code's enterprise skills.
	Managed string
	// AdditionalDirs are more directories whose skill folders load, like
	// Claude Code's --add-dir, after the project's.
	AdditionalDirs []string
	// Nested also loads the skill folders of directories below Dir. As in
	// Claude Code, such a skill is offered to the model only once it works
	// with a file in that directory (see Scoped), and when its name is
	// taken it is renamed "<dir>:<name>", with dir relative to Dir.
	// Hidden directories and node_modules are not searched.
	Nested bool
	// Folders are the skill folders searched in each directory, in
	// precedence order; nil means DefaultFolders.
	Folders []string
	// ProjectFirst gives project skills precedence over the user's, as
	// Gemini CLI does. By default the user's win, as in Claude Code.
	ProjectFirst bool
	// Strict loads skills with the Strict option.
	Strict bool
}

DiscoverConfig configures Discover.

type Discovery

type Discovery struct {
	// Skills are the skills found, one per name, in precedence order.
	Skills []Skill
	// Sources maps skill names to where they were found.
	Sources map[string]Source
	// Conflicts lists skills hidden by others of the same name.
	Conflicts []Conflict
}

Discovery is the result of Discover.

func Discover

func Discover(cfg DiscoverConfig) (*Discovery, error)

Discover finds skills in the standard locations, in precedence order:

  1. Managed: the skill folders of DiscoverConfig.Managed.
  2. User: Home/.claude/skills and Home/.agents/skills, when Home is set.
  3. Project: the skill folders of Dir and each parent up to the project root, nearest first.
  4. Additional directories.
  5. Nested: the skill folders below Dir, when Nested is set.

Within a directory, .claude/skills comes before .agents/skills. A skill whose name was already found is not used and is reported as a Conflict, except nested skills, which are renamed; a skill reached through several locations, for example by symbolic links to the same directory, loads once. Folders named "synced" at the top of a skill folder are skipped, as Claude Code keeps claude.ai skills there. Missing folders are skipped; the error joins the skills that failed to load, and the others are returned.

func (*Discovery) Registry

func (d *Discovery) Registry() *Registry

Registry returns a registry of the discovered skills.

type Forker

type Forker func(ctx context.Context, s Skill, task string) (*tool.Result, error)

Forker runs the content of a context: fork skill, task, as the prompt of an isolated sub-agent and returns the sub-agent's answer as the skill tool's result. It is called again with an empty task when the calling run resumes after the sub-agent interrupted for approvals; see loop.ResumedCall. The agent package provides one.

type LoadOption

type LoadOption func(*loadOptions)

LoadOption configures LoadFS and LoadDir.

func Strict

func Strict() LoadOption

Strict validates skills with Metadata.ValidateStrict, the Agent Skills specification, instead of Claude Code's rules, and requires each name to match its skill's directory, so skills loaded this way also load in other Agent Skills clients.

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager invokes the skills of a registry and gives their frontmatter effect in agent runs: its Tool lets the model invoke skills, its Middleware applies what active skills configure, and AttachHooks dispatches skill hooks. The agent package sets it up for agents with skills.

While a skill invoked by the skill tool, Invoke or WithCall is active, until the run that invoked it ends, as Claude Code clears them when the user sends the next message:

  • calls matching its allowed-tools need no approval, unless a hook or another middleware asks or denies; other tools are unaffected;
  • tools matching its disallowed-tools are removed from the model's requests and their calls denied;
  • model and effort override the run's model and reasoning effort.

For a context: fork skill these apply to the sub-agent instead. A skill's hooks are registered for the rest of the session, or of the run for runs without a session. Skills with paths, and Scoped skills, are listed for the model only once the conversation has read or edited a matching file; one that becomes relevant during a run is announced in a reminder after the tool result.

func NewManager

func NewManager(r *Registry, cfg Config) *Manager

NewManager returns a manager of the skills of r.

func (*Manager) AttachHooks

func (m *Manager) AttachHooks(h *hook.Manager) error

AttachHooks makes h dispatch the hooks of the skills invoked in a session or run to them, for the events that skills registered in the registry at this time handle. Pass the hook manager of the agent whose runs use the Middleware; context that asynchronous skill hooks produce is queued on h. The agent package attaches its own manager.

As in Claude Code (https://code.claude.com/docs/en/hooks#hooks-in-skills-and-agents), the hooks of a skill are registered when it is invoked and run for the rest of the session, and a handler with "once": true is removed after its first run that neither fails nor blocks. Hooks are registered once per skill and session, held in memory and registered again from the conversation when a run of the session starts, for example after a restart; a once handler may then run again.

func (*Manager) Invoke

func (m *Manager) Invoke(ctx context.Context, sessionID string, c Call) (string, error)

Invoke renders a skill as a user invokes it, for example with /name in a chat interface: unlike the skill tool it accepts skills with disable-model-invocation and refuses those with user-invocable: false. The result, wrapped in a <skill> element, is meant to be sent to the model as or with a user message. sessionID replaces ${CLAUDE_SESSION_ID}. Invoke does not activate the skill; to give allowed-tools and the other fields effect, invoke it in a run with WithCall or agent.RunSkill instead.

func (*Manager) Middleware

func (m *Manager) Middleware() loop.Middleware

Middleware returns the runtime middleware that gives active skills effect in an agent's runs; see Manager. It is named "skill".

func (*Manager) Prompt

func (m *Manager) Prompt(msgs []*model.Message) string

Prompt describes the skills offered to the model in a conversation: those it may invoke, without paths, or whose paths or scope match a file the conversation read or edited. It is empty when there are none.

func (*Manager) Registry

func (m *Manager) Registry() *Registry

Registry returns the manager's registry.

func (*Manager) Tool

func (m *Manager) Tool() tool.Tool

Tool returns the tool through which the model invokes skills. It refuses skills with disable-model-invocation, unless the user invoked the skill in the same run, and runs context: fork skills with Config.Fork. The tool is read-only, so it may run concurrently with other read-only calls, unless the registry has context: fork skills or command injection is enabled.

type Metadata

type Metadata struct {
	// Name is the skill name, by default the name of its directory.
	Name string
	// Description says what the skill does and when to use it. When the
	// frontmatter has none, the loader uses the first non-empty line of
	// the body, as Claude Code does.
	Description string
	// WhenToUse is "when_to_use": more context for when to invoke the
	// skill, appended to Description in the skill listing.
	WhenToUse string
	// ArgumentHint is "argument-hint", such as "[issue-number]", shown to
	// users and to the model.
	ArgumentHint string
	// Arguments names the positional arguments, so $name expands to the
	// argument at that name's position.
	Arguments []string
	// DisableModelInvocation hides the skill from the model: it is not
	// listed and the skill tool refuses it, but users can invoke it; see
	// Registry.Invoke.
	DisableModelInvocation bool
	// UserInvocable is false when "user-invocable: false" hides the skill
	// from users; the model can still invoke it. Nil means true.
	UserInvocable *bool
	// AllowedTools are tool rules, such as "read_file" or
	// "Bash(git add *)", that need no approval while the skill is active.
	// They do not restrict other tools.
	AllowedTools []string
	// DisallowedTools are tool rules removed from the model while the skill
	// is active.
	DisallowedTools []string
	// Model is the model to use while the skill is active, or for its
	// sub-agent with context: fork. "inherit" keeps the current model.
	Model string
	// Effort is the reasoning effort while the skill is active: low,
	// medium, high, xhigh or max.
	Effort string
	// Context is "fork" to run the skill in an isolated sub-agent.
	Context string
	// Agent names the sub-agent of a context: fork skill; the default is
	// general-purpose.
	Agent string
	// Background is false when a forked skill should block the turn that
	// invoked it. agentx always waits for a forked skill's result, as
	// Claude Code does in the Agent SDK; the field is informational.
	Background *bool
	// Hooks holds hooks, in the format of the "hooks" block of a Claude
	// Code settings file, that are registered for the session when the
	// skill is invoked. Values are JSON types.
	Hooks map[string]any
	// Paths are glob patterns: a skill with paths is offered to the model
	// only once it works with matching files.
	Paths []string
	// Shell is the shell of injected commands: bash (the default) or
	// powershell.
	Shell string
	// License, Compatibility and Metadata are fields of the Agent Skills
	// specification that agentx does not act on. Metadata values are
	// strings, []any or map[string]any.
	License       string
	Compatibility string
	Metadata      map[string]any
	// Extra holds frontmatter keys that are not recognized. Sequences are
	// joined with ", " and mappings encoded as JSON.
	Extra map[string]string
}

Metadata is the frontmatter of a skill. Field names follow Claude Code's frontmatter reference; fields of the Agent Skills specification are included.

func Parse

func Parse(doc string) (Metadata, string, error)

Parse splits a SKILL.md document into metadata and body. It understands the subset of YAML used by skill frontmatter: nested block mappings and sequences, flow mappings and sequences ({a: b}, [a, b]), plain scalars, single- and double-quoted scalars (which may span lines and contain "#"), and folded or literal block scalars with chomping and indentation indicators. As in YAML, a "#" that starts a line or follows white space outside quotes begins a comment, so "description: Use C# # note" reads as "Use C#".

Fields that take a list, such as allowed-tools, accept a YAML list or a string separated by commas and, except for paths, white space. Boolean fields accept true, yes, on and 1, or false, no, off and 0, in any letter case. Unrecognized keys are kept in Extra.

func (Metadata) Fork

func (m Metadata) Fork() bool

Fork reports whether the skill runs in a sub-agent (context: fork).

func (Metadata) IsUserInvocable

func (m Metadata) IsUserInvocable() bool

IsUserInvocable reports whether users may invoke the skill.

func (Metadata) Validate

func (m Metadata) Validate() error

Validate checks metadata against the rules of Claude Code (https://code.claude.com/docs/en/skills#characters-a-skill-name-cant-hold): a name is required and may hold any printable characters except "<" and ">"; it may not hold control, format (zero-width or bidirectional), private-use or unassigned characters, or blanks other than single spaces between words, and it may not be "anthropic-skills" or start with "anthropic-skills:". A description is required. ValidateStrict applies the Agent Skills specification instead.

func (Metadata) ValidateStrict

func (m Metadata) ValidateStrict() error

ValidateStrict checks metadata against the Agent Skills specification (https://agentskills.io/specification): a name of 1 to 64 lowercase letters, digits and single hyphens, neither leading nor trailing; a description of 1 to 1024 characters; and a compatibility of at most 500 characters. Loading with Strict also requires the name to match the skill's directory.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry holds skills by name.

func NewRegistry

func NewRegistry(skills ...Skill) *Registry

NewRegistry returns a registry containing skills. Later skills with the same name replace earlier ones, so project skills can override user skills.

func (*Registry) Get

func (r *Registry) Get(name string) (Skill, bool)

Get returns a skill by name.

func (*Registry) HookEvents

func (r *Registry) HookEvents() []hook.EventType

HookEvents returns the events the hooks of the registry's skills handle, in sorted order.

func (*Registry) Invoke

func (r *Registry) Invoke(ctx context.Context, name, arguments string) (string, error)

Invoke renders a skill as a user invokes it with arguments, with the defaults of Config; see Manager.Invoke. Unlike the skill tool, it accepts skills with disable-model-invocation.

func (*Registry) Len

func (r *Registry) Len() int

Len returns the number of skills.

func (*Registry) List

func (r *Registry) List() []Skill

List returns skills sorted by name.

func (*Registry) Load

func (r *Registry) Load(ctx context.Context, name string) (string, error)

Load returns the text given to the model when a skill is invoked without arguments, with the defaults of Config.

func (*Registry) Names

func (r *Registry) Names() []string

Names returns the skill names in sorted order.

func (*Registry) Prompt

func (r *Registry) Prompt() string

Prompt describes the skills offered to the model for the system prompt: all skills except those with disable-model-invocation and those that apply only to some files (see Conditional). Each skill is listed with its description, followed by its when_to_use, cut at 1,536 characters. It is empty when there are no such skills. Manager.Prompt also lists the conditional skills that apply to a conversation.

func (*Registry) Register

func (r *Registry) Register(s Skill)

Register adds or replaces a skill.

func (*Registry) Tool

func (r *Registry) Tool() tool.Tool

Tool returns the tool that invokes skills, with the defaults of Config: skills get their arguments and substitutions, and context: fork skills run inline. See Manager.Tool.

type Scope

type Scope int

Scope is where a discovered skill comes from.

const (
	// ScopeManaged is the managed (enterprise) directory.
	ScopeManaged Scope = iota
	// ScopeUser is the user's home directory.
	ScopeUser
	// ScopeProject is the working directory or one of its parents up to
	// the project root.
	ScopeProject
	// ScopeAdditional is an additional directory, like --add-dir.
	ScopeAdditional
	// ScopeNested is a directory below the working directory.
	ScopeNested
)

func (Scope) String

func (s Scope) String() string

type Scoped

type Scoped interface {
	// ScopeDir is the directory, absolute or relative to the project root.
	ScopeDir() string
}

Scoped is implemented by skills that apply only to the files below a directory, such as the nested skills Discover finds below the working directory: like a skill with paths, they are offered to the model once it works with a file there.

type Skill

type Skill interface {
	Metadata() Metadata
	// Content returns the instructions without frontmatter.
	Content(ctx context.Context) (string, error)
	// Dir is the skill's base directory, used to resolve bundled files. It is
	// empty for skills that are not backed by a directory.
	Dir() string
}

Skill is a loadable unit of instructions.

func Define

func Define(meta Metadata, content string) (Skill, error)

Define returns an in-memory skill with the given metadata. An empty description defaults to the first non-empty line of content.

func LoadDir

func LoadDir(dir string, opts ...LoadOption) ([]Skill, error)

LoadDir loads skills from a directory on disk with LoadFS. A missing directory yields no skills and no error.

func LoadFS

func LoadFS(fsys fs.FS, root, base string, opts ...LoadOption) ([]Skill, error)

LoadFS loads every skill under root in fsys. A skill is any directory containing SKILL.md; nested skills are found at any depth, including through symbolic links to directories. base is the directory on disk that fsys represents and is reported as the skills' Dir prefix. Leave it empty for file systems that are not backed by a directory, such as embedded files; their skills then have an empty Dir.

A skill without a name is named after its directory, and one without a description is described by the first non-empty line of its body, as in Claude Code. Skills that fail to load are reported in the joined error; the others are returned.

func New

func New(name, description, content string, allowedTools ...string) (Skill, error)

New returns an in-memory skill.

type Source

type Source struct {
	Scope Scope
	// Folder is the skill folder, such as /repo/.claude/skills.
	Folder string
}

Source describes where a discovered skill was found.

type Vars

type Vars struct {
	// Arguments is the argument string, as a user would type it after the
	// skill's name.
	Arguments string
	// SessionID replaces ${CLAUDE_SESSION_ID}.
	SessionID string
	// ProjectDir replaces ${CLAUDE_PROJECT_DIR}.
	ProjectDir string
	// Effort replaces ${CLAUDE_EFFORT}.
	Effort string
}

Vars are the values substituted into skill content; see Substitute.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL