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.
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);
}
}
};
+9 -1
View File
@@ -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<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
const maybeRegister = (
+21 -1
View File
@@ -202,10 +202,16 @@ export class ToolRegistry {
private allKnownTools: Map<string, AnyDeclarativeTool> = 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<string>();
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);
+16
View File
@@ -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);
}