Skill Loading

Progressive skill loading via the view_skill tool — two-phase discovery and on-demand content loading following the agentskills.io specification

Metadata in the prompt, content on demand

The view_skill(name, path?) tool implements progressive skill loading — a two-phase pattern that keeps the agent’s baseline prompt small while making arbitrarily deep domain knowledge available on demand. It follows the agentskills.io specification.

When path is omitted, the tool loads SKILL.md (full L2 instructions); when present, it reads the resource at that path (L3 reference material). One tool covers both “load skill” and “read referenced file” actions, avoiding the L1 token cost of a separate view_skill + read_skill_resource pair. Design rationale: see Integration Guide.

The tool resides in @zapvol/backend/src/tools/tools/view-skill.tool.ts, with the skill registry in @zapvol/backend/src/agent/skill/skill-registry.ts. Content flows through an injected SkillStorage port (R2 on the server, FS on Desktop) — decoupled from the task sandbox.


Two-Phase Loading

Phase 1 — Metadata in Prompt (L1)

At agent startup, the skill registry scans the skills directory and injects a lightweight listing into the system prompt (L1 metadata layer):

### Skills: `view_skill`

Available Skills:
- **market-research**: Systematic market analysis with competitive intelligence
- **code-review**: Structured code review with security and performance checks
- **data-analysis**: Statistical analysis and visualization workflows

Each entry costs ~20–30 tokens. Even with 50 skills, the total L1 overhead stays under 1,500 tokens.

Phase 2 — Full Content on Demand

When the agent recognizes a user task matching a skill’s domain, it calls view_skill(name="market-research") (path omitted). The tool reads the full SKILL.md content via SkillStorage and returns it as the tool result. The agent then follows the skill’s instructions; if the skill body references references/x.md, the agent calls view_skill(name="market-research", path="references/x.md") to fetch it on demand.

This deferred loading means only activated skills consume context window space.


Skill Directory Structure

Each skill is a {name}/ entry in the skill storage (the FS backend lays it out as a directory):

skills/
  market-research/
    SKILL.md              ← Entry point (frontmatter + instructions)
    references/
      frameworks.md       ← Additional reference files
      templates.md
  code-review/
    SKILL.md

SKILL.md Format

---
name: market-research
description: Systematic market analysis with competitive intelligence
description_zh: 系统化市场分析与竞争情报
compatibility: ">=1.0"
allowed-tools: tavily_search exa_company_search filesystem
min_tier: lite
metadata:
  zapvol:
    dependencies:
      python:
        - pandas
        - matplotlib
---

## Instructions

Step-by-step skill instructions here...

Frontmatter Fields

FieldRequiredPurpose
nameYesMust match directory name; kebab-case, max 64 chars
descriptionYesEnglish description shown in L1 listing
description_zhNoChinese description variant
compatibilityNoVersion compatibility range
allowed-toolsNoSpace-separated tool names this skill may use
min_tierNoMinimum model tier required ("lite" or "ultra")
metadata.zapvol.dependenciesNoDeclared package deps (python / node arrays) — parsed into metadata, not auto-installed

SkillRegistry

The skillRegistry singleton manages skill discovery, caching, and loading:

Discovery (Phase 1)

On first access to a scope, listMetas() / listMetasAcross() call storage.listSkills(scope), parse SKILL.md frontmatter via gray-matter, validate the metadata (name format, length limits, name-directory match), and cache the results. Subsequent access returns from cache.

Loading (Phase 2)

readFile(scope, name, relPath) reads through SkillStorage, returning { content, path } | null. When relPath is omitted it loads SKILL.md and strips the frontmatter, returning only the body; with a relPath it returns the raw resource content.

Path traversal is rejected as a SkillStorage contract invariant — each storage backend enforces it, so normalized paths containing .. or absolute paths never resolve.


Compaction

When context compression occurs, the output of view_skill path-omitted calls (which can be large — full skill instructions) is compacted to metadata only:

BeforeAfter
Full skill content (500–2,000 tokens){ skillName, path, totalChars, totalLines } (~20 tokens)

The compacted form tells the LLM what skill was loaded without preserving the full instructions. If the agent needs the instructions again, it can call view_skill(name) once more.

Calls with a path argument (L3 resource reads) are out of scope for this mechanism — they are treated as ordinary file-read tool results and handled by the generic compaction strategy.


Client Output Reduction

For the client stream, the full skill content is replaced with a simple confirmation:

toClientOutput: (output) => ({
  skillName: output.skillName,
  path: output.path,
  content: "Skill activated.",
});

This prevents large skill files from bloating the client-side message payload.


Integration Points

SystemHow view_skill integrates
Prompt (L1)instructions() injects the skill listing (no longer gated by sandbox.skillsDir)
Tool RegistryDynamic input schema: name enum from the listed skills, path optional
Compactioncompact() reduces path-omitted call output to metadata summary
Client StreamtoClientOutput() returns “Skill activated” stub
SkillStorageFiles read through SkillStorage.readFile(scope, name, relPath) — R2 / FS backends
Was this page helpful?