hermes skill router
July 2, 2026 · View on GitHub
Hermes plugin that indexes skills with Jina embeddings and injects the most relevant skill pointers at the right time.
What it does
- Embeds each user message with jina-embeddings-v5-text-small
- Cosine-matches against a pre-built skill index
- Optionally reranks ambiguous matches with jina-reranker-v3
- Keeps reranking off the hot path unless the gate says cosine is uncertain
- Gate decisions are logged at debug level so rerank frequency can be measured before changing defaults
- Falls back safely if the index or API is unavailable
Why it exists
Hermes skill libraries grow fast. The model should not have to guess from a long list of unrelated skills every turn. This plugin narrows the candidate set before the LLM sees it.
Behavior
Default mode:
- cosine-only
- gated reranking only when scores are ambiguous
Overrides:
SKILL_ROUTER_RERANK=1→ always rerankSKILL_ROUTER_RERANK=0→ never rerank
Files
__init__.py— hook entry point and gating logicbuild_index.py— scans skills and buildsskill_index.jsonjina_client.py— embedding clientreranker_client.py— optional second-stage rerankertests/test_router.py— regression tests
Local usage
The plugin is designed to be loaded by Hermes from ~/.hermes/plugins/hermes-skill-router/.
To rebuild the index manually from a shell:
python3 - <<'PY'
import importlib.util, os, re, sys
with open(os.path.expanduser('~/.hermes/.env')) as f:
m = re.search(r'JINA_API_KEY=(.+)', f.read())
os.environ['JINA_API_KEY'] = m.group(1).strip().strip('"').strip("'")
pkg_path = os.path.expanduser('~/.hermes/plugins/hermes-skill-router')
spec = importlib.util.spec_from_file_location(
'skill_router',
os.path.join(pkg_path, '__init__.py'),
submodule_search_locations=[pkg_path],
)
pkg = importlib.util.module_from_spec(spec)
sys.modules['skill_router'] = pkg
spec.loader.exec_module(pkg)
from skill_router.build_index import build_index, load_index
build_index(force=True)
idx = load_index()
print(f"rebuilt {len(idx['skills'])} skills with model {idx['model']}")
PY
Hermes also rebuilds the index automatically when the skills tree changes.
Release notes
- Keep generated artifacts out of version control
- Do not commit
skill_index.json - Verify the test file runs without live API access
Changelog
v1.1.0
Fixes:
- Runtime env override:
SKILL_ROUTER_RERANKis now read at call time, not import time. Changing the env var mid-session takes effect immediately without restarting Hermes. - Thread safety: All session state (
_suggested_skills,_turn_counts,_last_seen_turns) and index management (_index_cache) are now protected bythreading.Lock. Prevents TOCTOU races in concurrent multi-platform gateway use. - Bounded session tracking: Added TTL sweep (inactive sessions cleaned after 50 turns) and hard cap (max 100 tracked sessions with oldest-eviction). Prevents unbounded memory growth in long-running processes.
- Model/dimension validation: Index load now validates
modelanddimensionsagainst currentjina_clientconfig. Stale indexes from a different embedding model are detected and rebuilt automatically. - Dedup window reduced: Suppression window reduced from 8 turns to 4 turns. Prevents skills from disappearing mid-task on multi-turn work that needs the same skill across turns.
- Empty docs crash fix:
_batch_cosineno longer crashes on empty document lists (numpyAxisError).
Tests:
- Expanded from 4 tests to 83 tests across 14 test classes
- Full coverage of: conversational filter branches, rerank gate logic (forced on/off at runtime, case-insensitive normalization), matching pipeline (synthetic index, rerank failure fallback, rerank success reordering, reranker out-of-bounds index filtering, all-identical-scores edge case, candidate cap, sorting), cosine similarity edge cases (zero vectors, empty docs, batch consistency), session tracking (suppression, expiry, TTL sweep, hard cap eviction), thread safety (concurrent turn increments, concurrent mark/get), index validation via load_index (model mismatch, dimension mismatch, missing model, missing dimensions, corrupt JSON, missing file — all with tempfile isolation), graceful degradation (embed failure, missing index, no candidates, successful match formatting), query builder (history, list content, truncation, None handling), embed batch boundaries (64 exact, 65 split, 128 split), embed/rerank client edge cases, _maybe_rebuild (throttle skip/allow, hash change trigger, failure, counter advance), and env var test hygiene.