agent-experience
Serve your Agent Skills through MCP instead of loading them all into every prompt.
A skill library is progressive by design — ~100 tokens of metadata to find a skill, the full body only once you've chosen it. A flat listing throws that away and pays for the whole catalogue on every request. This plugin restores it.
The problem, measured
On a real inventory of 277 installed skills:
| Flat listing of every name + description | ~22,000 tokens, on every single request |
| Skills ever invoked, across 143 sessions | 7 |
| Invocation rate | 0.14% of all tool calls |
| Listing cost per skill that ever gets used | ~3,100 tokens |
Twenty-two thousand tokens of permanent context overhead to advertise a library that fires roughly once per seven hundred tool calls — all spent before the model knows whether the task needs a skill at all.
Worse, that cost is linear in inventory size, so the catalogue itself has a ceiling. At ~80 tokens per skill a thousand skills would be ~80k tokens of system prompt, and a few thousand simply will not fit.
What this changes
Measured against the same inventory, through the MCP server:
| Call | Result | Cost |
|---|---|---|
skills_list (no arguments) | all 263 skills, complete | ~1,900 tokens |
skills_list({query: "supabase"}) | 9 matches with descriptions | ~900 tokens |
activate_skill({name: "supabase"}) | the one skill you chose | ~2,300 tokens |
| Nothing needed this turn | — | 0 tokens |
~7 tokens per skill instead of ~80, because browsing needs names and only the narrowed set needs prose. A thousand skills is ~7k tokens; ten thousand is ~70k. The inventory stops being bounded by the context window, and nothing is paid at all on turns where no skill is used.
(Your numbers will differ — the audit below prints them for your machine.)
Progressive disclosure over MCP
The Agent Skills format defines three disclosure stages. This maps each onto an MCP primitive, so the cost is paid when a stage is reached, not upfront:
| Stage | Cost | MCP primitive |
|---|---|---|
| Discovery — what exists | ~7 tokens/skill, on request | skills_list tool |
Activation — full SKILL.md body | ~2,300 tokens, one skill | activate_skill tool |
| Resources — bundled scripts, references, assets | on demand, per file | skill://{name}/{path} |
The catalogue leaves the system prompt entirely. The model calls skills_list
when it wants to know what's available, activate_skill once it has chosen, and
reads bundled files through skill:// URIs only if the skill's instructions
point at them.
Discovery has three levels, so breadth is cheap and prose is bought only where it's needed:
detail: "names"(default with no query) — every skill name, grouped by root, uncapped. The complete catalogue for less than one activated skill.detail: "summary"(default with a query) — matching skills with descriptions truncated to 200 characters. Enough to choose between hits.detail: "full"— untruncated descriptions and bundle metadata.
Two stable tools plus one resource template — not one tool per skill, which would reintroduce the same bloat through the tool list instead of the prompt.
Install
/plugin marketplace add JesseVent/agent-experience
/plugin install agent-experience@agent-experience
The MCP server starts automatically. It auto-discovers skills from
~/.agents/skills, ~/.claude/skills, <cwd>/.agents/skills, and plugin skill
directories — resolving symlinks and de-duplicating by canonical path.
Tools
| Tool | Transport | What it does |
|---|---|---|
skills_list | stdio + HTTP | Discovery — filter by query/root, paginated, cacheable (5-min TTL) |
activate_skill | stdio + HTTP | Activation — loads the full SKILL.md into context |
skill_provenance | stdio + HTTP | Where a skill came from: source repo, hash, install/update times |
skill_experience | stdio + HTTP | What's been recorded about past use of a skill |
skill_updates_available | stdio | Detect locally-modified skills via folder-hash comparison |
skill_update | stdio | Apply upstream updates (destructive — replaces skill files) |
experience_record | stdio | Record an outcome after using a skill |
Read-only tools work on both transports. Anything with a local side effect is
stdio-only, and skill_update is explicitly destructive.
Resources: skill://{name}/{path} for any bundled file, skill://{name}/provenance
for lockfile data. Path traversal is rejected at three layers.
The audit
The measurement above is reproducible, and it's a tool in its own right — because knowing why 270 of 277 skills never fire is a prerequisite for fixing it.
node "${CLAUDE_PLUGIN_ROOT}/scripts/audit.cjs" --out ~/.agents/experience
Or just ask Claude to "audit my skills" — the bundled skill-audit skill
handles it. The report covers:
- Context cost — the numbers above, for your inventory
- Invoked — with mandated invocations flagged separately, so forced calls don't flatter the figures
- Near-misses — unused skills whose vocabulary overlaps your real prompts, ranked by prevalence × distinctiveness, always showing the matched terms
- Duplicates across roots — resolution is first-match-wins, so the second
copy is unreachable;
diverged: truemeans the copy you edit may never load - Description problems —
too_short,no_prompt_overlap,no_trigger_vocabulary - Dormant — the deletion shortlist, and only a shortlist
It reads session transcripts, which Claude Code already writes. No telemetry is collected — the transcript is the record.
Scoring is deliberately mechanical and always shows its matched terms: a match means the words line up, not that the skill would have helped. The script never decides a skill is dead; judgement belongs to the skill reading the report, and deletion belongs to you.
The suggest hook
A UserPromptSubmit hook that surfaces a never-used skill when your prompt
strongly matches one — discovery fails at prompt time, so that's where it acts.
Conservative by design; a prompt-path hook that cries wolf is worse than none:
- reads the precomputed
audit.json, never scans the filesystem (~48ms) - fires only on 3+ distinct term matches, on prompts of 25+ characters
- suggests at most 2 skills, phrased as availability rather than instruction
- 6-hour per-skill cooldown, so it cannot nag
- prints nothing and exits 0 on any error — never blocks a prompt
Disable with AGENT_EXPERIENCE_SUGGEST=0. Silent until you generate an
audit.json with --out.
Privacy
Everything runs locally; nothing is transmitted. The audit reads prompt text to extract term frequencies and never retains or writes it — only counts and normalised terms leave the scanner, and a term survives only if it already appears in one of your skill descriptions.
audit.json contains your skill names and matched terms. It lives under
~/.agents/experience and should not be committed anywhere public.
Development
scripts/*.cjs are esbuild bundles of a TypeScript source tree — bundled rather
than published as packages so the plugin has no install step and no dependency
resolution at hook or server startup.
esbuild bin/mcp-stdio.ts --bundle --platform=node --format=cjs --target=node20 \
--outfile=plugins/agent-experience/scripts/mcp-server.cjs
esbuild bin/audit.ts --bundle --platform=node --format=cjs --target=node20 \
--outfile=plugins/agent-experience/scripts/audit.cjs
License
MIT