Skip to content

fix(skills): name existing categories when a category filter matches nothing - #90082

Open
404Brothers wants to merge 1 commit into
NousResearch:mainfrom
404Brothers:fix/skills-list-category-miss
Open

fix(skills): name existing categories when a category filter matches nothing#90082
404Brothers wants to merge 1 commit into
NousResearch:mainfrom
404Brothers:fix/skills-list-category-miss

Conversation

@404Brothers

Copy link
Copy Markdown

What does this PR do?

skills_list() built its categories list from the already-filtered skill set, so a
category filter that matched nothing returned:

{"success": true, "skills": [], "categories": [], "count": 0}

That reply cannot distinguish "the category you guessed is wrong" from "no skills are
installed"
, and it never names the category the skill is actually installed under. The
documented Level 0 contract is skills_list() → [{name, description, category}, ...]
(Progressive Disclosure),
so category is the only handle the agent has — and on a miss the reply took that handle away.

Measured 2026-08-07 on a live task: a model looking for a transcription skill called
skills_list(category="audio") while the skill was installed under media, read the empty
reply as "the skill is missing", wrote its own script in /tmp and installed packages into
an unrelated venv. The skill was installed and enabled the whole time.

The fix keeps the failure mode observable instead of silent: categories are computed before
filtering, and an empty filtered result says explicitly that this is not proof of absence.

Related Issue

No existing upstream issue or PR covers this. Searched before opening, as
CONTRIBUTING
asks:

gh search issues --repo NousResearch/hermes-agent "skills_list category"
gh search prs --repo NousResearch/hermes-agent --state open  "skills_list categories filter"
gh search prs --repo NousResearch/hermes-agent --state closed "skills_list categories filter"

Nearest hits are about index size and routing (#26786, #22620, #37227, #23663) and about empty
category directories on install (#34237) — different defect, different code path.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • tools/skills_tool.pycategories is collected from the full skill set before the
    category filter is applied; an empty filtered result carries a message telling the caller
    to check categories and retry without the filter before concluding anything. A filter that
    did match stays untouched, so the message keeps its signal.
  • hermes_cli/skills_hub.pyhermes skills inspect <name> answered
    No skill named X found in any source for a skill that is installed locally but offered by
    no hub source. Formally true (hub commands search remote sources) and misleading at once. It
    now reports the local installation and its path; a genuinely unknown name still gets the
    not-found answer. Same for short-name resolution (_resolve_short_name). Shared helper
    _find_local_skill() accepts both name and category/name.
  • tests/tools/test_skills_tool.py — two assertions lock the contract in both directions.

How to Test

  1. A filter miss must still name every existing category and say so explicitly; a filter hit
    must stay clean. Against a real skills directory (125 skills, 20 categories):

    category='audio'  count=0   categories=20  'media' present: True
        message: No skills in category 'audio'. This does not mean the skill is missing —
                 check 'categories' above and retry without the filter before concluding anything.
    category='media'  count=5   categories=20  message absent   ← warning keeps its signal
    no filter         count=125 categories=20  message absent
    

    Before this change the first line was count=0 categories=0 with no message — the exact
    input the model in the 2026-08-07 incident acted on.

  2. CLI half — installed skill vs invented name:

    $ hermes skills inspect transcribe-roles
    'transcribe-roles' is installed locally, but no hub source offers it.
      path: /home/…/.hermes/skills/media/transcribe-roles
      Hub commands look at remote sources only. …
    
    $ hermes skills inspect meeting-transcription
    Error: No skill named 'meeting-transcription' found in any source.
    

    The invented name from the original incident is still honestly reported as absent.

  3. Tests. pytest tests/tools/test_skills_tool.py -q43 passed (41 before, plus the two
    new assertions). Widened to every skill-related test file in the tree, so both changed
    modules are covered:

    pytest $(find tests -name 'test_*skill*.py') -q      # 88 files
      this branch      → 2 failed, 1095 passed, 1 skipped
      upstream/main    → 2 failed, 1093 passed, 1 skipped   ← same two failures
    

    The two failures are pre-existing and unrelated — test_skill_bundle_provenance.py passes
    7 passed when run alone on both the branch and the base, so it is cross-test pollution
    inside a shared pytest session, which is exactly what scripts/run_tests.sh avoids by
    isolating each file in its own subprocess. Net effect of this PR: +2 passing tests, nothing
    else moved.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass — ran the 88-file skill-related subset
    instead (see "How to Test" item 3); the full 3104-file suite is left to CI
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Ubuntu on WSL2 (kernel 6.18, x86_64), Python 3.11

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

Notes on the boxes above: docs are N/A because neither the category parameter nor the
categories field is described in the
Tools Reference
(skills_list is documented as "List available skills (name + description)"), and the
documented Level 0 shape is unchanged — only the miss path stops lying. No config keys, no
architecture change. The skills_list tool schema is unchanged: same parameters, same
fields; message was already the field used for the "no skills at all" case, this reuses it.
Cross-platform: both changes are pure string/dict work, and paths are built with pathlib.

…nothing

skills_list() built its `categories` list from the already-filtered skill set,
so a filter that matched nothing returned {"skills": [], "categories": [],
"count": 0}. That reply cannot distinguish "the category you guessed is wrong"
from "no skills are installed", and it never names the category the skill is
actually installed under.

Measured 2026-08-07 on a live task: a model looking for a transcription skill
called skills_list(category="audio") while the skill was installed under
"media", read the empty reply as "the skill is missing", then wrote its own
script in /tmp and installed packages into an unrelated venv.

- Categories are now collected from the full set before filtering, and an empty
  filtered result carries an explicit message telling the caller to check
  `categories` and retry without the filter.
- `hermes skills inspect <name>` answered "No skill named X found in any
  source" for a skill that is installed locally but offered by no hub source.
  Formally true - hub commands search remote sources - but it reads as "this
  skill does not exist". It now reports the local installation and its path,
  while a genuinely unknown name still gets the not-found answer.

tests/tools/test_skills_tool.py locks both halves of the contract: a filter
miss must name every existing category and say so explicitly, and a filter hit
must stay clean so the warning keeps its signal.
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/cli CLI entry point, hermes_cli/, setup wizard tool/skills Skills system (list, view, manage) labels Aug 19, 2026
@Enough1122

Copy link
Copy Markdown
Contributor

AI code review — automated review; please use your judgment.

Exactly the right fix for an agent-facing UX trap: computing categories from the unfiltered set means a missed category filter now returns the full category list plus an explicit "this does not mean the skill is missing — retry without the filter" message, while hits stay clean (no spurious warning). The hub-side additions are equally good — distinguishing "installed locally, hub doesn't offer it" (with the on-disk path and a pointer to skill_view) from a genuine not-found kills the second measured failure mode where users concluded an installed skill didn't exist. Both behaviors are pinned by tests including the negative ("a hit carries no warning").

No blocking issues found.

Nit (hermes_cli/skills_hub.py _find_local_skill:~866–880): if two categories contain a skill with the same bare name, the first match wins arbitrarily and the printed path may point at the other one; returning all matches (or preferring exact category when the identifier carried one) would make the hint precise — cosmetic, since it's only used to say "it's installed somewhere".

— reviewer-a · automated agent review (Hermes week-review)

@404Brothers

Copy link
Copy Markdown
Author

Thanks for the review! I’ll keep this PR focused on the current fix for now. I can address this edge case too if needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard P3 Low — cosmetic, nice to have tool/skills Skills system (list, view, manage) type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants