diff --git a/package-lock.json b/package-lock.json
index c150c0adce..bea5addd35 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -139,7 +139,7 @@
"engines": {
"node": ">=22.14.0",
"npm": ">=9.0.0",
- "vscode": "^1.109.0-20260121"
+ "vscode": "^1.109.0-20260124"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
diff --git a/package.json b/package.json
index 552b858e77..241f42d28c 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,7 @@
"icon": "assets/copilot.png",
"pricing": "Trial",
"engines": {
- "vscode": "^1.109.0-20260121",
+ "vscode": "^1.109.0-20260124",
"npm": ">=9.0.0",
"node": ">=22.14.0"
},
@@ -3579,6 +3579,15 @@
"tags": [
"experimental"
]
+ },
+ "github.copilot.chat.implementAgent.model": {
+ "type": "string",
+ "default": "",
+ "scope": "resource",
+ "markdownDescription": "%github.copilot.config.implementAgent.model%",
+ "tags": [
+ "experimental"
+ ]
}
}
},
diff --git a/package.nls.json b/package.nls.json
index e0ea61b2ae..f45e46b604 100644
--- a/package.nls.json
+++ b/package.nls.json
@@ -302,6 +302,7 @@
"github.copilot.config.organizationInstructions.enabled": "When enabled, Copilot will load custom instructions defined by your GitHub Organization.",
"github.copilot.config.planAgent.additionalTools": "Additional tools to enable for the Plan agent, on top of built-in tools. Use fully-qualified tool names (e.g., `github/issue_read`, `mcp_server/tool_name`).",
"github.copilot.config.planAgent.model": "Override the language model used by the Plan agent. Leave empty to use the default model.",
+ "github.copilot.config.implementAgent.model": "Override the language model used by the Implement agent. Leave empty to use the default model.",
"copilot.toolSet.editing.description": "Edit files in your workspace",
"copilot.toolSet.read.description": "Read files in your workspace",
"copilot.toolSet.search.description": "Search files in your workspace",
diff --git a/src/extension/agents/vscode-node/implementAgentProvider.ts b/src/extension/agents/vscode-node/implementAgentProvider.ts
new file mode 100644
index 0000000000..be4ed60821
--- /dev/null
+++ b/src/extension/agents/vscode-node/implementAgentProvider.ts
@@ -0,0 +1,166 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';
+import { AGENT_FILE_EXTENSION } from '../../../platform/customInstructions/common/promptTypes';
+import { IVSCodeExtensionContext } from '../../../platform/extContext/common/extensionContext';
+import { IFileSystemService } from '../../../platform/filesystem/common/fileSystemService';
+import { ILogService } from '../../../platform/log/common/logService';
+import { Disposable } from '../../../util/vs/base/common/lifecycle';
+
+/**
+ * Complete Implement agent configuration
+ */
+interface ImplementAgentConfig {
+ name: string;
+ description: string;
+ model?: string;
+ body: string;
+}
+
+/**
+ * Base Implement agent configuration - embedded from Implement.agent.md
+ * This avoids runtime file loading and YAML parsing dependencies.
+ */
+const BASE_IMPLEMENT_AGENT_CONFIG: ImplementAgentConfig = {
+ name: 'Implement',
+ description: 'Executes an existing plan',
+ body: `You are an IMPLEMENTATION AGENT.
+
+You receive a plan that has already been created by the user or a planning agent. Your role is to carry out that plan by executing its steps in order.
+
+Focus on implementation, not planning or redesigning. Follow the plan as written and aim to achieve its intended outcome.
+
+If the plan is unclear or cannot be followed as written, pause and ask for clarification
+
+You are successful when the plan's stated outcome is achieved, with **no unrequested changes**.
+
+## Guidelines
+- Follow the plan's steps in sequence
+- Complete each step before moving to the next
+- Avoid introducing new scope or features
+- Limit changes to what is necessary to implement the plan
+
+
+Stop implementation and hand back to **Plan** if:
+- Required information or files are missing
+- A step cannot be completed as described
+- You need to make a significant decision not covered by the plan
+
+
+
+- Be concise and practical
+- Explain actions briefly when helpful
+- Summarize changes at the end if appropriate
+
+Your tone should reflect: **"I am implementing the plan."**
+`
+};
+
+/**
+ * Builds .agent.md content from a configuration object using string formatting.
+ */
+export function buildImplementAgentMarkdown(config: ImplementAgentConfig): string {
+ const lines: string[] = ['---'];
+
+ // Simple scalar fields
+ lines.push(`name: ${config.name}`);
+ lines.push(`description: ${config.description}`);
+
+ // Model (optional)
+ if (config.model) {
+ lines.push(`model: ${config.model}`);
+ }
+
+ lines.push('---');
+ lines.push(config.body);
+
+ return lines.join('\n');
+}
+
+/**
+ * Provides the Implement agent dynamically with settings-based customization.
+ *
+ * This provider uses an embedded configuration and generates .agent.md content
+ * with settings-based customization (model override).
+ */
+export class ImplementAgentProvider extends Disposable implements vscode.ChatCustomAgentProvider {
+ readonly label = vscode.l10n.t('Implement Agent');
+
+ private static readonly CACHE_DIR = 'implement-agent';
+ private static readonly AGENT_FILENAME = `Implement${AGENT_FILE_EXTENSION}`;
+
+ private readonly _onDidChangeCustomAgents = this._register(new vscode.EventEmitter());
+ readonly onDidChangeCustomAgents = this._onDidChangeCustomAgents.event;
+
+ constructor(
+ @IConfigurationService private readonly configurationService: IConfigurationService,
+ @IVSCodeExtensionContext private readonly extensionContext: IVSCodeExtensionContext,
+ @IFileSystemService private readonly fileSystemService: IFileSystemService,
+ @ILogService private readonly logService: ILogService,
+ ) {
+ super();
+
+ // Listen for settings changes to refresh agents
+ this._register(this.configurationService.onDidChangeConfiguration(e => {
+ if (e.affectsConfiguration(ConfigKey.ImplementAgentModel.fullyQualifiedId)) {
+ this.logService.trace('[ImplementAgentProvider] Settings changed, refreshing agent');
+ this._onDidChangeCustomAgents.fire();
+ }
+ }));
+ }
+
+ async provideCustomAgents(
+ _context: unknown,
+ _token: vscode.CancellationToken
+ ): Promise {
+ // Build config with settings-based customization
+ const config = this.buildCustomizedConfig();
+
+ // Generate .agent.md content
+ const content = buildImplementAgentMarkdown(config);
+
+ // Write to cache file and return URI
+ const fileUri = await this.writeCacheFile(content);
+ return [{ uri: fileUri }];
+ }
+
+ private async writeCacheFile(content: string): Promise {
+ const cacheDir = vscode.Uri.joinPath(
+ this.extensionContext.globalStorageUri,
+ ImplementAgentProvider.CACHE_DIR
+ );
+
+ // Ensure cache directory exists
+ try {
+ await this.fileSystemService.stat(cacheDir);
+ } catch {
+ await this.fileSystemService.createDirectory(cacheDir);
+ }
+
+ const fileUri = vscode.Uri.joinPath(cacheDir, ImplementAgentProvider.AGENT_FILENAME);
+ await this.fileSystemService.writeFile(fileUri, new TextEncoder().encode(content));
+ this.logService.trace(`[ImplementAgentProvider] Wrote agent file: ${fileUri.toString()}`);
+ return fileUri;
+ }
+
+ private buildCustomizedConfig(): ImplementAgentConfig {
+ const modelOverride = this.configurationService.getConfig(ConfigKey.ImplementAgentModel);
+
+ // Start with base config
+ const config: ImplementAgentConfig = {
+ ...BASE_IMPLEMENT_AGENT_CONFIG,
+ };
+
+ // Apply model override
+ if (modelOverride) {
+ config.model = modelOverride;
+ this.logService.trace(`[ImplementAgentProvider] Applied model override: ${modelOverride}`);
+ }
+
+ return config;
+ }
+}
diff --git a/src/extension/agents/vscode-node/planAgentProvider.ts b/src/extension/agents/vscode-node/planAgentProvider.ts
index a2e0ee2865..2948afee1f 100644
--- a/src/extension/agents/vscode-node/planAgentProvider.ts
+++ b/src/extension/agents/vscode-node/planAgentProvider.ts
@@ -58,7 +58,7 @@ const BASE_PLAN_AGENT_CONFIG: PlanAgentConfig = {
handoffs: [
{
label: 'Start Implementation',
- agent: 'agent',
+ agent: 'Implement',
prompt: 'Start implementation',
send: true
},
diff --git a/src/extension/agents/vscode-node/promptFileContrib.ts b/src/extension/agents/vscode-node/promptFileContrib.ts
index 8c2f7bb405..569af0adcd 100644
--- a/src/extension/agents/vscode-node/promptFileContrib.ts
+++ b/src/extension/agents/vscode-node/promptFileContrib.ts
@@ -11,6 +11,7 @@ import { IInstantiationService } from '../../../util/vs/platform/instantiation/c
import { IExtensionContribution } from '../../common/contributions';
import { GitHubOrgCustomAgentProvider } from './githubOrgCustomAgentProvider';
import { GitHubOrgInstructionsProvider } from './githubOrgInstructionsProvider';
+import { ImplementAgentProvider } from './implementAgentProvider';
import { PlanAgentProvider } from './planAgentProvider';
export class PromptFileContribution extends Disposable implements IExtensionContribution {
@@ -33,6 +34,10 @@ export class PromptFileContribution extends Disposable implements IExtensionCont
// Register Plan agent provider for dynamic settings-based customization
const planProvider = instantiationService.createInstance(PlanAgentProvider);
this._register(vscode.chat.registerCustomAgentProvider(planProvider));
+
+ // Register Implement agent provider for dynamic settings-based customization
+ const implementProvider = instantiationService.createInstance(ImplementAgentProvider);
+ this._register(vscode.chat.registerCustomAgentProvider(implementProvider));
}
// Register instructions provider
diff --git a/src/extension/agents/vscode-node/test/implementAgentProvider.spec.ts b/src/extension/agents/vscode-node/test/implementAgentProvider.spec.ts
new file mode 100644
index 0000000000..6976475a00
--- /dev/null
+++ b/src/extension/agents/vscode-node/test/implementAgentProvider.spec.ts
@@ -0,0 +1,219 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { assert } from 'chai';
+import * as os from 'os';
+import * as path from 'path';
+import { afterEach, beforeEach, suite, test } from 'vitest';
+import * as vscode from 'vscode';
+import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService';
+import { InMemoryConfigurationService } from '../../../../platform/configuration/test/common/inMemoryConfigurationService';
+import { IVSCodeExtensionContext } from '../../../../platform/extContext/common/extensionContext';
+import { IFileSystemService } from '../../../../platform/filesystem/common/fileSystemService';
+import { MockExtensionContext } from '../../../../platform/test/node/extensionContext';
+import { ITestingServicesAccessor } from '../../../../platform/test/node/services';
+import { DisposableStore } from '../../../../util/vs/base/common/lifecycle';
+import { SyncDescriptor } from '../../../../util/vs/platform/instantiation/common/descriptors';
+import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
+import { createExtensionUnitTestingServices } from '../../../test/node/services';
+import { buildImplementAgentMarkdown, ImplementAgentProvider } from '../implementAgentProvider';
+
+suite('ImplementAgentProvider', () => {
+ // Tests for the ImplementAgentProvider class - verifies the provider's integration
+ let disposables: DisposableStore;
+ let mockConfigurationService: InMemoryConfigurationService;
+ let fileSystemService: IFileSystemService;
+ let accessor: ITestingServicesAccessor;
+ let instantiationService: IInstantiationService;
+
+ beforeEach(() => {
+ disposables = new DisposableStore();
+
+ // Set up testing services with a mock extension context that has globalStorageUri
+ const testingServiceCollection = createExtensionUnitTestingServices(disposables);
+ const globalStoragePath = path.join(os.tmpdir(), 'implement-agent-test-' + Date.now());
+ testingServiceCollection.define(IVSCodeExtensionContext, new SyncDescriptor(MockExtensionContext, [globalStoragePath]));
+ accessor = testingServiceCollection.createTestingAccessor();
+ disposables.add(accessor);
+ instantiationService = accessor.get(IInstantiationService);
+
+ mockConfigurationService = accessor.get(IConfigurationService) as InMemoryConfigurationService;
+ fileSystemService = accessor.get(IFileSystemService);
+ });
+
+ afterEach(() => {
+ disposables.dispose();
+ });
+
+ function createProvider() {
+ const provider = instantiationService.createInstance(ImplementAgentProvider);
+ disposables.add(provider);
+ return provider;
+ }
+
+ async function getAgentContent(agent: vscode.ChatResource): Promise {
+ const content = await fileSystemService.readFile(agent.uri);
+ return new TextDecoder().decode(content);
+ }
+
+ test('provideCustomAgents() returns an Implement agent with correct structure', async () => {
+ const provider = createProvider();
+
+ const agents = await provider.provideCustomAgents({}, {} as any);
+
+ assert.equal(agents.length, 1);
+ assert.ok(agents[0].uri, 'Agent should have a URI');
+ assert.ok(agents[0].uri.path.endsWith('.agent.md'), 'Agent URI should end with .agent.md');
+ });
+
+ test('returns agent content with base frontmatter when no settings configured', async () => {
+ const provider = createProvider();
+
+ const agents = await provider.provideCustomAgents({}, {} as any);
+
+ assert.equal(agents.length, 1);
+ const content = await getAgentContent(agents[0]);
+
+ // Should contain base metadata
+ assert.ok(content.includes('name: Implement'));
+ assert.ok(content.includes('description: Executes an existing plan'));
+
+ // Should not have model override (not in base content)
+ assert.ok(!content.includes('model:'));
+ });
+
+ test('applies model override from settings', async () => {
+ await mockConfigurationService.setConfig(ConfigKey.ImplementAgentModel, 'Claude Haiku 4.5 (copilot)');
+
+ const provider = createProvider();
+ const agents = await provider.provideCustomAgents({}, {} as any);
+
+ assert.equal(agents.length, 1);
+ const content = await getAgentContent(agents[0]);
+
+ // Should contain model override
+ assert.ok(content.includes('model: Claude Haiku 4.5 (copilot)'));
+ });
+
+ test('fires onDidChangeCustomAgents when model setting changes', async () => {
+ const provider = createProvider();
+
+ let eventFired = false;
+ provider.onDidChangeCustomAgents(() => {
+ eventFired = true;
+ });
+
+ await mockConfigurationService.setConfig(ConfigKey.ImplementAgentModel, 'new-model');
+
+ assert.equal(eventFired, true);
+ });
+
+ test('does not fire onDidChangeCustomAgents for unrelated setting changes', async () => {
+ const provider = createProvider();
+
+ let eventFired = false;
+ provider.onDidChangeCustomAgents(() => {
+ eventFired = true;
+ });
+
+ // Set an unrelated config (using a different config key)
+ await mockConfigurationService.setConfig(ConfigKey.Advanced.FeedbackOnChange, true);
+
+ assert.equal(eventFired, false);
+ });
+
+ test('has correct label property', () => {
+ const provider = createProvider();
+ assert.ok(provider.label.includes('Implement'));
+ });
+
+ test('preserves body content after frontmatter when applying settings', async () => {
+ await mockConfigurationService.setConfig(ConfigKey.ImplementAgentModel, 'test-model');
+
+ const provider = createProvider();
+ const agents = await provider.provideCustomAgents({}, {} as any);
+
+ const content = await getAgentContent(agents[0]);
+
+ // Should preserve body content
+ assert.ok(content.includes('You are an IMPLEMENTATION AGENT.'));
+ assert.ok(content.includes('Focus on implementation, not planning or redesigning.'));
+ });
+
+ test('handles empty model string gracefully', async () => {
+ await mockConfigurationService.setConfig(ConfigKey.ImplementAgentModel, '');
+
+ const provider = createProvider();
+ const agents = await provider.provideCustomAgents({}, {} as any);
+
+ assert.equal(agents.length, 1);
+ const content = await getAgentContent(agents[0]);
+
+ // Should not have model field added
+ assert.ok(!content.includes('model:'));
+ });
+});
+
+suite('buildImplementAgentMarkdown', () => {
+ // Tests for the pure buildImplementAgentMarkdown function in isolation.
+ test('generates expected full content for Implement agent (snapshot test)', () => {
+ const config = {
+ name: 'Implement',
+ description: 'Executes an existing plan',
+ model: 'Claude Haiku 4.5 (copilot)',
+ body: 'You are an IMPLEMENTATION AGENT.'
+ };
+
+ const result = buildImplementAgentMarkdown(config);
+
+ assert.deepStrictEqual(result,
+ `---
+name: Implement
+description: Executes an existing plan
+model: Claude Haiku 4.5 (copilot)
+---
+You are an IMPLEMENTATION AGENT.`);
+ });
+
+ test('generates valid YAML frontmatter with basic config', () => {
+ const config = {
+ name: 'TestAgent',
+ description: 'Test description',
+ body: 'Test body content'
+ };
+
+ const result = buildImplementAgentMarkdown(config);
+
+ assert.ok(result.startsWith('---\n'));
+ assert.ok(result.includes('name: TestAgent'));
+ assert.ok(result.includes('description: Test description'));
+ assert.ok(result.includes('---\nTest body content'));
+ });
+
+ test('includes model when provided', () => {
+ const config = {
+ name: 'TestAgent',
+ description: 'Test',
+ model: 'Claude Haiku 4.5 (copilot)',
+ body: 'Body'
+ };
+
+ const result = buildImplementAgentMarkdown(config);
+
+ assert.ok(result.includes('model: Claude Haiku 4.5 (copilot)'));
+ });
+
+ test('omits model when not provided', () => {
+ const config = {
+ name: 'TestAgent',
+ description: 'Test',
+ body: 'Body'
+ };
+
+ const result = buildImplementAgentMarkdown(config);
+
+ assert.ok(!result.includes('model:'));
+ });
+});
diff --git a/src/platform/configuration/common/configurationService.ts b/src/platform/configuration/common/configurationService.ts
index 28253060a1..866cff2dab 100644
--- a/src/platform/configuration/common/configurationService.ts
+++ b/src/platform/configuration/common/configurationService.ts
@@ -939,6 +939,9 @@ export namespace ConfigKey {
export const PlanAgentAdditionalTools = defineSetting('chat.planAgent.additionalTools', ConfigType.Simple, []);
/** Model override for Plan agent (empty = use default) */
export const PlanAgentModel = defineSetting('chat.planAgent.model', ConfigType.Simple, '');
+
+ /** Model override for Implement agent (empty = use default) */
+ export const ImplementAgentModel = defineSetting('chat.implementAgent.model', ConfigType.Simple, '');
}
export function getAllConfigKeys(): string[] {