Don't emit empty <customizationsUpdate> on terminal steering turns (#318128)

Don't emit empty <customizationsUpdate> on steering turns

Fixes #317763

Terminal steering requests sent from runInTerminalTool.ts don't forward an
instructionContext, so chatServiceImpl short-circuits collectInstructions()
to [] and the vscode.customizations.index variable is absent for those turns.

The old freeze/drift consumer in agentPrompt.tsx treated that absence as
'all customizations removed' via 'effectiveCurrent = currentValue ?? ''',
emitting an empty <customizationsUpdate> block whose own text says it
'supersedes the system prompt' — falsely telling the model that all
skills/instructions/agents had been wiped mid-task and churning the cache
tail.

Gate drift emission on 'currentValue !== undefined' so an absent variable
preserves the frozen system-prompt listing instead of falsely signalling
removal. Also graduates the experimental
'github.copilot.chat.freezeCustomizationsIndex' setting (it was already
default true on onExp).
This commit is contained in:
Bhavya U
2026-05-23 18:45:49 -07:00
committed by GitHub
parent 8e2b261364
commit 7d45e9906e
4 changed files with 24 additions and 41 deletions

View File

@@ -4294,16 +4294,6 @@
"advanced"
]
},
"github.copilot.chat.freezeCustomizationsIndex": {
"type": "boolean",
"default": true,
"tags": [
"advanced",
"experimental",
"onExp"
],
"description": "%github.copilot.config.freezeCustomizationsIndex%"
},
"github.copilot.chat.installExtensionSkill.enabled": {
"type": "boolean",
"default": false,

View File

@@ -340,7 +340,6 @@
"github.copilot.config.anthropic.promptCaching.extendedTtl": "Use the extended (1 hour) prompt cache TTL on tools and system blocks for the Anthropic Messages API. Applied to Claude Opus 4.5/4.6/4.7 and Sonnet 4.5/4.6 variants; other models keep the default 5 minute TTL even when this setting is enabled.\n\n**Note**: This is an experimental feature. Only the main agent conversation is eligible — inline chat, terminal chat, notebook chat, and subagent requests are excluded.",
"github.copilot.config.anthropic.promptCaching.extendedTtlMessages": "Also extend the 1 hour prompt cache TTL to message-level breakpoints. Requires `chat.anthropic.promptCaching.extendedTtl` to be enabled; has no effect on its own.\n\n**Note**: This is an experimental feature.",
"github.copilot.config.modelCapabilityOverrides": "Per-model capability overrides keyed by model id, intended for evaluating preview and tenanted models against an existing model's capability profile. For each model id, declare an aliased `family`. Setting `family` to a known production family (e.g. `\"claude-opus-4.7\"`) makes the model receive that family's full capability profile — Anthropic family detection, latest Opus prompt, multi-replace tools, tool search, context editing, extended cache TTL — without a code change.\n\n**Note**: This is an advanced setting for evaluation use; it is not intended for regular end-user configuration.",
"github.copilot.config.freezeCustomizationsIndex": "Freeze the bundled `<instructions>`, `<skills>`, and `<agents>` listing in the system prompt at the first turn of a conversation and reuse it on every subsequent turn. Prevents per-turn churn (e.g. the active mode swapping which subagent entry is listed, or async experimentation flipping a skill in or out) from invalidating the prompt cache. When the listing changes mid-conversation, the updated set is appended to the latest user message so the model still sees instructions, skills, or agents that became available or were removed.\n\n**Note**: This is an experimental feature.",
"github.copilot.config.useResponsesApi": "Use the Responses API instead of the Chat Completions API when supported. Enables reasoning and reasoning summaries.\n\n**Note**: This is an experimental feature that is not yet activated for all users.\n\n**Important**: For Custom Endpoint models, the API type is independent of this setting and is determined per-model via the `apiType` property, or inferred from the `url` path when omitted.",
"github.copilot.config.responsesApiReasoningSummary": "Sets the reasoning summary style used for the Responses API. Requires `#github.copilot.chat.useResponsesApi#`.",
"github.copilot.config.responsesApiContextManagement.enabled": "Enables context management for the Responses API. Requires `#github.copilot.chat.useResponsesApi#`.",

View File

@@ -230,8 +230,7 @@ export class AgentPrompt extends PromptElement<AgentPromptProps> {
}
/**
* When the experimental `FreezeCustomizationsIndex` setting is enabled,
* snapshot the customizations-index variable on the first turn of the
* Snapshot the customizations-index variable on the first turn of the
* conversation and reuse it for every subsequent turn. Stops per-turn
* churn in the bundled `<instructions>`/`<skills>`/`<agents>` text (e.g.
* the active mode swapping which subagent entry is listed in `<agents>`)
@@ -239,27 +238,23 @@ export class AgentPrompt extends PromptElement<AgentPromptProps> {
*
* Returns:
* - `frozen`: the value (and matching tool-reference offsets) to substitute
* in the system prompt. Always present when the setting is enabled and a
* variable is available.
* in the system prompt. Always present once a variable is available.
* - `drift`: the live current-turn value (and offsets) when it differs from
* `frozen`. Rendered in the latest user message so the model sees the
* up-to-date listing without busting the system prompt cache. Also
* emitted as an empty value when the live variable disappears, so the
* model gets a signal that previously listed entries are no longer
* available.
* up-to-date listing without busting the system prompt cache. Only
* surfaced when the variable was present this turn; an absent variable
* (e.g. on a request path without `instructionContext` such as terminal
* steering, or when collection ran but produced no listings) leaves the
* frozen system-prompt listing standing rather than emitting an empty
* update that would falsely signal removal.
*
* Returns `undefined` overall if no override should apply (setting off,
* no first turn available, or no snapshot yet and the variable is absent
* on this turn).
* Returns `undefined` overall if no override should apply (no first turn
* available, or no snapshot yet and the variable is absent on this turn).
*/
private getOrFreezeCustomizationsIndex(): {
frozen: { value: string; toolReferences: readonly ChatLanguageModelToolReference[] | undefined };
drift?: { value: string; toolReferences: readonly ChatLanguageModelToolReference[] | undefined };
} | undefined {
const enabled = this.configurationService.getExperimentBasedConfig(ConfigKey.Advanced.FreezeCustomizationsIndex, this.experimentationService);
if (!enabled) {
return undefined;
}
const firstTurn = this.props.promptContext.conversation?.turns.at(0);
if (!firstTurn) {
return undefined;
@@ -272,14 +267,17 @@ export class AgentPrompt extends PromptElement<AgentPromptProps> {
const existing = firstTurn.getMetadata(CustomizationsIndexMetadata);
if (existing && existing.cacheKey === currentCacheKey) {
const frozen = { value: existing.value, toolReferences: existing.toolReferences };
// Surface drift in either direction: a different live value, or the
// live variable disappearing entirely (treated as an empty listing).
// Without the second case the model is left looking at the stale
// frozen `<instructions>`/`<skills>`/`<agents>` block with no signal
// that entries have been removed.
const effectiveCurrent = currentValue ?? '';
if (effectiveCurrent !== existing.value) {
return { frozen, drift: { value: effectiveCurrent, toolReferences: currentToolReferences } };
// Only surface drift when the variable was present this turn AND
// differs from the snapshot. An absent variable can mean either
// "collection didn't run" (e.g. terminal steering requests omit
// `instructionContext`) or "collection ran but produced no listings".
// In either case we keep the frozen system-prompt listing standing
// rather than emit an empty `<customizationsUpdate>` block, which
// would falsely tell the model that all customizations were removed
// (its rendered text says it "supersedes" the system prompt) and
// would needlessly churn the cache tail.
if (currentValue !== undefined && currentValue !== existing.value) {
return { frozen, drift: { value: currentValue, toolReferences: currentToolReferences } };
}
return { frozen };
}
@@ -404,8 +402,7 @@ export interface AgentUserMessageProps extends BasePromptElementProps, AgentUser
* store just the drift block and historical replays on later turns would
* lose the actual user query, busting cross-turn cache continuity.
*
* Only set when the experimental `FreezeCustomizationsIndex` setting is
* enabled and the current value differs from the snapshot captured on
* Only set when the current value differs from the snapshot captured on
* the first turn.
*/
readonly customizationsIndexUpdate?: { value: string; toolReferences: readonly ChatLanguageModelToolReference[] | undefined };
@@ -638,8 +635,8 @@ interface CustomizationsIndexUpdateProps extends BasePromptElementProps {
* the drift block and historical replays on later turns would lose the
* actual user query.
*
* Used only when `FreezeCustomizationsIndex` is on and the live index
* differs from the snapshot captured on the first turn.
* Used only when the live index differs from the snapshot captured on the
* first turn.
*/
class CustomizationsIndexUpdate extends PromptElement<CustomizationsIndexUpdateProps> {
constructor(

View File

@@ -742,9 +742,6 @@ export namespace ConfigKey {
/** Per-model capability overrides. Keys are model ids, values declare an aliased `family`. Lets evals route an unknown preview model id to a known production family (e.g. `"claude-opus-4.7"`) so the Anthropic prompt resolver, multi-replace tools, tool search, context editing, etc. all activate without a code change. */
export const ModelCapabilityOverrides = defineSetting<Record<string, IModelCapabilityOverride>>('chat.modelCapabilityOverrides', ConfigType.Simple, {});
/** Freeze the customizations-index variable (the `<instructions>`/`<skills>`/`<agents>` block) at the first turn of a conversation and reuse it on subsequent turns. Prevents the system prompt cache from being invalidated by per-turn churn — e.g. the active mode swapping which subagent entry appears in `<agents>`, or async experimentation flipping a `when`-gated skill. */
export const FreezeCustomizationsIndex = defineSetting<boolean>('chat.freezeCustomizationsIndex', ConfigType.ExperimentBased, true);
export const InlineEditsXtabProviderModelConfiguration = (() => {
const oldKey = 'chat.advanced.inlineEdits.xtabProvider.modelConfiguration';
const newKey = 'chat.inlineEdits.xtabProvider.modelConfiguration';