Skip to content

Skills

pkg/agent/skills/ and pkg/agent/skilltoolset/ implement agentskills.io-spec progressive-disclosure skills — folder-shaped capability bundles that an agent can discover and load on demand. Each skill is a SKILL.md (YAML frontmatter + Markdown body) plus optional references/, assets/, and scripts/ directories. The agent sees only the L1 metadata (name + description) up front; when the LLM judges a skill relevant, it calls load_skill to read the full instructions, list_skill_resources to enumerate the skill's files, and load_skill_resource to fetch a specific one. This keeps system prompts small while giving the agent access to a much larger set of specialised playbooks.

What it does

A skill on disk looks like:

skills/
  pdf-extraction/
    SKILL.md          # frontmatter + instructions
    references/
      formats.md
    assets/
      template.json
    scripts/
      run.sh

SKILL.md opens with YAML frontmatter:

---
name: pdf-extraction
description: Extract structured tables from scanned PDF financial reports.
license: MIT
compatibility: helix-1.x
allowed-tools:
  - fs.read
  - shell.exec
metadata:
  owner: search-team
---

# PDF extraction skill

Step 1 — call `fs.read` on the input path…

A skills.Source provides storage abstraction: list frontmatters, load instructions, list and load resources. The SkillToolset (pkg/agent/skilltoolset) wraps a Source as three auto-generated tools:

  • list_skills — returns L1 metadata (names + descriptions) for every skill in the source. (At Run start, the agent also emits a skills_registered event carrying the same metadata so Studio shows the catalogue upfront.)
  • load_skill — loads a skill's full SKILL.md body. Activates the skill in the per-run ActiveSkillsTracker, granting any allowed-tools in its frontmatter pre-approval for the rest of the run. Its result also carries a resources manifest — the exact file paths in the skill — so the common case needs no separate discovery call.
  • list_skill_resources — enumerates a skill's files (references/ / assets/ / scripts/ …) so the LLM loads real paths instead of guessing. Takes skill_name and an optional subpath (e.g. references) to scope the listing; . or an omitted subpath lists everything. Wraps Source.ListResources.
  • load_skill_resource — reads a specific file under the skill's references/ / assets/ / scripts/ subtree.

The system prompt picks up a fixed instruction block telling the LLM how to use these four tools — including that it must discover real paths via list_skill_resources (or the load_skill manifest) before reading a file, and that load_skill_resource is the ONLY way to view skill files (not the regular file tools).

Implementations

Sources

FileSystemSource

NewFileSystemSource(fs.FS) — reads skills from an fs.FS (a real filesystem rooted at skills/, an embedded embed.FS, or any in-memory FS). Validates the frontmatter on every list / load and rejects skills with the wrong layout (missing SKILL.md, bad name). Resource paths are sandboxed: .. traversal is rejected, and only paths under references/, assets/, and scripts/ are reachable.

InlineSource

NewInlineSource([]InlineSkill) — same Source surface but reads from in-memory data. Use this when skills are declared inline in YAML or generated at startup. Validates frontmatter and resource paths at construction, so misconfigured inline skills fail fast at workspace load rather than at first use.

MergedSource

