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

View File

@@ -55,8 +55,9 @@ export const GeneralistAgent = (
return {
systemPrompt: getCoreSystemPrompt(
context.config,
/*useMemory=*/ undefined,
/*userMemory=*/ undefined,
/*interactiveOverride=*/ false,
/*topicUpdateNarrationOverride=*/ false,
),
query: '${request}',
};

View File

@@ -82,6 +82,7 @@ import { CompleteTaskTool } from '../tools/complete-task.js';
import {
COMPLETE_TASK_TOOL_NAME,
ACTIVATE_SKILL_TOOL_NAME,
UPDATE_TOPIC_TOOL_NAME,
} from '../tools/definitions/base-declarations.js';
/** A callback function to report on agent activity. */
@@ -189,6 +190,10 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
return;
}
if (tool.name === UPDATE_TOPIC_TOOL_NAME) {
return;
}
// Clone the tool, so it gets its own state and subagent messageBus
const clonedTool = tool.clone(subagentMessageBus);
agentToolRegistry.registerTool(clonedTool);

View File

@@ -24,11 +24,13 @@ export function getCoreSystemPrompt(
config: Config,
userMemory?: string | HierarchicalMemory,
interactiveOverride?: boolean,
topicUpdateNarrationOverride?: boolean,
): string {
return new PromptProvider().getCoreSystemPrompt(
config,
userMemory,
interactiveOverride,
topicUpdateNarrationOverride,
);
}

View File

@@ -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', () => {
beforeEach(() => {
mockConfig.topicState.reset();

View File

@@ -44,6 +44,7 @@ export class PromptProvider {
context: AgentLoopContext,
userMemory?: string | HierarchicalMemory,
interactiveOverride?: boolean,
topicUpdateNarrationOverride?: boolean,
): string {
const systemMdResolution = resolvePathFromEnv(
process.env['GEMINI_SYSTEM_MD'],
@@ -57,6 +58,10 @@ export class PromptProvider {
const isYoloMode = approvalMode === ApprovalMode.YOLO;
const skills = context.config.getSkillManager().getSkills();
const toolNames = context.toolRegistry.getAllToolNames();
const isTopicUpdateNarrationEnabled =
topicUpdateNarrationOverride ??
context.config.isTopicUpdateNarrationEnabled();
const enabledToolNames = new Set(toolNames);
const approvedPlanPath = context.config.getApprovedPlanPath();
@@ -139,7 +144,7 @@ export class PromptProvider {
hasSkills: skills.length > 0,
hasHierarchicalMemory,
contextFilenames,
topicUpdateNarration: context.config.isTopicUpdateNarrationEnabled(),
topicUpdateNarration: isTopicUpdateNarrationEnabled,
})),
subAgents: this.withSection(
'agentContexts',
@@ -184,8 +189,7 @@ export class PromptProvider {
? { path: approvedPlanPath }
: undefined,
taskTracker: trackerDir,
topicUpdateNarration:
context.config.isTopicUpdateNarrationEnabled(),
topicUpdateNarration: isTopicUpdateNarrationEnabled,
};
},
!isPlanMode,
@@ -207,8 +211,7 @@ export class PromptProvider {
enableShellEfficiency:
context.config.getEnableShellOutputEfficiency(),
interactiveShellEnabled: context.config.isInteractiveShellEnabled(),
topicUpdateNarration:
context.config.isTopicUpdateNarrationEnabled(),
topicUpdateNarration: isTopicUpdateNarrationEnabled,
memoryManagerEnabled: context.config.isMemoryManagerEnabled(),
}),
),
@@ -251,7 +254,7 @@ export class PromptProvider {
let sanitizedPrompt = finalPrompt.replace(/\n{3,}/g, '\n\n');
// Context Reinjection (Active Topic)
if (context.config.isTopicUpdateNarrationEnabled()) {
if (isTopicUpdateNarrationEnabled) {
const activeTopic = context.config.topicState.getTopic();
if (activeTopic) {
const sanitizedTopic = activeTopic