feat(skills): compact system prompt index with usage-driven pinned skills - #14319
Closed
sontianye wants to merge 1 commit into
Closed
feat(skills): compact system prompt index with usage-driven pinned skills#14319sontianye wants to merge 1 commit into
sontianye wants to merge 1 commit into
Conversation
…ills
The skills system already implements progressive disclosure across three
tiers (skills_tool.py docstring), but the system prompt (tier 0) was
duplicating tier 1 — injecting the full name+description list for every
installed skill on every turn.
With 71 bundled skills this costs ~2500 tokens/turn of system prompt
space, and the descriptions are truncated to 60 chars — worse than what
skills_list already returns (236 chars avg). The cost scales linearly
with no upper bound as users install optional skills, plugins, and
external directories.
This commit makes each tier do its own job:
**Tier 0 (system prompt)** — compact awareness trigger (~500 tokens):
- Total skill count and category summary (one line)
- <pinned_skills> block: usage-driven top skills within a fixed token
budget, falling back to one representative per category when no
usage data exists (cold start)
- <skill_categories> block: top-level categories with counts
- Search guidance pointing to skills_list(query=...)
**Tier 1 (skills_list tool)** — gains a `query` parameter for keyword
search across name, description, and category. This is the mechanism
that replaces the removed full index — the agent searches on demand
with richer matching information than the old truncated descriptions.
Token savings: ~2500 → ~500 tokens/turn (80% reduction).
Matching quality: improves — search returns full descriptions vs 60-char
truncations.
Implementation details:
- _get_pinned_skills(): queries state.db for skill_view call frequency
(reusing the insights.py extraction pattern), with a char budget cap
and category-representative fallback for cold start
- _get_usage_epoch(): coarse hourly timestamp in cache key so pinned
skills refresh at most once/hour without cache thrashing
- skills_list(query=...): simple case-insensitive substring match on
name, description, and category — no new dependencies
- All existing filtering (platform, disabled, conditional activation)
unchanged
- build_skills_system_prompt() signature unchanged — callers unaffected
Contributor
|
Thanks for the work @sontianye — closing without merging. The core token-saving idea is real (the skills block on a fully-loaded install is ~3,500 tokens), but a few things in the implementation don't fit how we want skills to work:
The If you want to take another swing at this, the path I'd suggest: read pinned candidates from |
15 tasks
2 tasks
sontianye
added a commit
to sontianye/hermes-agent
that referenced
this pull request
Jul 13, 2026
…erwise The current index shows every skill with its full description, costing ~3,500 tokens on a fully-loaded install. This PR reduces that to a fixed-budget format that still honours the demote-never-hide contract: every skill name stays visible, but descriptions appear only for skills explicitly pinned by the user or agent via the skill_usage sidecar. How it works - _get_pinned_candidates() reads ~/.hermes/skills/.usage.json via tools/skill_usage.agent_created_report(), filters pinned=True && state != archived, orders by activity_count descending, and trims to _PINNED_SKILLS_CHAR_BUDGET (1200 chars) so the description budget stays bounded regardless of how many skills are pinned. - The index loop shows "name: description" for pinned skills and "name" for everything else — no entries ever removed, no skills_list() needed for discovery. - Posture-driven compact_categories demotion (names-only lines for non-coding categories in coding posture) is unchanged. - _skill_usage_epoch() adds sidecar mtime_ns to the cache key so pin state changes take effect on the next prompt build automatically. No new settings. build_skills_system_prompt() signature unchanged. Closes NousResearch#14319
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
queryparameter toskills_listtool for keyword search across name, description, and categorystate.db, with category-representative fallback for cold startProblem
The skills system implements progressive disclosure across three tiers (
skills_tool.py:9), but tier 0 (system prompt) was duplicating tier 1 — injecting the full name+description list for all 71 bundled skills on every turn.skills_listreturns 236 chars avg — better matching qualitySolution
Make each tier do its own job:
Tier 0 (system prompt) — compact awareness trigger:
Tier 1 (
skills_list) — gainsqueryparam for precise search with full descriptions.Key design decisions
state.dbforskill_viewcall frequency in the last 30 days (reusing theinsights.pyextraction pattern), with a char budget cap (~300 tokens)build_skills_system_prompt()callers are unaffectedResults
Changes
agent/prompt_builder.py_query_skill_usage(),_get_usage_epoch(),_get_pinned_skills(); rewrite rendering inbuild_skills_system_prompt()tools/skills_tool.pyqueryparam toskills_list(); update schema and handlertests/agent/test_prompt_builder.pyTestCompactSkillsPrompt(4 tests)tests/tools/test_skills_tool.pyTest plan
TestBuildSkillsSystemPrompttests passTestCompactSkillsPrompttests pass (usage-driven pinning, category fallback, budget cap, format structure)TestSkillsListQuerytests pass (name/description/category match, case insensitivity, combination with category filter)Checklist