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
- Variables
- func Applies(s Skill, root string, files []string) bool
- func Conditional(s Skill) bool
- func FilesOf(call *model.ToolCall) []string
- func MatchTool(root, rule string, call *model.ToolCall) bool
- func SkillHooks(meta Metadata, opts hook.SettingsOptions) (*hook.Manager, []string, error)
- func Substitute(content string, meta Metadata, dir string, v Vars) string
- func WithActivation(ctx context.Context, s Skill) context.Context
- func WithCall(ctx context.Context, c Call) context.Context
- type Call
- type Config
- type Conflict
- type DiscoverConfig
- type Discovery
- type Forker
- type LoadOption
- type Manager
- func (m *Manager) AttachHooks(h *hook.Manager) error
- func (m *Manager) Invoke(ctx context.Context, sessionID string, c Call) (string, error)
- func (m *Manager) Middleware() loop.Middleware
- func (m *Manager) Prompt(msgs []*model.Message) string
- func (m *Manager) Registry() *Registry
- func (m *Manager) Tool() tool.Tool
- type Metadata
- type Registry
- func (r *Registry) Get(name string) (Skill, bool)
- func (r *Registry) HookEvents() []hook.EventType
- func (r *Registry) Invoke(ctx context.Context, name, arguments string) (string, error)
- func (r *Registry) Len() int
- func (r *Registry) List() []Skill
- func (r *Registry) Load(ctx context.Context, name string) (string, error)
- func (r *Registry) Names() []string
- func (r *Registry) Prompt() string
- func (r *Registry) Register(s Skill)
- func (r *Registry) Tool() tool.Tool
- type Scope
- type Scoped
- type Skill
- type Source
- type Vars
Constants ¶
const (
DefaultShellTimeout = 2 * time.Minute
)
Defaults of command injection, as in Claude Code, whose injected commands share the Bash tool's 2-minute timeout.
const FileName = "SKILL.md"
FileName is the name of the file that defines a skill.
const ToolName = "skill"
ToolName is the name of the tool that loads skills.
Variables ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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:
- Managed: the skill folders of DiscoverConfig.Managed.
- User: Home/.claude/skills and Home/.agents/skills, when Home is set.
- Project: the skill folders of Dir and each parent up to the project root, nearest first.
- Additional directories.
- 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.
type Forker ¶
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 ¶
NewManager returns a manager of the skills of r.
func (*Manager) AttachHooks ¶
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 ¶
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 ¶
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) 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 ¶
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) IsUserInvocable ¶
IsUserInvocable reports whether users may invoke the skill.
func (Metadata) Validate ¶
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 ¶
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 ¶
NewRegistry returns a registry containing skills. Later skills with the same name replace earlier ones, so project skills can override user skills.
func (*Registry) HookEvents ¶
HookEvents returns the events the hooks of the registry's skills handle, in sorted order.
func (*Registry) Invoke ¶
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) Load ¶
Load returns the text given to the model when a skill is invoked without arguments, with the defaults of Config.
func (*Registry) Prompt ¶
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.
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 )
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 ¶
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 ¶
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.
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.