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:
- The L1 listing (rebuilt each turn from live metadata) always shows the current
(v:...). view_skillreturns theversionof the bytes it loaded — and that tag survives compaction (the content is dropped, the version and a reload hint are kept).- 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
| Field | Required | Purpose |
|---|---|---|
name | Yes | Must match directory name; kebab-case, max 64 chars |
description | Yes | English description shown in L1 listing |
description_zh | No | Chinese description variant |
version | No | Fallback version tag for vendored skills (≤32 chars). Uploaded skills ignore it — they get an auto-assigned integer revision in a .version sidecar instead |
compatibility | No | Version compatibility range |
allowed-tools | No | Space-separated tool names this skill may use |
min_tier | No | Minimum model tier required ("lite" or "ultra") |
metadata.aibuddy.dependencies | No | Declared 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:
| Before | After |
|---|---|
| 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
| System | How view_skill integrates |
|---|---|
| Prompt (L1) | instructions() injects the skill listing; availability gated by scope + tool keys |
| Tool Registry | Dynamic input schema: name enum from the listed skills, path optional |
| Compaction | compact() reduces path-omitted call output to metadata summary |
| Client Stream | toClientOutput() returns “Skill activated” stub |
| SkillStorage | Authoritative store (R2 / FS); read only by discovery + the provisioner, not by tools |
| Sandbox | Runtime home — the skill dir is materialized to .skills/<name>/; reads + shell runs hit it |