From c68a2cb933b02cdae50c2c86c246eb0d4802eced Mon Sep 17 00:00:00 2001 From: Akhilesh Kumar Date: Tue, 10 Mar 2026 21:58:40 +0000 Subject: [PATCH] feat(core): implement configuration-based tool isolation for subagents --- packages/core/src/agents/local-executor.ts | 8 +++++--- packages/core/src/config/config.ts | 10 +++++++++- packages/core/src/tools/tool-registry.ts | 22 +++++++++++++++++++++- packages/core/src/tools/tools.ts | 16 ++++++++++++++++ 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/packages/core/src/agents/local-executor.ts b/packages/core/src/agents/local-executor.ts index 4ec9ea3eb3..9d9cebdd45 100644 --- a/packages/core/src/agents/local-executor.ts +++ b/packages/core/src/agents/local-executor.ts @@ -160,13 +160,15 @@ export class LocalAgentExecutor { // registry and register it with the agent's isolated registry. const tool = parentToolRegistry.getTool(toolName); if (tool) { - if (tool instanceof DiscoveredMCPTool) { + // Clone the tool, so it gets its own state and subagent messageBus + const clonedTool = tool.clone(subagentMessageBus); + if (clonedTool instanceof DiscoveredMCPTool) { // Subagents MUST use fully qualified names for MCP tools to ensure // unambiguous tool calls and to comply with policy requirements. // We automatically "upgrade" any MCP tool to its qualified version. - agentToolRegistry.registerTool(tool.asFullyQualifiedTool()); + agentToolRegistry.registerTool(clonedTool.asFullyQualifiedTool()); } else { - agentToolRegistry.registerTool(tool); + agentToolRegistry.registerTool(clonedTool); } } }; diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index bc52050286..e8029c495e 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -235,6 +235,7 @@ export interface AgentOverride { modelConfig?: ModelConfig; runConfig?: AgentRunConfig; enabled?: boolean; + tools?: string[]; } export interface AgentSettings { @@ -484,6 +485,7 @@ export interface ConfigParameters { question?: string; coreTools?: string[]; + mainAgentTools?: string[]; /** @deprecated Use Policy Engine instead */ allowedTools?: string[]; /** @deprecated Use Policy Engine instead */ @@ -635,6 +637,7 @@ export class Config implements McpContext, AgentLoopContext { readonly enableConseca: boolean; private readonly coreTools: string[] | undefined; + private readonly mainAgentTools: string[] | undefined; /** @deprecated Use Policy Engine instead */ private readonly allowedTools: string[] | undefined; /** @deprecated Use Policy Engine instead */ @@ -831,6 +834,7 @@ export class Config implements McpContext, AgentLoopContext { this.question = params.question; this.coreTools = params.coreTools; + this.mainAgentTools = params.mainAgentTools; this.allowedTools = params.allowedTools; this.excludeTools = params.excludeTools; this.toolDiscoveryCommand = params.toolDiscoveryCommand; @@ -1778,6 +1782,10 @@ export class Config implements McpContext, AgentLoopContext { return this.coreTools; } + getMainAgentTools(): string[] | undefined { + return this.mainAgentTools; + } + getAllowedTools(): string[] | undefined { return this.allowedTools; } @@ -2823,7 +2831,7 @@ export class Config implements McpContext, AgentLoopContext { } async createToolRegistry(): Promise { - const registry = new ToolRegistry(this, this.messageBus); + const registry = new ToolRegistry(this, this.messageBus, true); // helper to create & register core tools that are enabled const maybeRegister = ( diff --git a/packages/core/src/tools/tool-registry.ts b/packages/core/src/tools/tool-registry.ts index 69695877c2..c7a46e9700 100644 --- a/packages/core/src/tools/tool-registry.ts +++ b/packages/core/src/tools/tool-registry.ts @@ -202,10 +202,16 @@ export class ToolRegistry { private allKnownTools: Map = new Map(); private config: Config; private messageBus: MessageBus; + private isMainRegistry: boolean; - constructor(config: Config, messageBus: MessageBus) { + constructor( + config: Config, + messageBus: MessageBus, + isMainRegistry: boolean = false, + ) { this.config = config; this.messageBus = messageBus; + this.isMainRegistry = isMainRegistry; } getMessageBus(): MessageBus { @@ -545,6 +551,10 @@ export class ToolRegistry { const declarations: FunctionDeclaration[] = []; const seenNames = new Set(); + const mainAgentTools = this.isMainRegistry + ? this.config.getMainAgentTools() + : undefined; + this.getActiveTools().forEach((tool) => { const toolName = tool instanceof DiscoveredMCPTool @@ -554,6 +564,16 @@ export class ToolRegistry { if (seenNames.has(toolName)) { return; } + + if ( + mainAgentTools && + !mainAgentTools.includes(toolName) && + !mainAgentTools.includes(tool.constructor.name) && + !mainAgentTools.some((t) => t.startsWith(`${tool.constructor.name}(`)) + ) { + return; + } + seenNames.add(toolName); let schema = tool.getSchema(modelId); diff --git a/packages/core/src/tools/tools.ts b/packages/core/src/tools/tools.ts index 828461ea65..f00aeb2d33 100644 --- a/packages/core/src/tools/tools.ts +++ b/packages/core/src/tools/tools.ts @@ -380,6 +380,22 @@ export abstract class DeclarativeTool< readonly extensionId?: string, ) {} + clone(messageBus?: MessageBus): this { + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + const cloned = Object.assign( + Object.create(Object.getPrototypeOf(this)), + this, + ) as this; + if (messageBus) { + Object.defineProperty(cloned, 'messageBus', { + value: messageBus, + writable: false, + configurable: true, + }); + } + return cloned; + } + get isReadOnly(): boolean { return READ_ONLY_KINDS.includes(this.kind); }