feat(core): implement configuration-based tool isolation for subagents

This commit is contained in:
Akhilesh Kumar
2026-03-10 21:58:40 +00:00
parent 50384ab3c9
commit c68a2cb933
4 changed files with 51 additions and 5 deletions
+5 -3
View File
@@ -160,13 +160,15 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
// registry and register it with the agent's isolated registry. // registry and register it with the agent's isolated registry.
const tool = parentToolRegistry.getTool(toolName); const tool = parentToolRegistry.getTool(toolName);
if (tool) { 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 // Subagents MUST use fully qualified names for MCP tools to ensure
// unambiguous tool calls and to comply with policy requirements. // unambiguous tool calls and to comply with policy requirements.
// We automatically "upgrade" any MCP tool to its qualified version. // We automatically "upgrade" any MCP tool to its qualified version.
agentToolRegistry.registerTool(tool.asFullyQualifiedTool()); agentToolRegistry.registerTool(clonedTool.asFullyQualifiedTool());
} else { } else {
agentToolRegistry.registerTool(tool); agentToolRegistry.registerTool(clonedTool);
} }
} }
}; };
+9 -1
View File
@@ -235,6 +235,7 @@ export interface AgentOverride {
modelConfig?: ModelConfig; modelConfig?: ModelConfig;
runConfig?: AgentRunConfig; runConfig?: AgentRunConfig;
enabled?: boolean; enabled?: boolean;
tools?: string[];
} }
export interface AgentSettings { export interface AgentSettings {
@@ -484,6 +485,7 @@ export interface ConfigParameters {
question?: string; question?: string;
coreTools?: string[]; coreTools?: string[];
mainAgentTools?: string[];
/** @deprecated Use Policy Engine instead */ /** @deprecated Use Policy Engine instead */
allowedTools?: string[]; allowedTools?: string[];
/** @deprecated Use Policy Engine instead */ /** @deprecated Use Policy Engine instead */
@@ -635,6 +637,7 @@ export class Config implements McpContext, AgentLoopContext {
readonly enableConseca: boolean; readonly enableConseca: boolean;
private readonly coreTools: string[] | undefined; private readonly coreTools: string[] | undefined;
private readonly mainAgentTools: string[] | undefined;
/** @deprecated Use Policy Engine instead */ /** @deprecated Use Policy Engine instead */
private readonly allowedTools: string[] | undefined; private readonly allowedTools: string[] | undefined;
/** @deprecated Use Policy Engine instead */ /** @deprecated Use Policy Engine instead */
@@ -831,6 +834,7 @@ export class Config implements McpContext, AgentLoopContext {
this.question = params.question; this.question = params.question;
this.coreTools = params.coreTools; this.coreTools = params.coreTools;
this.mainAgentTools = params.mainAgentTools;
this.allowedTools = params.allowedTools; this.allowedTools = params.allowedTools;
this.excludeTools = params.excludeTools; this.excludeTools = params.excludeTools;
this.toolDiscoveryCommand = params.toolDiscoveryCommand; this.toolDiscoveryCommand = params.toolDiscoveryCommand;
@@ -1778,6 +1782,10 @@ export class Config implements McpContext, AgentLoopContext {
return this.coreTools; return this.coreTools;
} }
getMainAgentTools(): string[] | undefined {
return this.mainAgentTools;
}
getAllowedTools(): string[] | undefined { getAllowedTools(): string[] | undefined {
return this.allowedTools; return this.allowedTools;
} }
@@ -2823,7 +2831,7 @@ export class Config implements McpContext, AgentLoopContext {
} }
async createToolRegistry(): Promise<ToolRegistry> { async createToolRegistry(): Promise<ToolRegistry> {
const registry = new ToolRegistry(this, this.messageBus); const registry = new ToolRegistry(this, this.messageBus, true);
// helper to create & register core tools that are enabled // helper to create & register core tools that are enabled
const maybeRegister = ( const maybeRegister = (
+21 -1
View File
@@ -202,10 +202,16 @@ export class ToolRegistry {
private allKnownTools: Map<string, AnyDeclarativeTool> = new Map(); private allKnownTools: Map<string, AnyDeclarativeTool> = new Map();
private config: Config; private config: Config;
private messageBus: MessageBus; private messageBus: MessageBus;
private isMainRegistry: boolean;
constructor(config: Config, messageBus: MessageBus) { constructor(
config: Config,
messageBus: MessageBus,
isMainRegistry: boolean = false,
) {
this.config = config; this.config = config;
this.messageBus = messageBus; this.messageBus = messageBus;
this.isMainRegistry = isMainRegistry;
} }
getMessageBus(): MessageBus { getMessageBus(): MessageBus {
@@ -545,6 +551,10 @@ export class ToolRegistry {
const declarations: FunctionDeclaration[] = []; const declarations: FunctionDeclaration[] = [];
const seenNames = new Set<string>(); const seenNames = new Set<string>();
const mainAgentTools = this.isMainRegistry
? this.config.getMainAgentTools()
: undefined;
this.getActiveTools().forEach((tool) => { this.getActiveTools().forEach((tool) => {
const toolName = const toolName =
tool instanceof DiscoveredMCPTool tool instanceof DiscoveredMCPTool
@@ -554,6 +564,16 @@ export class ToolRegistry {
if (seenNames.has(toolName)) { if (seenNames.has(toolName)) {
return; return;
} }
if (
mainAgentTools &&
!mainAgentTools.includes(toolName) &&
!mainAgentTools.includes(tool.constructor.name) &&
!mainAgentTools.some((t) => t.startsWith(`${tool.constructor.name}(`))
) {
return;
}
seenNames.add(toolName); seenNames.add(toolName);
let schema = tool.getSchema(modelId); let schema = tool.getSchema(modelId);
+16
View File
@@ -380,6 +380,22 @@ export abstract class DeclarativeTool<
readonly extensionId?: string, 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 { get isReadOnly(): boolean {
return READ_ONLY_KINDS.includes(this.kind); return READ_ONLY_KINDS.includes(this.kind);
} }