▞ jazzdocsblogpersonas
github

Skills loading — progressive disclosure

This page explains how Jazz can have a hundred skills installed without paying for them in every request.

Source: tools/skill-tools.ts · core/skills/skill-service.ts

For what skills are and how to write one, see Skills. This page is about the loading mechanism.


The problem

A skill is a markdown playbook — often long, sometimes with supplementary files. Preloading every installed skill into the system prompt is the obvious design and it doesn’t scale: a hundred skills would consume the context window before the user has said anything.

Jazz solves it with three levels of detail, each fetched only when the previous level isn’t enough.

flowchart TB
    L0["<b>Level 0 · Always present</b><br/>Skill index in the system prompt:<br/>names + one-line descriptions<br/><i>cost: a few hundred tokens total</i>"]

    L0 --> Q{"Enough to pick<br/>a skill?"}
    Q -->|"yes"| L2
    Q -->|"no — need detail"| L1

    L1["<b>Level 1 · find_skills(query)</b><br/>ranked matches with full descriptions<br/><i>cost: one tool call</i>"]
    L1 --> L2

    L2["<b>Level 2 · load_skill(name)</b><br/>the skill's full instructions<br/><i>cost: one tool call + the skill body</i>"]

    L2 --> Q2{"Instructions reference<br/>another file?"}
    Q2 -->|yes| L3["<b>Level 3 · load_skill_section(name, section)</b><br/>one supplementary file<br/><i>cost: one tool call + that file</i>"]
    Q2 -->|no| DONE(["Work"])
    L3 --> DONE

    classDef cheap fill:#4f9d9d,stroke:#2f6d6d,color:#ffffff
    classDef mid fill:#f9a03f,stroke:#b3541e,color:#1a1a1a
    class L0 cheap
    class L2,L3 mid

Most runs stop at level 0 — the index is enough to decide no skill applies, or to name the one that does and load it directly. You pay for depth only when depth is used.


The three tools

ToolInputReturnsRisk
find_skillsquery, optional limit (max 10, default 5)Ranked name: description linesread-only
load_skillskill_nameThe skill’s full instructionsread-only
load_skill_sectionskill_name, section_nameOne supplementary file referenced by the skillread-only

A detail worth noticing: skill_name is a z.enum built from the actually-discovered skill names, not a free string. The model literally cannot hallucinate a skill name — an invalid one is a schema violation caught before execution rather than a “skill not found” round trip.

find_skills ranks with a scoring function over names and descriptions, and when nothing matches it says so and points at load_skill for exact names, rather than returning an empty result the model has to interpret.


Where skills come from

Three sources, merged by precedence. Later wins, so you can shadow a built-in skill with your own.

flowchart LR
    B["<b>1 · Built-in</b><br/>shipped in the jazz-ai package<br/>18 skills"]
    G["<b>2 · Global</b><br/>~/.jazz/skills/<br/><i>cached index</i>"]
    L["<b>3 · Local</b><br/>./skills/<br/>project-specific"]

    B --> M["Merged catalog<br/>later source wins on name collision"]
    G --> M
    L --> M
    M --> IDX["Skill index<br/>→ system prompt"]

    classDef win fill:#4f9d9d,stroke:#2f6d6d,color:#ffffff
    class L,M win

The global directory’s index is cached at ~/.jazz/global-skills-index.json so startup doesn’t re-scan a large skill library every time. Local skills are scanned per project, which is what makes a checked-in ./skills/ directory work — clone the repo, get the team’s skills.

Jazz follows the .agents convention, so skills written for other agents work here. npx skills add installs from the ecosystem; /skills in chat lists what’s available.


Cost profile

ScenarioTool callsContext cost
No skill needed0the index only
Agent knows the skill by name1index + skill body
Agent needs to search first2index + matches + skill body
Skill pulls in a reference file3index + matches + body + file

The trade-off: up to two extra round trips before the agent starts working. That’s the price of not spending the window on skills nobody asked for. When the agent already knows the name from the index — the common case — it’s one call.

The loaded body stays in the conversation as a tool result. It is not copied into the system prompt on later turns: that prefix is cached, and mutating it on load_skill would miss on every subsequent request. The Skills instructions tell the model to treat that tool result as the playbook and execute it rather than improvise a shorter path.


machine-readable: /docs/internals/skills-loading.md · /llms.txt · /llms-full.txt