From 428108c1de73f7cb9aa57715dcce4ce685e3019d Mon Sep 17 00:00:00 2001 From: DennisYu07 <617072224@qq.com> Date: Thu, 28 May 2026 15:53:11 +0800 Subject: [PATCH 1/2] fix(core): remove proactive subagent system-reminder injection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove getSubagentSystemReminder and its runtime injection points. This function injected a system-reminder message every turn that commanded the model to 'PROACTIVELY use the Agent tool', causing excessive subagent spawning. Agent information is already available via the static tool description — the runtime push is unnecessary and harmful for tasks that benefit from direct tool use. Benchmark evidence (django__django-15280): - Before: 6 subagent calls, only fixed 1/3 locations, FAILED - After: 1 subagent call, fixed all 3 locations, PASSED --- .../src/acp-integration/session/Session.ts | 11 -------- packages/core/src/core/client.ts | 15 ----------- packages/core/src/core/prompts.test.ts | 26 ------------------- packages/core/src/core/prompts.ts | 20 -------------- 4 files changed, 72 deletions(-) diff --git a/packages/cli/src/acp-integration/session/Session.ts b/packages/cli/src/acp-integration/session/Session.ts index 624a3554cbf..dfba180159c 100644 --- a/packages/cli/src/acp-integration/session/Session.ts +++ b/packages/cli/src/acp-integration/session/Session.ts @@ -52,7 +52,6 @@ import { generateToolUseId, MessageBusType, getPlanModeSystemReminder, - getSubagentSystemReminder, getArenaSystemReminder, STARTUP_CONTEXT_MODEL_ACK, evaluatePermissionFlow, @@ -1738,16 +1737,6 @@ export class Session implements SessionContext { async #buildInitialSystemReminders(): Promise { const reminders: Part[] = []; - const hasAgentTool = await this.config - .getToolRegistry() - .ensureTool(ToolNames.AGENT); - const subagents = (await this.config.getSubagentManager().listSubagents()) - .filter((subagent) => subagent.level !== 'builtin') - .map((subagent) => subagent.name); - if (hasAgentTool && subagents.length > 0) { - reminders.push({ text: getSubagentSystemReminder(subagents) }); - } - if (this.config.getApprovalMode() === ApprovalMode.PLAN) { reminders.push({ text: getPlanModeSystemReminder(this.config.getSdkMode?.()), diff --git a/packages/core/src/core/client.ts b/packages/core/src/core/client.ts index a878ba3d78d..346a8eacb1f 100644 --- a/packages/core/src/core/client.ts +++ b/packages/core/src/core/client.ts @@ -36,7 +36,6 @@ import { getCoreSystemPrompt, getCustomSystemPrompt, getPlanModeSystemReminder, - getSubagentSystemReminder, } from './prompts.js'; import { CompressionStatus, @@ -1610,20 +1609,6 @@ export class GeminiClient { ) { const systemReminders = []; - // add subagent system reminder if there are subagents - const hasAgentTool = await this.config - .getToolRegistry() - .ensureTool(ToolNames.AGENT); - const subagents = ( - await this.config.getSubagentManager().listSubagents() - ) - .filter((subagent) => subagent.level !== 'builtin') - .map((subagent) => subagent.name); - - if (hasAgentTool && subagents.length > 0) { - systemReminders.push(getSubagentSystemReminder(subagents)); - } - // add plan mode system reminder if approval mode is plan if (this.config.getApprovalMode() === ApprovalMode.PLAN) { systemReminders.push( diff --git a/packages/core/src/core/prompts.test.ts b/packages/core/src/core/prompts.test.ts index e9b052b18a8..43dc59e7695 100644 --- a/packages/core/src/core/prompts.test.ts +++ b/packages/core/src/core/prompts.test.ts @@ -9,7 +9,6 @@ import { buildDeferredToolsSection, getCoreSystemPrompt, getCustomSystemPrompt, - getSubagentSystemReminder, getPlanModeSystemReminder, resolvePathFromEnv, } from './prompts.js'; @@ -454,31 +453,6 @@ describe('getCustomSystemPrompt', () => { }); }); -describe('getSubagentSystemReminder', () => { - it('should format single agent type correctly', () => { - const result = getSubagentSystemReminder(['python']); - - expect(result).toMatch(/^.*<\/system-reminder>$/); - expect(result).toContain('available agent types are: python'); - expect(result).toContain('PROACTIVELY use the'); - }); - - it('should join multiple agent types with commas', () => { - const result = getSubagentSystemReminder(['python', 'web', 'analysis']); - - expect(result).toContain( - 'available agent types are: python, web, analysis', - ); - }); - - it('should handle empty array', () => { - const result = getSubagentSystemReminder([]); - - expect(result).toContain('available agent types are: '); - expect(result).toContain(''); - }); -}); - describe('buildDeferredToolsSection', () => { it('returns an empty string when no deferred tools are passed', () => { expect(buildDeferredToolsSection([])).toBe(''); diff --git a/packages/core/src/core/prompts.ts b/packages/core/src/core/prompts.ts index f45a964fcd2..e4c324fa04d 100644 --- a/packages/core/src/core/prompts.ts +++ b/packages/core/src/core/prompts.ts @@ -896,26 +896,6 @@ function getToolCallExamples(model?: string): string { return generalToolCallExamples; } -/** - * Generates a system reminder message about available subagents for the AI assistant. - * - * This function creates an internal system message that informs the AI about specialized - * agents it can delegate tasks to. The reminder encourages proactive use of the TASK tool - * when user requests match agent capabilities. - * - * @param agentTypes - Array of available agent type names (e.g., ['python', 'web', 'analysis']) - * @returns A formatted system reminder string wrapped in XML tags for internal AI processing - * - * @example - * ```typescript - * const reminder = getSubagentSystemReminder(['python', 'web']); - * // Returns: "You have powerful specialized agents..." - * ``` - */ -export function getSubagentSystemReminder(agentTypes: string[]): string { - return `You have powerful specialized agents at your disposal, available agent types are: ${agentTypes.join(', ')}. PROACTIVELY use the ${ToolNames.AGENT} tool to delegate user's task to appropriate agent when user's task matches agent capabilities. Ignore this message if user's task is not relevant to any agent. This message is for internal use only. Do not mention this to user in your response.`; -} - /** * Generates a system reminder message for plan mode operation. * From 7306b073760f56ed145bb12ad45ec69087ee0485 Mon Sep 17 00:00:00 2001 From: DennisYu07 <617072224@qq.com> Date: Wed, 3 Jun 2026 16:26:22 +0800 Subject: [PATCH 2/2] fix test failed --- .../acp-integration/session/Session.test.ts | 57 ------------------- .../session/Session.worktree.test.ts | 5 -- packages/core/src/core/client.test.ts | 5 -- 3 files changed, 67 deletions(-) diff --git a/packages/cli/src/acp-integration/session/Session.test.ts b/packages/cli/src/acp-integration/session/Session.test.ts index f1124e3e6ab..6586b59fbec 100644 --- a/packages/cli/src/acp-integration/session/Session.test.ts +++ b/packages/cli/src/acp-integration/session/Session.test.ts @@ -215,9 +215,6 @@ describe('Session', () => { mockToolRegistry = { getTool: vi.fn(), - // #executePrompt → #buildInitialSystemReminders calls - // getToolRegistry().ensureTool(ToolNames.AGENT) on every session.prompt(), - // so the default mock must provide it (#1151 / #3479). ensureTool: vi.fn().mockResolvedValue(true), }; const fileService = { shouldGitIgnoreFile: vi.fn().mockReturnValue(false) }; @@ -239,12 +236,6 @@ describe('Session', () => { .fn() .mockReturnValue(mockChatRecordingService), getToolRegistry: vi.fn().mockReturnValue(mockToolRegistry), - // #buildInitialSystemReminders iterates listSubagents() on every - // session.prompt(). Default to an empty list so tests that don't - // exercise subagent reminders don't need to stub it (#1151 / #3479). - getSubagentManager: vi.fn().mockReturnValue({ - listSubagents: vi.fn().mockResolvedValue([]), - }), getFileService: vi.fn().mockReturnValue(fileService), getFileFilteringRespectGitIgnore: vi.fn().mockReturnValue(true), getEnableRecursiveFileSearch: vi.fn().mockReturnValue(false), @@ -2874,20 +2865,7 @@ describe('Session', () => { return capture; }; - const stubEmptySubagents = () => { - (mockConfig as unknown as Record)[ - 'getSubagentManager' - ] = vi.fn().mockReturnValue({ - listSubagents: vi.fn().mockResolvedValue([]), - }); - // ensureTool is called on the result of getToolRegistry(); add it. - ( - mockToolRegistry as unknown as { ensureTool: () => Promise } - ).ensureTool = vi.fn().mockResolvedValue(true); - }; - it('prepends plan-mode reminder when approval mode is PLAN (#1151)', async () => { - stubEmptySubagents(); mockConfig.getApprovalMode = vi.fn().mockReturnValue(ApprovalMode.PLAN); const capture = captureFirstTurnMessage(); @@ -2910,7 +2888,6 @@ describe('Session', () => { }); it('does not prepend plan-mode reminder in default approval mode', async () => { - stubEmptySubagents(); mockConfig.getApprovalMode = vi .fn() .mockReturnValue(ApprovalMode.DEFAULT); @@ -2926,40 +2903,6 @@ describe('Session', () => { ); expect(hasPlanReminder).toBe(false); }); - - it('prepends subagent reminder when user-level subagents exist', async () => { - (mockConfig as unknown as Record)[ - 'getSubagentManager' - ] = vi.fn().mockReturnValue({ - listSubagents: vi.fn().mockResolvedValue([ - { name: 'researcher', level: 'user' }, - { name: 'planner', level: 'project' }, - // builtin entries are filtered out, matching client.ts:853. - { name: 'builtin-helper', level: 'builtin' }, - ]), - }); - ( - mockToolRegistry as unknown as { ensureTool: () => Promise } - ).ensureTool = vi.fn().mockResolvedValue(true); - mockConfig.getApprovalMode = vi - .fn() - .mockReturnValue(ApprovalMode.DEFAULT); - const capture = captureFirstTurnMessage(); - - await session.prompt({ - sessionId: 'test-session-id', - prompt: [{ type: 'text', text: 'hi' }], - }); - - const reminder = capture.parts.find( - (p) => - p.text && - p.text.includes('researcher') && - p.text.includes('planner'), - ); - expect(reminder).toBeTruthy(); - expect(reminder!.text).not.toContain('builtin-helper'); - }); }); }); }); diff --git a/packages/cli/src/acp-integration/session/Session.worktree.test.ts b/packages/cli/src/acp-integration/session/Session.worktree.test.ts index 3db6c588f93..3fbd6056772 100644 --- a/packages/cli/src/acp-integration/session/Session.worktree.test.ts +++ b/packages/cli/src/acp-integration/session/Session.worktree.test.ts @@ -116,13 +116,8 @@ describe('Session.pendingWorktreeNotice', () => { }), getToolRegistry: vi.fn().mockReturnValue({ getTool: vi.fn(), - // Called on every prompt() via #buildInitialSystemReminders ensureTool: vi.fn().mockResolvedValue(true), }), - // Called on every prompt() to check subagent system reminders - getSubagentManager: vi.fn().mockReturnValue({ - listSubagents: vi.fn().mockResolvedValue([]), - }), getFileService: vi.fn().mockReturnValue({ shouldGitIgnoreFile: vi.fn().mockReturnValue(false), }), diff --git a/packages/core/src/core/client.test.ts b/packages/core/src/core/client.test.ts index 51d29b6ff3e..0d5abb14970 100644 --- a/packages/core/src/core/client.test.ts +++ b/packages/core/src/core/client.test.ts @@ -418,10 +418,6 @@ describe('Gemini Client (client.ts)', () => { vertexai: false, authType: AuthType.USE_GEMINI, }; - const mockSubagentManager = { - listSubagents: vi.fn().mockResolvedValue([]), - addChangeListener: vi.fn().mockReturnValue(() => {}), - }; mockConfig = { getContentGeneratorConfig: vi .fn() @@ -473,7 +469,6 @@ describe('Gemini Client (client.ts)', () => { }, getContentGenerator: vi.fn().mockReturnValue(mockContentGenerator), getBaseLlmClient: vi.fn(), - getSubagentManager: vi.fn().mockReturnValue(mockSubagentManager), getSkipLoopDetection: vi.fn().mockReturnValue(false), getChatRecordingService: vi.fn().mockReturnValue(undefined), getResumedSessionData: vi.fn().mockReturnValue(undefined),