From 00ef58f843052bb02e5d608e20e41a55fc3ed3e7 Mon Sep 17 00:00:00 2001 From: roboomp Date: Tue, 30 Jun 2026 22:30:15 +0000 Subject: [PATCH] fix(agent): steered override-schema subagents - Marked eval agent schema calls as caller overrides so subagent prompts can revoke native output/yield instructions.\n- Added override-schema prompt guidance telling agents to ignore conflicting native output labels and terminal-yield the caller schema object.\n- Added prompt coverage for the override notice.\n\nRefs #3926 --- .../src/eval/__tests__/agent-bridge.test.ts | 2 ++ .../coding-agent/src/eval/agent-bridge.ts | 1 + .../prompts/system/subagent-system-prompt.md | 4 ++++ packages/coding-agent/src/task/executor.ts | 6 +++++ .../test/task/subagent-system-prompt.test.ts | 23 +++++++++++++++++++ 5 files changed, 36 insertions(+) create mode 100644 packages/coding-agent/test/task/subagent-system-prompt.test.ts diff --git a/packages/coding-agent/src/eval/__tests__/agent-bridge.test.ts b/packages/coding-agent/src/eval/__tests__/agent-bridge.test.ts index 30c4678c1..3db8b33b1 100644 --- a/packages/coding-agent/src/eval/__tests__/agent-bridge.test.ts +++ b/packages/coding-agent/src/eval/__tests__/agent-bridge.test.ts @@ -235,10 +235,12 @@ describe("runEvalAgent", () => { expect(firstOptions.signal).toBe(abortController.signal); expect(firstOptions.parentActiveModelPattern).toBe("p/current"); expect(firstOptions.outputSchema).toBe(schema); + expect(firstOptions.outputSchemaOverridesAgent).toBe(true); expect(firstOptions.assignment).toBe("hello"); expect(firstOptions.description).toBe("My Agent"); expect(firstOptions.modelOverride).toEqual(["p/override"]); expect(secondOptions.outputSchema).toBeUndefined(); + expect(secondOptions.outputSchemaOverridesAgent).toBeUndefined(); }); it("forces LSP off for bridge subagents even when task.enableLsp is on", async () => { diff --git a/packages/coding-agent/src/eval/agent-bridge.ts b/packages/coding-agent/src/eval/agent-bridge.ts index 301709281..b27068b69 100644 --- a/packages/coding-agent/src/eval/agent-bridge.ts +++ b/packages/coding-agent/src/eval/agent-bridge.ts @@ -371,6 +371,7 @@ export async function runEvalAgent(args: unknown, options: EvalAgentBridgeOption parentActiveModelPattern, thinkingLevel: effectiveAgent.thinkingLevel, outputSchema: structured ? parsed.schema : undefined, + outputSchemaOverridesAgent: structured, sessionFile, persistArtifacts: Boolean(sessionFile), artifactsDir, diff --git a/packages/coding-agent/src/prompts/system/subagent-system-prompt.md b/packages/coding-agent/src/prompts/system/subagent-system-prompt.md index daebdad7b..295f0b29c 100644 --- a/packages/coding-agent/src/prompts/system/subagent-system-prompt.md +++ b/packages/coding-agent/src/prompts/system/subagent-system-prompt.md @@ -61,6 +61,10 @@ Yield protocol: This is your only way to return a final result. For structured results, you NEVER put JSON in plain text or substitute a text summary for `result.data`. +{{#if outputSchemaOverridesAgent}} +Caller schema overrides agent-native output instructions. Ignore ROLE-provided output/yield labels, field names, examples, and procedures that conflict with the interface below. Use ONLY labels/fields from the caller schema; safest path: omit `type` and terminal-yield the full `result.data` object. + +{{/if}} {{#if outputSchema}} Your result MUST match this TypeScript interface: ```ts diff --git a/packages/coding-agent/src/task/executor.ts b/packages/coding-agent/src/task/executor.ts index d0f718f00..d4cd31d0d 100644 --- a/packages/coding-agent/src/task/executor.ts +++ b/packages/coding-agent/src/task/executor.ts @@ -295,6 +295,11 @@ export interface ExecutorOptions { parentActiveModelPattern?: string; thinkingLevel?: ThinkingLevel; outputSchema?: unknown; + /** + * Caller supplied a schema that supersedes the agent's native output prompt. + * Eval `agent(..., schema=...)` sets this so built-in agents ignore stale yield labels. + */ + outputSchemaOverridesAgent?: boolean; /** Parent task recursion depth (0 = top-level, 1 = first child, etc.) */ taskDepth?: number; /** @@ -2141,6 +2146,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise { + it("revokes native output labels when caller schema overrides the agent", () => { + const out = prompt.render(subagentSystemPromptTemplate, { + agent: 'Use incremental yield with type: ["findings"].', + outputSchemaOverridesAgent: true, + outputSchema: { + properties: { + issue_key: { type: "string" }, + verdict: { enum: ["clean", "blockers"] }, + }, + }, + }); + + expect(out).toContain("Caller schema overrides agent-native output instructions"); + expect(out).toContain("Ignore ROLE-provided output/yield labels"); + expect(out).toContain("omit `type` and terminal-yield the full `result.data` object"); + }); +});