Skip to content

feat(prompt): agent.skills_catalog_mode to compact the skills catalog on chat surfaces - #72200

Open
wernerhp wants to merge 8 commits into
NousResearch:mainfrom
wernerhp:feat/skills-catalog-mode
Open

feat(prompt): agent.skills_catalog_mode to compact the skills catalog on chat surfaces#72200
wernerhp wants to merge 8 commits into
NousResearch:mainfrom
wernerhp:feat/skills-catalog-mode

Conversation

@wernerhp

@wernerhp wernerhp commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

The messaging/chat system prompt re-sends a large stable cache prefix on every turn, and the <available_skills> catalog block is its single largest compressible line item. The renderer already has a names-only demotion lever (build_skills_system_prompt(compact_categories=...)), but it is gated behind the coding posture and the interactive-coding surface set, so it never fires on chat/messaging surfaces, which is exactly where the tokens burn.

This adds agent.skills_catalog_mode (full | compact | names-only), resolved once from session-fixed inputs (platform, config) and unioned with the existing coding-posture demotion set before the single render call, so the catalog stays a single cache-safe LRU entry that is byte-identical for the life of a conversation.

  • full: no demotion (default for interactive coding surfaces; today's behaviour).
  • compact: demote the non-coding category deny-list to names-only (default for messaging surfaces).
  • names-only: demote every present category to names-only (max compaction; opt-in).

Never hides a skill: names-only demotion drops the description but keeps every name visible and loadable via skill_view / skills_list.

Type of Change

  • New feature (non-breaking change that adds functionality)
  • Refactor (no behavior change)
  • Tests (adding or improving test coverage)

Changes Made

  • agent/coding_context.py: new pure resolver resolve_skills_catalog_compaction(*, platform, config) + _skills_catalog_mode() (config precedence over per-surface default). Reads no turn-varying state.
  • agent/prompt_builder.py: ALL_SKILL_CATEGORIES sentinel (a distinct empty frozenset subclass) for names-only; renderer short-circuits "all present categories demoted" and keys the LRU cache distinctly from full.
  • agent/system_prompt.py: resolve the catalog set alongside the coding-posture set and union both into the single build_skills_system_prompt(compact_categories=...) call.
  • cli-config.yaml.example: documents the new agent.skills_catalog_mode key. No new HERMES_* env var, config-only.
  • tests/agent/test_coding_context.py, tests/agent/test_prompt_builder.py: behavior-contract tests T1 to T7.

How to Test

pytest tests/agent/test_coding_context.py tests/agent/test_prompt_builder.py -q
# 237 passed

Contract tests:

  • T1 chat default is compact, coding surface is full.
  • T2 every name present under full is still present under compact and names-only (never-hide invariant).
  • T3 compact drops only non-coding descriptions; names-only drops all, each category rendered as a [names only] line.
  • T4 determinism: resolver + renderer are byte-identical for identical inputs (cache-safety).
  • T5 config precedence: profile/base override the per-surface default; empty string falls through.
  • T6 measured saving gate (see below).
  • T7 no HERMES_* env var / os.environ read for the mode.

T6, measured catalog reduction

Measured against a representative 204-skill catalog, <available_skills> block only, tokens via tiktoken cl100k_base:

mode chars tokens token reduction
full 19,786 4,678 n/a
compact 16,176 3,859 17.5%
names-only 5,295 1,390 70.3%

The reduction from compact varies with the category mix. On a coding-heavy catalog (many github / gitlab / devops / software-development skills that compact deliberately keeps full), compact reclaims ~17.5%; the full cut comes from names-only at ~70.3%, which strips coding descriptions too and is therefore opt-in, not the chat default. The synthetic-catalog unit test (test_t6_compact_shrinks_block_at_least_50pct) asserts >=50% on a non-coding-heavy catalog to guard the mechanism; the table above is a real-profile figure.

Checklist

  • Tests added and passing (237 passed)
  • cli-config.yaml.example updated for the new config key
  • No new env var introduced (config-only, per repo .env-is-secrets-only rule)
  • Cache-safety preserved: resolver reads only session-fixed inputs; result joins the existing LRU cache key

Copilot AI review requested due to automatic review settings July 26, 2026 20:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a session-stable agent.skills_catalog_mode configuration to compact the <available_skills> system-prompt catalog on chat/messaging surfaces, reducing token spend while preserving prompt-cache safety and the “never hide skill names” invariant.

Changes:

  • Introduces agent.skills_catalog_mode (full | compact | names-only) resolution in agent/coding_context.py, defaulting by platform.
  • Adds an ALL_SKILL_CATEGORIES sentinel in agent/prompt_builder.py to represent “demote all categories to names-only” and key the renderer cache distinctly.
  • Updates system prompt assembly to union coding-posture demotion with catalog-mode demotion, plus adds tests and documents the new config key.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
agent/coding_context.py Adds resolver for agent.skills_catalog_mode and exposes compact-category selection for prompt building.
agent/prompt_builder.py Introduces the ALL_SKILL_CATEGORIES sentinel and updates caching + demotion logic accordingly.
agent/system_prompt.py Unions coding-posture and catalog-mode demotion sets and passes them into the skills catalog renderer.
cli-config.yaml.example Documents the new agent.skills_catalog_mode configuration key and intended behavior.
tests/agent/test_coding_context.py Adds contract tests for mode resolution defaults, config precedence, and determinism.
tests/agent/test_prompt_builder.py Adds renderer/caching/compaction tests for compact and names-only modes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread agent/system_prompt.py
Comment on lines +356 to 361
_resolved_cats = _union_compact_categories(_compact_cats, _catalog_cats)
skills_prompt = _r.build_skills_system_prompt(
available_tools=agent.valid_tool_names,
available_toolsets=avail_toolsets,
compact_categories=_compact_cats or None,
compact_categories=_resolved_cats or None,
)
Comment thread cli-config.yaml.example
Comment on lines +837 to +842
# full - every category keeps full descriptions (default for
# interactive coding surfaces; today's behaviour)
# compact - non-coding categories demoted to names-only (default for
# messaging/chat surfaces; ~55-70% catalog reduction)
# names-only - every category demoted to names-only (max compaction;
# opt-in for token-critical high-volume profiles)
Comment thread tests/agent/test_prompt_builder.py Outdated
Comment on lines +1851 to +1860
def test_t7_no_env_var_for_mode(self):
"""The mode is config-only — no HERMES_* env read for it."""
import inspect
from agent import coding_context as cc

src = inspect.getsource(cc._skills_catalog_mode)
src += inspect.getsource(cc.resolve_skills_catalog_compaction)
assert "os.environ" not in src
assert "getenv" not in src
assert "HERMES_" not in src
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint tool/skills Skills system (list, view, manage) area/config Config system, migrations, profiles sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades needs-decision Awaiting maintainer decision before any implementation labels Jul 26, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related: #12015 uses a minimal/no-index gateway policy, while this PR retains every skill name and demotes descriptions; #40993 proposes an older prompt-mode path. These are distinct mechanisms but require a maintainer decision on the messaging-surface discovery contract.

@wernerhp
wernerhp force-pushed the feat/skills-catalog-mode branch from 2069348 to 4d29e53 Compare July 30, 2026 06:04
@wernerhp

wernerhp commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

All three points are addressed on the current branch:

  1. Catalog-reduction figure: the "~55-70%" claim is replaced. The comment now reads "catalog reduction varies with the category mix, ~17.5% measured on the default profile," matching the config-example docs.
  2. Sentinel coercion: _resolved_cats is forwarded verbatim to build_skills_system_prompt; the empty-but-meaningful ALL_SKILL_CATEGORIES frozenset is no longer collapsed by or None. build_skills_system_prompt identity-checks the sentinel, so full, compact, and names-only all resolve correctly.
  3. Test methodology: TestSkillsCatalogModeWiring is a behavioral E2E test. It populates a temp HERMES_HOME, renders the actual system prompt, and asserts on the rendered output (pinning the empty-sentinel regression), not on source text.

Rebased onto current main and re-pushed.

@wernerhp
wernerhp force-pushed the feat/skills-catalog-mode branch from 4d29e53 to a1d2209 Compare July 30, 2026 06:58
@wernerhp wernerhp changed the title feat(prompt): agent.skills_catalog_mode — compact the skills catalog on chat surfaces (ADR-0041) feat(prompt): agent.skills_catalog_mode to compact the skills catalog on chat surfaces Jul 30, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for preserving every skill name while targeting a real prompt-cost issue. Current main only demotes categories in explicit coding focus mode (agent/coding_context.py:564-584), and agent/system_prompt.py:299-325 still sends the full catalog for messaging/general sessions.

Problems

  • cli-config.yaml.example:850-865 documents agent.skills_catalog_mode, but the PR does not add it to DEFAULT_CONFIG["agent"]. hermes_cli/config.py:3312-3326 builds resolved config from that schema, while hermes_cli/config.py:4680-4765 validates dotted settings against it. The option works if manually present in YAML, but hermes config get has no default and hermes config set warns that this documented setting is unknown.

Suggested changes

  • Register skills_catalog_mode: "" in hermes_cli/config_defaults.py and add resolved-config/config-validation coverage alongside the existing renderer tests.

Automated hermes-sweeper review.

Comment thread cli-config.yaml.example
# coding). Overridable per-profile in profiles/<name>/config.yaml. Config
# only - no HERMES_* env var. Takes effect next session.
# skills_catalog_mode: ''

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please register this documented setting in DEFAULT_CONFIG["agent"] as well. load_config_readonly() begins from that schema, and config-key validation also derives recognized nested keys from it; without a default, hermes config get agent.skills_catalog_mode has no resolved value and hermes config set warns that the documented key is unknown.

@teknium1 teknium1 added sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 30, 2026
…alog on chat surfaces

The messaging/chat system prompt re-sends a large stable cache prefix on
every turn, and the <available_skills> catalog block is its single largest
compressible line item. The renderer already has a names-only demotion lever
(build_skills_system_prompt(compact_categories=...)), but it is gated behind
the coding posture and the interactive-coding surface set, so it never fires
on chat/messaging surfaces, which is exactly where the tokens burn.

Add agent.skills_catalog_mode (full | compact | names-only), resolved once
from session-fixed inputs (platform, config) and unioned with the existing
coding-posture demotion set before the single render call, so the catalog
stays a single cache-safe LRU entry that is byte-identical for the life of a
conversation. Never hides a skill: names-only keeps every name visible and
loadable via skill_view/skills_list. Config-only, no new env var.
The names-only sentinel is an empty frozenset subclass; `_resolved_cats or
None` collapsed it to None, dropping the demotion. Forward it verbatim and
let build_skills_system_prompt identity-check the sentinel.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint needs-decision Awaiting maintainer decision before any implementation P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades tool/skills Skills system (list, view, manage) type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants