moonlight-lupin/agent-skills

skill-retrieval

- BM25-based skill retrieval plugin for Hermes Agent.

View source
Original skill document

Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.

Skill Retrieval

This is a Hermes Agent plugin. It is not a Claude Code plugin and will not load in Claude Code — that runtime has no pre_llm_call event, no Python register() entry point, and reads .claude-plugin/plugin.json rather than plugin.yaml. Developed against Hermes Agent >=0.20.0.

BM25-based progressive disclosure for Hermes Agent skills. Instead of dumping every skill description into the system prompt (~11.5K tokens), this plugin keeps a compact names-only index and injects only the top-K relevant descriptions per turn.

What it does

Two-phase progressive disclosure:

  1. Phase 1 — System prompt compaction (session start): Monkey-patches

build_skills_system_prompt so the <available_skills> block lists skill names only (descriptions stripped). All skills remain discoverable by name (~2K tokens instead of ~11.5K).

  1. Phase 2 — Per-turn BM25 retrieval (pre_llm_call hook): Tokenizes the

user message, ranks active skill descriptions with BM25 Okapi, and injects the top-K matches (~300 tokens) as context above the user message.

Architecture

Session start
    │
    ▼
Phase 1: patch build_skills_system_prompt
    └── <available_skills> → names only (~2K tokens)

Each turn (pre_llm_call)
    │
    ▼
Phase 2: BM25Index.retrieve(user_message, top_k)
    └── inject "## Retrieved Skills ..." into user message (~300 tokens)

