feat(prompt): agent.skills_catalog_mode to compact the skills catalog on chat surfaces - #72200
feat(prompt): agent.skills_catalog_mode to compact the skills catalog on chat surfaces#72200wernerhp wants to merge 8 commits into
Conversation
There was a problem hiding this comment.
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 inagent/coding_context.py, defaulting by platform. - Adds an
ALL_SKILL_CATEGORIESsentinel inagent/prompt_builder.pyto 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.
| _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, | ||
| ) |
| # 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) |
| 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 |
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. |
2069348 to
4d29e53
Compare
|
All three points are addressed on the current branch:
Rebased onto current |
4d29e53 to
a1d2209
Compare
teknium1
left a comment
There was a problem hiding this comment.
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-865documentsagent.skills_catalog_mode, but the PR does not add it toDEFAULT_CONFIG["agent"].hermes_cli/config.py:3312-3326builds resolved config from that schema, whilehermes_cli/config.py:4680-4765validates dotted settings against it. The option works if manually present in YAML, buthermes config gethas no default andhermes config setwarns that this documented setting is unknown.
Suggested changes
- Register
skills_catalog_mode: ""inhermes_cli/config_defaults.pyand add resolved-config/config-validation coverage alongside the existing renderer tests.
Automated hermes-sweeper review.
| # coding). Overridable per-profile in profiles/<name>/config.yaml. Config | ||
| # only - no HERMES_* env var. Takes effect next session. | ||
| # skills_catalog_mode: '' | ||
|
|
There was a problem hiding this comment.
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.
…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.
e0ce27f to
7e976ba
Compare
# Conflicts: # agent/system_prompt.py
# Conflicts: # agent/system_prompt.py
# Conflicts: # tests/agent/test_system_prompt.py
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
Changes Made
agent/coding_context.py: new pure resolverresolve_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_CATEGORIESsentinel (a distinct emptyfrozensetsubclass) for names-only; renderer short-circuits "all present categories demoted" and keys the LRU cache distinctly fromfull.agent/system_prompt.py: resolve the catalog set alongside the coding-posture set and union both into the singlebuild_skills_system_prompt(compact_categories=...)call.cli-config.yaml.example: documents the newagent.skills_catalog_modekey. No newHERMES_*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
Contract tests:
compact, coding surface isfull.fullis still present undercompactandnames-only(never-hide invariant).compactdrops only non-coding descriptions;names-onlydrops all, each category rendered as a[names only]line.HERMES_*env var /os.environread for the mode.T6, measured catalog reduction
Measured against a representative 204-skill catalog,
<available_skills>block only, tokens viatiktokencl100k_base:fullcompactnames-onlyThe reduction from
compactvaries with the category mix. On a coding-heavy catalog (manygithub/gitlab/devops/software-developmentskills thatcompactdeliberately keeps full),compactreclaims ~17.5%; the full cut comes fromnames-onlyat ~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
cli-config.yaml.exampleupdated for the new config key.env-is-secrets-only rule)