NewMergedSource(s1, s2, …) — composes multiple Sources into one. Duplicate skill names error on first list. Useful for mixing a filesystem source with an inline source (the workspace's checked-in skills + ones generated at start).

ActiveSkillsTracker

Per-run state held on the context. load_skill activates a skill; allowed-tools from its frontmatter then pre-approve those tool names — they skip human approval even when the agent's approval_policy: always or tagged would otherwise require it. The tracker is fresh on every agent.Run, so concurrent runs don't bleed permissions into each other.

Public methods:

  • Activate(name, allowedTools) — register a successful load.
  • IsToolAllowedByActiveSkill(toolName) — runtime check the agent uses inside requiresApprovalCtx.
  • ActiveSkills() — names of currently active skills.
  • SkillsAllowingTool(toolName) — which active skills granted a given tool (for "approved by X" trace events).

Frontmatter schema

type Frontmatter struct {
    Name          string            `yaml:"name"`           // required, kebab-case, 1-64 chars
    Description   string            `yaml:"description"`    // required, 1-1024 chars
    License       string            `yaml:"license,omitempty"`
    Compatibility string            `yaml:"compatibility,omitempty"`
    Metadata      map[string]string `yaml:"metadata,omitempty"`
    AllowedTools  []string          `yaml:"allowed-tools,omitempty"`
}

Validate(fm) checks name shape, description bounds, etc. Parse(r) reads the leading frontmatter from a bufio.Reader and leaves it positioned at the start of the markdown body.

Sentinel errors

ErrInvalidSkillName, ErrInvalidFrontmatter, ErrSkillNotFound, ErrDuplicateSkill, ErrInvalidResourcePath, ErrResourceNotFound — use errors.Is to distinguish source error classes.

skilltoolset.SkillToolset

ts, err := skilltoolset.New(ctx, skilltoolset.Config{
    Source: fsSource,
    SystemInstruction: "",  // empty = use the default instruction block
})

for _, t := range ts.Tools() { agentTools.Register(t) }
agentSystemPrompt += ts.SystemInstruction()
agentSystemPrompt += "\n" + ts.SkillsCatalogXML(ctx)

SystemInstruction() returns the default block telling the LLM:

If a skill seems relevant, you MUST use load_skill with name="<SKILL_NAME>" to read its full instructions before proceeding. Use load_skill_resource to view files within a skill's directory.

SkillsCatalogXML(ctx) returns L1 metadata as an XML block that the reifier appends to the system prompt at construction time. Without it, the LLM would have to call list_skills on every turn just to discover what's available. SkillsCatalogJSON(ctx) is the same data shaped for machine consumption (debug events, IDE rendering).

Agent integration

When AgentDefinition.Skills is non-empty, the reifier:

  1. Resolves each skill ID against the registered Sources.
  2. Constructs a merged Source covering all matched skills.
  3. Wires a SkillToolset and registers its three tools on the agent's tool.Registry.
  4. Appends the SystemInstruction and SkillsCatalogXML to the agent's system prompt.
  5. Forwards SkillsCatalogJSON to the agent so it can emit a skills_registered event at the start of each Run.

At runtime:

  • load_skill → activates the skill in the tracker → agent emits a typed skill_loaded event.
  • load_skill_resource → emits a skill_resource_loaded event with both skill ID and resource path.
  • Subsequent tool calls consult IsToolAllowedByActiveSkill to bypass approval for tools the skill pre-approved.

Configuration

YAML reference:

agents:
  - id: doc-processor
    type: react
    skills:
      - pdf-extraction
      - invoice-parsing
      - email-summarisation
    approval_policy: tagged
    approval_categories: [external_write]

skills: is a list of skill IDs. The reifier resolves these against the workspace's skills/ directory (plus any registered inline sources). When the agent runs:

  • All four skill tools (list_skills, load_skill, list_skill_resources, load_skill_resource) are auto-registered alongside any other tools you've declared.
  • The system prompt is auto-augmented with the catalogue and the usage instructions.
  • If a loaded skill's frontmatter declares allowed-tools: [shell.exec] and the agent has approval_policy: tagged with external_write matching shell, calls to shell.exec after load_skill proceed WITHOUT approval — the skill has vouched for them.

Behavior notes

  • allowed-tools pre-approval is per-RUN, not per-AGENT. Each call to agent.Run starts with an empty tracker; the LLM must re-load_skill for the next run if it needs the same tool bypass.
  • Skill names must be kebab-case (^[a-z0-9][a-z0-9-]{0,62}[a-z0-9]$) and unique within the merged Source. Duplicates fail at construction.
  • Resource paths are scoped — load_skill_resource rejects absolute paths, .. traversal, and anything outside references/, assets/, scripts/. Path validation runs in safeRelativePath.
  • The LLM should NOT use generic file tools (fs.read) on skill files — the toolset's system instruction explicitly tells it to use load_skill_resource so the tracking and sandboxing apply.
  • SkillsCatalog on AgentConfig is the JSON form of the L1 catalogue. When the reifier wires a SkillToolset, it sets this field; the agent emits it as a skills_registered debug event upfront so Studio's debugger can render the catalogue before the LLM has had a chance to call list_skills.

Example

A workspace with two filesystem skills:

project/
  app.yaml
  skills/
    pdf-extraction/SKILL.md
    csv-cleanup/SKILL.md
agents:
  - id: extractor
    type: react
    model: { provider: anthropic, model: claude-sonnet-4-7 }
    system_prompt: "You extract structured data from documents."
    skills: [pdf-extraction, csv-cleanup]
    tools:
      - { name: read-file, type: function, function: fs.read }
      - { name: shell-exec, type: function, function: shell.exec,
          categories: [external_write] }
    approval_policy: tagged
    approval_categories: [external_write]

When the agent receives "Pull the totals from invoices.pdf", the LLM sees the L1 catalogue, decides pdf-extraction is relevant, calls load_skill name="pdf-extraction", reads the instructions (which say to call shell.exec for pdftotext), and proceeds — the shell call is auto-approved because the skill listed shell.exec under allowed-tools.

Source

  • pkg/agent/skills/source.go — Source interface.
  • pkg/agent/skills/frontmatter.go — Frontmatter, Parse/Validate/Build, sentinel errors.
  • pkg/agent/skills/filesystem_source.go — FileSystemSource, safeRelativePath.
  • pkg/agent/skills/inline_source.go — InlineSource, InlineSkill.
  • pkg/agent/skills/merged_source.go — MergedSource.
  • pkg/agent/skills/active.go — ActiveSkillsTracker, WithActiveSkillsTracker.
  • pkg/agent/skilltoolset/toolset.go — SkillToolset, SkillsCatalogXML, SkillsCatalogJSON.
  • pkg/agent/skilltoolset/internal/skilltool/ — the three tool factories ListSkills, LoadSkill, LoadSkillResource, plus SkillsToXML.
  • pkg/agent/adk/agent.go — emitSkillsRegistered, emitSkillLoaded, emitSkillResourceLoaded, the requiresApprovalCtx hook.
  • pkg/dsl/reifier.go — Skill resolution + toolset wiring.

See also

  • tools — the underlying tool framework; skills register as ordinary tool.Tools.
  • react — the loop that emits skill_loaded etc.
  • guardrails — skills' allowed-tools pre-approve calls without weakening guardrails.