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
This commit is contained in:
@@ -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 () => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<SingleRes
|
||||
planReferencePath: options.planReference?.path ?? "",
|
||||
worktree: worktree ?? "",
|
||||
outputSchema: normalizedOutputSchema,
|
||||
outputSchemaOverridesAgent: options.outputSchemaOverridesAgent === true,
|
||||
ircPeers: ircEnabled ? renderIrcPeerRoster(id) : "",
|
||||
ircSelfId: ircEnabled ? id : "",
|
||||
});
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import "../../src/config/prompt-templates";
|
||||
import subagentSystemPromptTemplate from "../../src/prompts/system/subagent-system-prompt.md" with { type: "text" };
|
||||
|
||||
describe("subagent system prompt", () => {
|
||||
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");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user