The BM25 index is built once at plugin load from standalone skills (~/.hermes/skills) and plugin-bundled skills (~/.hermes/plugins/*/skills). Retrieval uses a pure-stdlib inverted index (term → posting list of precomputed BM25 weights) and is sub-millisecond for ~200 skills.

Token savings

StageTokens (approx.)
Before (full skill list in system prompt)~11.5K
After — names-only system prompt~2.0K
After — per-turn top-K descriptions~0.3K
Net per turn~2.3K (~9K saved)

Measured on a Hermes install with ~300 skills; savings scale with skill count.

Installation

Copy or symlink this directory into the Hermes plugins folder:

bash
# From this repo
ln -s "$(pwd)/plugins/skill-retrieval" ~/.hermes/plugins/skill-retrieval

# Or copy
cp -r plugins/skill-retrieval ~/.hermes/plugins/skill-retrieval

Ensure the plugin is enabled in Hermes (plugins under ~/.hermes/plugins/ with a valid plugin.yaml are typically auto-discovered). Restart the agent session so register() runs — it patches the system prompt and registers the pre_llm_call hook.

Dependencies (install into the Hermes Python env if missing):

bash
pip install pyyaml

Configuration

SettingDefaultHow to set
TOP_K6Env var SKILL_RETRIEVAL_TOP_K
BM25 k11.5Constant in scripts/bm25_retriever.py
BM25 b0.75Constant in scripts/bm25_retriever.py
bash
export SKILL_RETRIEVAL_TOP_K=8

Verify it's working

Phase 1 silently no-ops outside a full Hermes runtime, and the BM25 index can silently empty. After restart, check the Hermes logs.

Healthy start — look for these log lines:

  • BM25 index built: N docs …
  • Skill retrieval plugin registered (top_k=…, compact=true)

Degraded — these warnings mean it's not working:

  • Cannot locate prompt_builder — compaction skipped (Phase 1 failed, Phase 2 still works)
  • Cannot locate run_agent — patching prompt_builder only (Phase 1 partially

applied: callers resolving the builder via run_agent still get the full, uncompacted skill list, so the expected token saving does not materialise)

  • No active skills found for BM25 index (index is empty — zero retrieval injection)

How it works

  • Tokenizer — lowercases text, strips punctuation, splits on whitespace.
  • Corpus — each skill becomes "name: description" from SKILL.md YAML

frontmatter. Disabled skills from ~/.hermes/config.yaml are skipped.

  • Index — BM25 Okapi TF saturation + Lucene-style clipped IDF, stored as an

inverted index: `dict[str, list[tuple[int, float]]]` mapping each term to a posting list of (doc_index, precomputed BM25 weight).

  • Retrieve — for each unique query token present in the index, walk its

posting list and accumulate scores; sort by descending score (score > 0 only).

Performance

  • Index built once at plugin load (~8 ms for 200 skills on a CPU-only VM).
  • Retrieval is sub-millisecond (~0.03 ms mean for 200 skills). The inverted

index touches only documents that share a query term — no full-corpus scan.

  • No compiled dependencies. The plugin uses only the Python standard library

(plus pyyaml for config/frontmatter parsing). This removes a 154 MB numpy/scipy install and a ~573 ms import cost, which matters for subprocess spawning paths (e.g. a Claude Code UserPromptSubmit variant).

  • Failures in the hook return None (no injection) so the agent keeps working.

Dependencies

  • pyyaml

Limitations

  • BM25 is lexical, not semantic. Paraphrased queries that share few tokens

with a skill's description may rank poorly even when the intent matches.

  • Descriptions longer than 200 characters are truncated in the injected block;

use skill_view(name) for the full skill body.

  • Compaction requires Hermes's prompt_builder / run_agent modules; if they

cannot be imported, Phase 1 is skipped (Phase 2 still works if skills load).

  • The index is built once at load and never refreshes — skills added, edited,

or enabled mid-session are invisible until the agent restarts.

  • Phase 1 depends on Hermes internals (prompt_builder, run_agent) and can

break on a Hermes upgrade.

  • BM25 top-1 precision is soft: the best-matching skill is often not rank 1,

though it usually lands within the first few results. Ranking depends entirely on your own corpus and how its descriptions are worded, so TOP_K below ~5 is not recommended.

  • The stdlib index computes in float64 (the previous scipy version used

float32). Equal-scoring skills may order differently than before. This is harmless — the scores are genuine ties (~1e-6 difference) — but it is a real behaviour delta from the scipy version.

from this repository

More skills

All skills
moonlight-lupin
Community

claude-plugin-converter

Convert Claude Code plugins into self-contained Hermes plugins — discovery analysis then full conversion

installs
1
GitHub stars
62
Updated
Sep 7
moonlight-lupin
Community

clips-studio

Use when the user wants to "make a video/clip", "generate a video from text", "animate this image/photo", "create a marketing reel / social video / teaser", "do a 3D / parallax move", "pan/zoom/orbit a shot", or mentions fal.ai, Kling, Veo or Seedance for video. Staged fal.ai workflow — brainstorm, draft cheaply, produce the final at quality. Three modes: text-to-video, animate (still to motion), camera-move (push-in/pan/orbit). Not for still images (image-studio), slide decks, or charts. Honesty discipline: never depict real identifiable subjects via text-to-video.

installs
1
GitHub stars
62
Updated
Sep 7
moonlight-lupin
Community

decision-log

ADR-style decision journal for agents and teams. Create numbered decision records, track superseding chains, schedule periodic reviews, and search past decisions to avoid re-litigating settled questions.

installs
1
GitHub stars
62
Updated
Sep 7
moonlight-lupin
Community

deep-research

Autonomous multi-step deep research engine implementing an iterative Think → Search → Extract → Synthesize → Stop loop. The LLM drives every decision: what to search, what's relevant, what's missing, and when to stop. Produces a cited, magazine-quality report with inline citations, category- specific formatting, and research stats. Trigger when the user asks for "deep research", "research report on", "comprehensive analysis of", "look into X in depth", "write a report on X", or any question needing multi-source synthesis beyond a single search. For entity vetting/dossiers use entity-research; for news digests use news-monitoring; for source-grounded Q&A use notebooklm-mode.

installs
1
GitHub stars
62
Updated
Sep 7