Skill Loading

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 @aibuddy/core/src/agent/tools/view-skill.tool.ts, with the skill registry in @aibuddy/core/src/agent/skill/skill-registry.ts. SkillStorage (R2 on the server, FS on Desktop) is the authoritative definition store — but it is not the runtime surface. On first view, a skill’s whole directory is materialized out of the store into the task sandbox at .skills/<name>/, and the tool then reads the requested file back from that mount. One runtime home: the model reads exactly the bytes shell executes, so a SKILL.md that references scripts/*.py actually resolves.


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** (v:6): Systematic market analysis with competitive intelligence
- **code-review** (v:12): Structured code review with security and performance checks
- **data-analysis** (v:1): Statistical analysis and visualization workflows

Each entry costs ~20–30 tokens. Even with 50 skills, the total L1 overhead stays under 1,500 tokens. The (v:...) tag is the skill’s version — an integer revision auto-assigned on every upload and stored in a .version sidecar file inside the skill directory. No author discipline: each re-upload bumps it, so it always changes when the skill changes, and it drives the mid-task self-heal below. (A vendored skill that never goes through the upload path has no sidecar; it falls back to an optional version: frontmatter field, or shows no tag.)

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). On this first view the tool materializes the whole skill directory from the store into the sandbox at .skills/market-research/ (once per turn, memoized), then reads SKILL.md back from that mount and returns its body as the tool result. The agent then follows the skill’s instructions; if the body references references/x.md, it calls view_skill(name="market-research", path="references/x.md") to fetch it on demand — served from the same mount.

Because the directory is now on disk in the workspace, the agent can also run a skill’s bundled scripts with the shell tool — e.g. python .skills/market-research/scripts/analyze.py — the capability that makes a skill executable, not just a reference document. (Declared dependencies are parsed but not auto-installed; a skill that needs packages installs them via shell as one of its steps.)

This deferred loading means only activated skills consume context window space and only activated skills touch the sandbox. If the copy fails (store unreachable), the tool returns error.code = "skill_copy_failed" rather than a half-copied skill.

Versioning & mid-task edits

A skill can change while a task is running (a re-upload, an admin edit). Without a signal, the agent’s context would hold the old SKILL.md while a later reload silently returns the new one — mixed versions within one task. Rather than freeze a snapshot in the harness, aibuddy makes the version visible to the agent and lets it reconcile:

  1. The L1 listing (rebuilt each turn from live metadata) always shows the current (v:...).
  2. view_skill returns the version of the bytes it loaded — and that tag survives compaction (the content is dropped, the version and a reload hint are kept).
  3. A prompt principle tells the agent: if a skill you already loaded now shows a different version in the listing, its definition changed — reload it before relying on it further.

So consistency is agent-driven, not harness-enforced: the agent notices loaded v:5 vs listing v:6 and re-calls view_skill. The version is an auto-assigned integer revision, not a content hash and not the database: skillRegistry.writeSkillWithVersion (the single upload path used by both the user skill service and the admin system-skill service) reads the current .version, increments it, strips any incoming .version, and writes the new one alongside the skill. Because it bumps unconditionally on every upload, coverage is automatic — no author has to remember, and external skills with no version field get one for free.

It lives in a .version sidecar rather than the database on purpose. Skill metadata already comes from parsing storage (cached in SkillMetaCache), not from the DB — so the version is read from the same storage layer and cached alongside name / description, with no DB coupling and no new table (user skills have no DB content row — only enable flags). At cold-load listMetas overlays each skill’s .version onto its metadata (sidecar wins; frontmatter version is the fallback for vendored skills). writeSkill’s existing cache invalidation refreshes it after an upload, so the next turn’s L1 listing shows the new revision. The .version file is hidden from the UI file tree and never materialized into the sandbox. Full content-addressed versioning (pin a task to an exact revision, reproduce historical runs) is intentionally deferred until a concrete need appears.


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:
  aibuddy:
    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
versionNoFallback version tag for vendored skills (≤32 chars). Uploaded skills ignore it — they get an auto-assigned integer revision in a .version sidecar instead
compatibilityNoVersion compatibility range
allowed-toolsNoSpace-separated tool names this skill may use
min_tierNoMinimum model tier required ("lite" or "ultra")
metadata.aibuddy.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), overlay each skill’s .version sidecar onto meta.version (sidecar wins; frontmatter version is the fallback), and cache the results. Subsequent access returns from cache — so the version tag in the L1 listing is read from storage once and served from cache, never from the DB and not per-turn.

Loading (Phase 2)

The copy step moves the skill out of the store and into the sandbox. copySkillIntoSandbox (in agent/skill/sandbox-mount.ts) walks readTree(scope, name), reads each file raw via readRawFile (no frontmatter strip — the sandbox copy is a faithful mirror), and writes it to .skills/<name>/<relPath> with sandbox.writeFile. The mount is workspace-relative because the sandbox clamps every path under its workspace root.

view_skill then reads the requested file back from the mount with sandbox.readFile. SKILL.md is stripped of its frontmatter at read time for model display (the on-disk copy keeps it); other paths return verbatim. The registry’s own readFile (frontmatter-stripping) still backs the UI / admin file browser, but is no longer on the agent read path.

Path traversal is rejected before the mount is touched (.., absolute, or backslash paths → skill_path_invalid), and the sandbox independently clamps any survivor under the workspace root.


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, version, content: marker, note } (~30 tokens)

The compacted form tells the LLM what skill was loaded — and at which version — without preserving the full instructions. Retaining version is deliberate: it is what lets the agent compare against the L1 listing and notice a mid-task edit. If the agent needs the instructions again (or sees a newer version listed), it calls 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; availability gated by scope + tool keys
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
SkillStorageAuthoritative store (R2 / FS); read only by discovery + the provisioner, not by tools
SandboxRuntime home — the skill dir is materialized to .skills/<name>/; reads + shell runs hit it
Was this page helpful?