From 38edc2366bc1ea566af94a95bd338eb047c3cbe9 Mon Sep 17 00:00:00 2001 From: Phil Larson Date: Sun, 23 Aug 2026 16:45:03 -0700 Subject: [PATCH] fix(core): restore executor workflow creation guidance (#3513) ## Summary - restore explicit executor guidance for assigning workflows to tasks the agent creates - keep the existing prohibition on rerouting the task currently being executed - restore parity between both built-in executor prompt variants and their regression test ## Test plan - `pnpm --filter @fusion/core exec vitest run --silent=passed-only --reporter=dot src/__tests__/agent-prompts.test.ts` - `pnpm --filter @fusion/core typecheck` - `pnpm check:changesets` - `pnpm exec eslint packages/core/src/agents/agent-prompts.ts` ## Summary by CodeRabbit * **Improvements** * Executor workflow guidance now appears only when task creation or delegation capabilities are available. * Built-in executor prompts provide clearer task-assignment instructions based on available capabilities. * Custom executor prompts remain unchanged. * Removed outdated workflow-setting guidance when task-management capabilities are unavailable. * **Tests** * Added coverage for task creation, delegation, and capability-specific workflow guidance scenarios. --- .changeset/executor-workflow-prompt-parity.md | 7 +++ .../core/src/__tests__/agent-prompts.test.ts | 24 ++++++++++ packages/core/src/agents/agent-prompts.ts | 34 ++++++++++++++ .../src/__tests__/executor-prompt.test.ts | 45 +++++++++++++++++++ .../executor-review-verdicts.test.ts | 3 +- packages/engine/src/executor/system-prompt.ts | 11 ++++- 6 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 .changeset/executor-workflow-prompt-parity.md diff --git a/.changeset/executor-workflow-prompt-parity.md b/.changeset/executor-workflow-prompt-parity.md new file mode 100644 index 0000000000..9ae6f0ce07 --- /dev/null +++ b/.changeset/executor-workflow-prompt-parity.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Match executor workflow guidance to the task-creation tools available in each session. +category: fix +dev: Built-in executor variants render created-task workflow guidance only on creator-capable tool surfaces. diff --git a/packages/core/src/__tests__/agent-prompts.test.ts b/packages/core/src/__tests__/agent-prompts.test.ts index 27f28296bb..d60e9b123e 100644 --- a/packages/core/src/__tests__/agent-prompts.test.ts +++ b/packages/core/src/__tests__/agent-prompts.test.ts @@ -240,6 +240,30 @@ describe("resolveAgentPrompt", () => { } }); + it("renders created-task workflow guidance only for creation tools on the resolved surface", () => { + const unavailable = resolveAgentPrompt("executor", undefined, { + taskCreateToolAvailable: false, + delegateTaskToolAvailable: false, + }); + expect(unavailable).not.toContain("set the workflow on tasks you create"); + + const taskCreateOnly = resolveAgentPrompt("executor", undefined, { + taskCreateToolAvailable: true, + delegateTaskToolAvailable: false, + }); + expect(taskCreateOnly).toContain("set the workflow on tasks you create via `fn_task_create`"); + expect(taskCreateOnly).not.toContain("set the workflow on tasks you create via `fn_delegate_task`"); + + const seniorDelegateOnly = resolveAgentPrompt("executor", { + roleAssignments: { executor: "senior-engineer" }, + }, { + taskCreateToolAvailable: false, + delegateTaskToolAvailable: true, + }); + expect(seniorDelegateOnly).toContain("set the workflow on tasks you create via `fn_delegate_task`"); + expect(seniorDelegateOnly).not.toContain("set the workflow on tasks you create via `fn_task_create`"); + }); + it("senior-engineer prompt limits fixes to impacted failures and follow-ups unrelated broad-suite failures", () => { const config: AgentPromptsConfig = { roleAssignments: { diff --git a/packages/core/src/agents/agent-prompts.ts b/packages/core/src/agents/agent-prompts.ts index 680d23a8b1..bfbe7eb201 100644 --- a/packages/core/src/agents/agent-prompts.ts +++ b/packages/core/src/agents/agent-prompts.ts @@ -1381,6 +1381,34 @@ export const BUILTIN_AGENT_PROMPTS: readonly AgentPromptTemplate[] = [ */ export interface ResolveAgentPromptOptions { plannerHeartbeatPatrolEnabled?: boolean; + taskCreateToolAvailable?: boolean; + delegateTaskToolAvailable?: boolean; +} + +const EXECUTOR_WORKFLOW_SELECTION_GUARDRAIL = "- Do not call `fn_workflow_select` to change the workflow of the task you are executing; you did not create that task, the user or triage did. The only exception is when the user explicitly requested a specific workflow for this task in a steering comment, task instruction, or similar direct instruction."; + +/* +FNXC:WorkflowRouting 2026-08-23-18:49: +Workflow assignment guidance must match the executor's real tool surface. Built-in creator-capable +personas may set workflows on tasks they create, while task-execution sessions omit this permission +because they structurally withhold both creation tools. Render the clause at resolution time so the +default and senior-engineer variants cannot drift from session capabilities. +*/ +function renderExecutorWorkflowRoutingToolSurface( + prompt: string, + options: ResolveAgentPromptOptions, +): string { + const availableCreationTools = [ + ...(options.taskCreateToolAvailable !== false ? ["`fn_task_create`"] : []), + ...(options.delegateTaskToolAvailable !== false ? ["`fn_delegate_task`"] : []), + ]; + if (availableCreationTools.length === 0) return prompt; + + const toolList = availableCreationTools.join(" or "); + return prompt.replace( + EXECUTOR_WORKFLOW_SELECTION_GUARDRAIL, + `${EXECUTOR_WORKFLOW_SELECTION_GUARDRAIL}\n- You may still set the workflow on tasks you create via ${toolList}.`, + ); } export function resolveAgentPrompt( @@ -1410,6 +1438,9 @@ export function resolveAgentPrompt( if (role === PLANNER_AGENT_ROLE && template.builtIn && template.id === "concise-triage") { return `${CONCISE_TRIAGE_PROMPT_TEXT}\n\n${buildConciseTriageHeartbeatGuidance(options)}`; } + if (role === "executor" && template.builtIn) { + return renderExecutorWorkflowRoutingToolSurface(template.prompt, options); + } return template.prompt; } @@ -1418,6 +1449,9 @@ export function resolveAgentPrompt( if (role === PLANNER_AGENT_ROLE && builtIn?.id === "default-triage") { return `${TRIAGE_PROMPT_TEXT}\n\n${buildTriageHeartbeatGuidance(options)}`; } + if (role === "executor" && builtIn) { + return renderExecutorWorkflowRoutingToolSurface(builtIn.prompt, options); + } return builtIn?.prompt ?? ""; } diff --git a/packages/engine/src/__tests__/executor-prompt.test.ts b/packages/engine/src/__tests__/executor-prompt.test.ts index 7b9cfecb07..b8b6e59a53 100644 --- a/packages/engine/src/__tests__/executor-prompt.test.ts +++ b/packages/engine/src/__tests__/executor-prompt.test.ts @@ -3080,6 +3080,9 @@ describe("executor base prompt runtime self-awareness", () => { }); describe("completion recommendation prompt contract", () => { + const getCreatedTaskWorkflowRoutingClause = (prompt: string): string | undefined => + prompt.split("\n").find((line) => line.startsWith("- You may still set the workflow on tasks you create via ")); + it.each([ ["built-in default", { agentPrompts: undefined }], ["custom executor prompt", { @@ -3121,6 +3124,48 @@ describe("completion recommendation prompt contract", () => { expect(customDisabledPrompt).toMatch(/Always send recommendations\.[\s\S]*Ignore any earlier generic recommendation guidance/); }); + /* + FNXC:WorkflowRouting 2026-08-23-13:25: + The production executor prompt resolver treats creation tools as available by default. Only an + explicit withheld=true removes that tool's created-task workflow guidance; omitted and empty + availability inputs preserve both clauses, while one withheld tool preserves the other clause. + */ + it.each([ + ["task creation only", { taskCreateWithheld: false, delegateWithheld: true }, "`fn_task_create`", "`fn_delegate_task`"], + ["delegation only", { taskCreateWithheld: true, delegateWithheld: false }, "`fn_delegate_task`", "`fn_task_create`"], + ])("renders workflow routing for the %s surface", async (_label, availability, presentTool, absentTool) => { + const { getExecutorSystemPrompt } = await import("../executor.js"); + const prompt = getExecutorSystemPrompt({ agentPrompts: undefined } as any, availability); + + const clause = getCreatedTaskWorkflowRoutingClause(prompt); + expect(clause).toBe(`- You may still set the workflow on tasks you create via ${presentTool}.`); + expect(clause).not.toContain(absentTool); + }); + + it.each([ + ["omitted availability", undefined], + ["empty availability", {}], + ])("keeps both workflow creation clauses for %s", async (_label, availability) => { + const { getExecutorSystemPrompt } = await import("../executor.js"); + const prompt = getExecutorSystemPrompt({ agentPrompts: undefined } as any, availability); + + expect(getCreatedTaskWorkflowRoutingClause(prompt)).toBe( + "- You may still set the workflow on tasks you create via `fn_task_create` or `fn_delegate_task`.", + ); + expect(prompt).not.toContain("Follow-up task creation is disabled for this session"); + }); + + it("removes created-task workflow guidance when both creation tools are withheld", async () => { + const { getExecutorSystemPrompt } = await import("../executor.js"); + const prompt = getExecutorSystemPrompt( + { agentPrompts: undefined } as any, + { taskCreateWithheld: true, delegateWithheld: true }, + ); + + expect(getCreatedTaskWorkflowRoutingClause(prompt)).toBeUndefined(); + expect(prompt).toContain("Task-execution sessions structurally withhold `fn_task_create` and `fn_delegate_task`"); + }); + it.each([ ["only task creation is withheld", { taskCreateWithheld: true }], ["only delegation is withheld", { delegateWithheld: true }], diff --git a/packages/engine/src/__tests__/executor-review-verdicts.test.ts b/packages/engine/src/__tests__/executor-review-verdicts.test.ts index de39879b10..5ce5b9e20f 100644 --- a/packages/engine/src/__tests__/executor-review-verdicts.test.ts +++ b/packages/engine/src/__tests__/executor-review-verdicts.test.ts @@ -570,7 +570,8 @@ describe("Code review verdict enforcement - fn_task_update blocking", () => { expect(capturedSystemPrompt).toContain("Do not call `fn_workflow_select` to change the workflow of the task you are executing"); expect(capturedSystemPrompt).toContain("The only exception is when the user explicitly requested a specific workflow for this task"); expect(capturedSystemPrompt).toContain("Implement required in-scope work directly here"); - expect(capturedSystemPrompt).not.toContain("You may still set the workflow on tasks you create via `fn_task_create` or `fn_delegate_task`"); + expect(capturedSystemPrompt).not.toContain("set the workflow on tasks you create"); + expect(capturedSystemPrompt).toContain("Task-execution sessions structurally withhold `fn_task_create` and `fn_delegate_task`"); }); // Note: The EXECUTOR_SYSTEM_PROMPT constant is tested indirectly via the buildExecutionPrompt test. diff --git a/packages/engine/src/executor/system-prompt.ts b/packages/engine/src/executor/system-prompt.ts index 93120e6345..02c7e2d510 100644 --- a/packages/engine/src/executor/system-prompt.ts +++ b/packages/engine/src/executor/system-prompt.ts @@ -318,7 +318,16 @@ export function getExecutorSystemPrompt( settings: Settings, toolAvailability?: { taskCreateWithheld?: boolean; delegateWithheld?: boolean }, ): string { - const customPrompt = resolveAgentPrompt("executor", settings.agentPrompts); + /* + FNXC:WorkflowRouting 2026-08-23-13:25: + Tool availability is opt-out at this production boundary: omitted availability, an empty object, + and omitted fields all preserve the core resolver's available-by-default contract. Only an + explicit withheld=true removes that tool's created-task workflow guidance. + */ + const customPrompt = resolveAgentPrompt("executor", settings.agentPrompts, { + taskCreateToolAvailable: toolAvailability?.taskCreateWithheld !== true, + delegateTaskToolAvailable: toolAvailability?.delegateWithheld !== true, + }); const basePrompt = customPrompt || EXECUTOR_SYSTEM_PROMPT; /* FNXC:TaskRecommendations 2026-08-10-01:15: