fix(core): propagate subagent context to policy engine (#22086)

This commit is contained in:
N. Taylor Mullen
2026-03-11 16:01:45 -07:00
committed by GitHub
parent 1a7f50661a
commit 4a6d1fad9d
6 changed files with 44 additions and 1 deletions

View File

@@ -19,6 +19,8 @@ import type { EditorType } from '../utils/editor.js';
export interface AgentSchedulingOptions {
/** The unique ID for this agent's scheduler. */
schedulerId: string;
/** The name of the subagent. */
subagent?: string;
/** The ID of the tool call that invoked this agent. */
parentCallId?: string;
/** The tool registry specific to this agent. */
@@ -46,6 +48,7 @@ export async function scheduleAgentTools(
): Promise<CompletedToolCall[]> {
const {
schedulerId,
subagent,
parentCallId,
toolRegistry,
signal,
@@ -69,6 +72,7 @@ export async function scheduleAgentTools(
messageBus: toolRegistry.getMessageBus(),
getPreferredEditor: getPreferredEditor ?? (() => undefined),
schedulerId,
subagent,
parentCallId,
onWaitingForConfirmation,
});

View File

@@ -1099,6 +1099,7 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
toolRequests,
{
schedulerId: this.agentId,
subagent: this.definition.name,
parentCallId: this.parentCallId,
toolRegistry: this.toolRegistry,
signal,

View File

@@ -68,6 +68,7 @@ describe('policy.ts', () => {
{ name: 'test-tool', args: {} },
undefined,
undefined,
undefined,
);
});
@@ -97,6 +98,7 @@ describe('policy.ts', () => {
{ name: 'mcp-tool', args: {} },
'my-server',
{ readOnlyHint: true },
undefined,
);
});

View File

@@ -52,6 +52,7 @@ export function getPolicyDenialError(
export async function checkPolicy(
toolCall: ValidatingToolCall,
config: Config,
subagent?: string,
): Promise<CheckResult> {
const serverName =
toolCall.tool instanceof DiscoveredMCPTool
@@ -66,6 +67,7 @@ export async function checkPolicy(
{ name: toolCall.request.name, args: toolCall.request.args },
serverName,
toolAnnotations,
subagent,
);
const { decision } = result;
@@ -115,6 +117,7 @@ export async function updatePolicy(
toolInvocation?: AnyToolInvocation,
): Promise<void> {
const deps = { ...context, toolInvocation };
// Mode Transitions (AUTO_EDIT)
if (isAutoEditTransition(tool, outcome)) {
deps.config.setApprovalMode(ApprovalMode.AUTO_EDIT);

View File

@@ -368,6 +368,32 @@ describe('Scheduler (Orchestrator)', () => {
);
});
it('should propagate subagent name to checkPolicy', async () => {
const { checkPolicy } = await import('./policy.js');
const scheduler = new Scheduler({
context: mockConfig,
schedulerId: 'sub-scheduler',
subagent: 'my-agent',
getPreferredEditor: () => undefined,
});
const request: ToolCallRequestInfo = {
callId: 'call-1',
name: 'test-tool',
args: {},
isClientInitiated: false,
prompt_id: 'p1',
};
await scheduler.schedule([request], new AbortController().signal);
expect(checkPolicy).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
'my-agent',
);
});
it('should correctly build ValidatingToolCalls for happy path', async () => {
await scheduler.schedule(req1, signal);

View File

@@ -61,6 +61,7 @@ export interface SchedulerOptions {
messageBus?: MessageBus;
getPreferredEditor: () => EditorType | undefined;
schedulerId: string;
subagent?: string;
parentCallId?: string;
onWaitingForConfirmation?: (waiting: boolean) => void;
}
@@ -102,6 +103,7 @@ export class Scheduler {
private readonly messageBus: MessageBus;
private readonly getPreferredEditor: () => EditorType | undefined;
private readonly schedulerId: string;
private readonly subagent?: string;
private readonly parentCallId?: string;
private readonly onWaitingForConfirmation?: (waiting: boolean) => void;
@@ -115,6 +117,7 @@ export class Scheduler {
this.messageBus = options.messageBus ?? this.context.messageBus;
this.getPreferredEditor = options.getPreferredEditor;
this.schedulerId = options.schedulerId;
this.subagent = options.subagent;
this.parentCallId = options.parentCallId;
this.onWaitingForConfirmation = options.onWaitingForConfirmation;
this.state = new SchedulerStateManager(
@@ -563,7 +566,11 @@ export class Scheduler {
const callId = toolCall.request.callId;
// Policy & Security
const { decision, rule } = await checkPolicy(toolCall, this.config);
const { decision, rule } = await checkPolicy(
toolCall,
this.config,
this.subagent,
);
if (decision === PolicyDecision.DENY) {
const { errorMessage, errorType } = getPolicyDenialError(