feat(core): Disable topic updates for subagents (#25567)

This commit is contained in:
Christian Gunderman
2026-04-17 17:35:17 +00:00
committed by GitHub
parent 8379099e85
commit f7b2632939
5 changed files with 68 additions and 7 deletions
+2 -1
View File
@@ -55,8 +55,9 @@ export const GeneralistAgent = (
return { return {
systemPrompt: getCoreSystemPrompt( systemPrompt: getCoreSystemPrompt(
context.config, context.config,
/*useMemory=*/ undefined, /*userMemory=*/ undefined,
/*interactiveOverride=*/ false, /*interactiveOverride=*/ false,
/*topicUpdateNarrationOverride=*/ false,
), ),
query: '${request}', query: '${request}',
}; };
@@ -82,6 +82,7 @@ import { CompleteTaskTool } from '../tools/complete-task.js';
import { import {
COMPLETE_TASK_TOOL_NAME, COMPLETE_TASK_TOOL_NAME,
ACTIVATE_SKILL_TOOL_NAME, ACTIVATE_SKILL_TOOL_NAME,
UPDATE_TOPIC_TOOL_NAME,
} from '../tools/definitions/base-declarations.js'; } from '../tools/definitions/base-declarations.js';
/** A callback function to report on agent activity. */ /** A callback function to report on agent activity. */
@@ -189,6 +190,10 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
return; return;
} }
if (tool.name === UPDATE_TOPIC_TOOL_NAME) {
return;
}
// Clone the tool, so it gets its own state and subagent messageBus // Clone the tool, so it gets its own state and subagent messageBus
const clonedTool = tool.clone(subagentMessageBus); const clonedTool = tool.clone(subagentMessageBus);
agentToolRegistry.registerTool(clonedTool); agentToolRegistry.registerTool(clonedTool);
+2
View File
@@ -24,11 +24,13 @@ export function getCoreSystemPrompt(
config: Config, config: Config,
userMemory?: string | HierarchicalMemory, userMemory?: string | HierarchicalMemory,
interactiveOverride?: boolean, interactiveOverride?: boolean,
topicUpdateNarrationOverride?: boolean,
): string { ): string {
return new PromptProvider().getCoreSystemPrompt( return new PromptProvider().getCoreSystemPrompt(
config, config,
userMemory, userMemory,
interactiveOverride, interactiveOverride,
topicUpdateNarrationOverride,
); );
} }
@@ -277,6 +277,56 @@ describe('PromptProvider', () => {
}); });
}); });
describe('topicUpdateNarrationOverride', () => {
let provider: PromptProvider;
beforeEach(() => {
provider = new PromptProvider();
mockConfig.topicState.reset();
(mockConfig.getToolRegistry as ReturnType<typeof vi.fn>).mockReturnValue({
getAllToolNames: vi.fn().mockReturnValue([UPDATE_TOPIC_TOOL_NAME]),
});
(mockConfig.getAgentRegistry as ReturnType<typeof vi.fn>).mockReturnValue(
{
getAllDefinitions: vi.fn().mockReturnValue([]),
getDefinition: vi.fn().mockReturnValue(undefined),
},
);
});
it('should disable topic update narration when override is false, even if config is true', () => {
vi.mocked(mockConfig.isTopicUpdateNarrationEnabled).mockReturnValue(true);
const prompt = provider.getCoreSystemPrompt(
mockConfig as unknown as Config,
/*userMemory=*/ undefined,
/*interactiveOverride=*/ undefined,
/*topicUpdateNarrationOverride=*/ false,
);
expect(prompt).not.toContain(
`As you work, the user follows along by reading topic updates that you publish with ${UPDATE_TOPIC_TOOL_NAME}.`,
);
});
it('should enable topic update narration when override is true, even if config is false', () => {
vi.mocked(mockConfig.isTopicUpdateNarrationEnabled).mockReturnValue(
false,
);
const prompt = provider.getCoreSystemPrompt(
mockConfig as unknown as Config,
/*userMemory=*/ undefined,
/*interactiveOverride=*/ undefined,
/*topicUpdateNarrationOverride=*/ true,
);
expect(prompt).toContain(
`As you work, the user follows along by reading topic updates that you publish with ${UPDATE_TOPIC_TOOL_NAME}.`,
);
});
});
describe('Topic & Update Narration', () => { describe('Topic & Update Narration', () => {
beforeEach(() => { beforeEach(() => {
mockConfig.topicState.reset(); mockConfig.topicState.reset();
+9 -6
View File
@@ -44,6 +44,7 @@ export class PromptProvider {
context: AgentLoopContext, context: AgentLoopContext,
userMemory?: string | HierarchicalMemory, userMemory?: string | HierarchicalMemory,
interactiveOverride?: boolean, interactiveOverride?: boolean,
topicUpdateNarrationOverride?: boolean,
): string { ): string {
const systemMdResolution = resolvePathFromEnv( const systemMdResolution = resolvePathFromEnv(
process.env['GEMINI_SYSTEM_MD'], process.env['GEMINI_SYSTEM_MD'],
@@ -57,6 +58,10 @@ export class PromptProvider {
const isYoloMode = approvalMode === ApprovalMode.YOLO; const isYoloMode = approvalMode === ApprovalMode.YOLO;
const skills = context.config.getSkillManager().getSkills(); const skills = context.config.getSkillManager().getSkills();
const toolNames = context.toolRegistry.getAllToolNames(); const toolNames = context.toolRegistry.getAllToolNames();
const isTopicUpdateNarrationEnabled =
topicUpdateNarrationOverride ??
context.config.isTopicUpdateNarrationEnabled();
const enabledToolNames = new Set(toolNames); const enabledToolNames = new Set(toolNames);
const approvedPlanPath = context.config.getApprovedPlanPath(); const approvedPlanPath = context.config.getApprovedPlanPath();
@@ -139,7 +144,7 @@ export class PromptProvider {
hasSkills: skills.length > 0, hasSkills: skills.length > 0,
hasHierarchicalMemory, hasHierarchicalMemory,
contextFilenames, contextFilenames,
topicUpdateNarration: context.config.isTopicUpdateNarrationEnabled(), topicUpdateNarration: isTopicUpdateNarrationEnabled,
})), })),
subAgents: this.withSection( subAgents: this.withSection(
'agentContexts', 'agentContexts',
@@ -184,8 +189,7 @@ export class PromptProvider {
? { path: approvedPlanPath } ? { path: approvedPlanPath }
: undefined, : undefined,
taskTracker: trackerDir, taskTracker: trackerDir,
topicUpdateNarration: topicUpdateNarration: isTopicUpdateNarrationEnabled,
context.config.isTopicUpdateNarrationEnabled(),
}; };
}, },
!isPlanMode, !isPlanMode,
@@ -207,8 +211,7 @@ export class PromptProvider {
enableShellEfficiency: enableShellEfficiency:
context.config.getEnableShellOutputEfficiency(), context.config.getEnableShellOutputEfficiency(),
interactiveShellEnabled: context.config.isInteractiveShellEnabled(), interactiveShellEnabled: context.config.isInteractiveShellEnabled(),
topicUpdateNarration: topicUpdateNarration: isTopicUpdateNarrationEnabled,
context.config.isTopicUpdateNarrationEnabled(),
memoryManagerEnabled: context.config.isMemoryManagerEnabled(), memoryManagerEnabled: context.config.isMemoryManagerEnabled(),
}), }),
), ),
@@ -251,7 +254,7 @@ export class PromptProvider {
let sanitizedPrompt = finalPrompt.replace(/\n{3,}/g, '\n\n'); let sanitizedPrompt = finalPrompt.replace(/\n{3,}/g, '\n\n');
// Context Reinjection (Active Topic) // Context Reinjection (Active Topic)
if (context.config.isTopicUpdateNarrationEnabled()) { if (isTopicUpdateNarrationEnabled) {
const activeTopic = context.config.topicState.getTopic(); const activeTopic = context.config.topicState.getTopic();
if (activeTopic) { if (activeTopic) {
const sanitizedTopic = activeTopic const sanitizedTopic = activeTopic