fix(agent): gate skill-index demotion behind the opt-in focus mode (#44387)

The coding posture's names-only demotion of non-coding skill categories
(#44342) applied under the default auto mode, silently changing the skill
index for every user in a git repo. Index changes must be opt-in: demotion
now only fires under agent.coding_context=focus, alongside the toolset
collapse. auto/on leave the skill index untouched; focus semantics are
unchanged (demoted, never hidden; deny-list keeps coding-adjacent and
custom categories at full entries).
This commit is contained in:
Teknium 2026-06-11 10:00:57 -07:00 committed by GitHub
parent c7bfc938d5
commit 4d6a133a9f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 52 additions and 33 deletions

View file

@ -38,12 +38,13 @@ session (deferred), the same contract as ``/skills install`` vs ``--now``.
Activation (config ``agent.coding_context``):
* ``auto`` (default) posture (brief + snapshot + names-only demotion of
non-coding skill categories) on an interactive coding surface sitting in
a code workspace (git repo or recognised project root). Prompt-only;
toolsets untouched, no skill is ever hidden.
* ``auto`` (default) posture (brief + snapshot) on an interactive coding
surface sitting in a code workspace (git repo or recognised project root).
Prompt-only; toolsets and the skill index untouched.
* ``focus`` like ``auto``, but additionally collapses the toolset to the
``coding`` set + enabled MCP servers. Explicit opt-in for a lean schema.
``coding`` set + enabled MCP servers and demotes non-coding skill
categories to names-only in the prompt's skill index (no skill is ever
hidden). Explicit opt-in for a lean schema.
* ``on`` force the posture anywhere (incl. non-workspaces). Prompt-only.
* ``off`` disable entirely.
"""
@ -214,8 +215,8 @@ class ContextProfile:
(extension seam; not yet consumed by the router).
``memory_policy`` memory namespace/weighting hint (extension seam).
``compact_skill_categories`` skill categories DEMOTED to names-only in
the system-prompt skill index while this posture is
active. Never hidden: every skill name stays visible
the system-prompt skill index under the opt-in ``focus``
mode. Never hidden: every skill name stays visible
(so memory-anchored recall keeps working) only the
descriptions are dropped to cut index noise. Deny-list
semantics so unknown/custom categories keep full
@ -231,7 +232,7 @@ class ContextProfile:
# Skill categories that are clearly not part of a coding workflow. Demoted to
# names-only in the prompt's skill index while the coding posture is active
# names-only in the prompt's skill index under the opt-in ``focus`` mode only
# (deny-list — anything not listed here, incl. custom user categories, keeps
# full entries). Coding-adjacent categories (devops, github, mcp,
# data-science, diagramming, research, security, …) are intentionally absent.
@ -438,15 +439,22 @@ class RuntimeMode:
def compact_skill_categories(self) -> frozenset[str]:
"""Skill categories to demote to names-only in the prompt's skill index.
Demoted never hidden. An earlier revision fully pruned these
categories from the index, which caused silent capability loss in a
real workflow: agent-created skills are the model's accumulated
project memory (server-ops runbooks, learned pitfalls, ), and models
do not reliably reach for ``skills_list`` to rediscover what the
index stopped showing them. Names-only keeps every skill loadable on
recall while still cutting the description noise from the index.
Gated on the opt-in ``focus`` mode, like the toolset collapse: the
default posture leaves the skill index untouched. Users who didn't ask
for a lean prompt keep full entries for every category index changes
under ``auto`` proved too surprising in practice, even names-only ones
(a demoted description is information the model no longer weighs when
deciding what to load).
Demoted never hidden even under ``focus``. An earlier revision
fully pruned these categories from the index, which caused silent
capability loss in a real workflow: agent-created skills are the
model's accumulated project memory (server-ops runbooks, learned
pitfalls, ), and models do not reliably reach for ``skills_list`` to
rediscover what the index stopped showing them. Names-only keeps every
skill loadable on recall while still cutting the description noise.
"""
if not self.is_coding:
if not self.is_coding or self.config_mode != "focus":
return frozenset()
return frozenset(self.profile.compact_skill_categories)
@ -534,9 +542,11 @@ def coding_compact_skill_categories(
) -> frozenset[str]:
"""Skill categories the active posture demotes to names-only in the index.
Empty outside the coding posture. Demoted never hidden: every skill
name stays in the index and remains loadable via ``skill_view`` /
``skills_list``; only descriptions are dropped.
Empty outside the coding posture and outside the opt-in ``focus`` mode
the default posture never touches the skill index. Under ``focus``,
demoted never hidden: every skill name stays in the index and remains
loadable via ``skill_view`` / ``skills_list``; only descriptions are
dropped.
"""
return resolve_runtime_mode(
platform=platform, cwd=cwd, config=config

View file

@ -191,9 +191,10 @@ def build_system_prompt_parts(agent: Any, system_message: Optional[str] = None)
)
if toolset
}
# Coding posture demotes non-coding skill categories to names-only in
# the index (never hidden — skill_view/skills_list reach everything,
# and every name stays visible for memory-anchored recall).
# Focus mode (opt-in) demotes non-coding skill categories to
# names-only in the index (never hidden — skill_view/skills_list
# reach everything, and every name stays visible for recall). The
# default coding posture leaves the index untouched.
_compact_cats = frozenset()
try:
from agent.coding_context import coding_compact_skill_categories

View file

@ -877,7 +877,9 @@ DEFAULT_CONFIG = {
# Toolsets are never touched; messaging platforms
# unaffected.
# "focus" — auto + collapse the toolset to the lean coding
# set (+ enabled MCP servers). Explicit opt-in.
# set (+ enabled MCP servers) + demote non-coding
# skill categories to names-only in the prompt's
# skill index. Explicit opt-in.
# "on" — force the prompt posture everywhere.
# "off" — disable entirely.
"coding_context": "auto",

View file

@ -368,21 +368,27 @@ class TestProfiles:
assert cc.GENERAL_PROFILE.toolset is None
assert cc.GENERAL_PROFILE.guidance == ""
def test_skill_demotion_scoped_to_coding_posture(self, tmp_path):
# Coding posture demotes clearly-non-coding categories to names-only
# in the index (never hides them — agent-created skills are the
# model's project memory and must stay recallable by name).
# Coding-adjacent categories keep full entries (deny-list semantics).
def test_skill_demotion_gated_on_focus(self, tmp_path):
# Names-only demotion is opt-in via focus mode — the default (auto)
# and forced (on) postures leave the skill index untouched. Under
# focus, clearly-non-coding categories are demoted (never hidden) and
# coding-adjacent ones keep full entries (deny-list semantics).
_git_init(tmp_path)
for raw in ("auto", "on", "focus"):
for raw in ("auto", "on"):
mode = cc.resolve_runtime_mode(
platform="cli", cwd=tmp_path, config={"agent": {"coding_context": raw}}
)
assert mode.is_coding is True
compact = mode.compact_skill_categories()
assert "social-media" in compact and "smart-home" in compact
for kept in ("github", "devops", "software-development", "data-science"):
assert kept not in compact
assert mode.compact_skill_categories() == frozenset()
focus = cc.resolve_runtime_mode(
platform="cli", cwd=tmp_path,
config={"agent": {"coding_context": "focus"}},
)
assert focus.is_coding is True
compact = focus.compact_skill_categories()
assert "social-media" in compact and "smart-home" in compact
for kept in ("github", "devops", "software-development", "data-science"):
assert kept not in compact
# General posture demotes nothing.
general = cc.resolve_runtime_mode(platform="telegram", cwd=tmp_path, config={})
assert general.compact_skill_categories() == frozenset()