From 86c892b67c1447d9613d86eaa0f0908fbcc0b797 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sun, 26 Jul 2026 09:42:24 -0700 Subject: [PATCH] fix(FN-8600): make the duplicate-report instruction writable, not self-contradictory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The planning prompt said "do not write PROMPT.md" and, in the same breath, "write DUPLICATE: {id} to the output file" — where the output file IS PROMPT.md. A planner that took the first clause literally wrote no file and reported the duplicate in prose. The engine only ever reads the verdict from PROMPT.md's contents, so that duplicate was invisible: the task failed deterministic validation as "PROMPT.md file not found or empty", retried, terminalized to failed, emitted a task-wedge mail, was recovered to todo by self-healing, and re-planned — three full Opus planning cycles on FN-8600 before it was caught, with no operator decision ever surfaced because sourceMetadata.nearDuplicateOf is only set on the branch that parses the file. Both prompt sites now say to write PROMPT.md with the marker as its entire contents, and say why prose alone is not recorded. Note the engine ordering is already correct — tryFinalizeExplicitDuplicateMarker runs before validateGeneratedPrompt, and a worktree-local spec is recovered first. Nothing to reorder; the file simply never existed. Co-Authored-By: Claude Opus 5 (1M context) --- .changeset/duplicate-marker-must-write-prompt.md | 7 +++++++ packages/core/src/agent-prompts.ts | 10 +++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 .changeset/duplicate-marker-must-write-prompt.md diff --git a/.changeset/duplicate-marker-must-write-prompt.md b/.changeset/duplicate-marker-must-write-prompt.md new file mode 100644 index 0000000000..7c2524ca94 --- /dev/null +++ b/.changeset/duplicate-marker-must-write-prompt.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Stop duplicate tasks re-planning in a loop instead of asking you to keep or delete them. +category: fix +dev: The planning prompt told the planner "do not write PROMPT.md" and "write DUPLICATE: to the output file" — the output file being PROMPT.md. Planners resolved the contradiction by writing no file and reporting the duplicate in prose, which the engine cannot see (`parseExplicitDuplicateMarker` reads PROMPT.md's contents). The task then failed deterministic validation as "PROMPT.md not found or empty", retried, terminalized, sent a task-wedge mail, self-healed back to todo, and re-planned indefinitely — never setting `sourceMetadata.nearDuplicateOf`, which is what renders the operator's keep/delete decision. Both prompt sites now state that the file must be written with the marker as its entire contents. diff --git a/packages/core/src/agent-prompts.ts b/packages/core/src/agent-prompts.ts index a027e7575d..97db90c209 100644 --- a/packages/core/src/agent-prompts.ts +++ b/packages/core/src/agent-prompts.ts @@ -273,7 +273,7 @@ Write a lean, executable PROMPT.md quickly. Preserve safety gates, but skip heav - Preserve required safety sections for bugs, workflow routing, forensic tasks, and decision-only work. ## Duplicate check -Before writing a spec, call \`fn_task_list\` for active work, then call \`fn_task_search\` with 2-4 targeted keyword phrases from the title/description, such as file paths, symptoms, and symbols. For any likely match in \`done\` or \`archived\`, call \`fn_task_show\` and inspect it before deciding. If an existing task covers the same work, do not write PROMPT.md; write exactly \`DUPLICATE: {existing-task-id}\`. +Before writing a spec, call \`fn_task_list\` for active work, then call \`fn_task_search\` with 2-4 targeted keyword phrases from the title/description, such as file paths, symptoms, and symbols. For any likely match in \`done\` or \`archived\`, call \`fn_task_show\` and inspect it before deciding. If an existing task covers the same work, do not write a spec — but still write PROMPT.md, with its entire contents being the single line \`DUPLICATE: {existing-task-id}\` and nothing else. That file is how the duplicate is recorded; announcing it only in your reply leaves no plan behind and re-plans the task in a loop. ## Required PROMPT.md shape Write PROMPT.md with Original Description, Before → After Transformation, Mission, Dependencies, Context to Read First, File Scope, Steps, Documentation Requirements, Completion Criteria, Git Commit Convention, and Do NOT. Put \`## Original Description\` immediately after the title/\`Created\`/\`Size\` metadata with the operator's original task description copied **verbatim** (do not paraphrase). Put \`## Before → After Transformation\` next, before \`## Mission\`, with concise Before/After bullets: current state, target state, why it satisfies the user's request at a glance. In \`## Steps\`, every executable heading MUST use \`### Step N: \` (e.g. \`### Step 1: Preflight\`). Do not write bare \`### Preflight\` / \`### Implementation\` headings, and do not add review-level, triage subtask, or proactive subtask headings. @@ -552,9 +552,13 @@ Verified facts about this codebase's storage — cite these correctly so Plan Re ## Duplicate check Before writing a spec, first call \`fn_task_list\` to see active tasks, then call \`fn_task_search\` with 2-4 distinct keyword phrases from the task title and description (for example file paths, error symptoms, and symbol names). For any likely match in \`done\` or \`archived\`, call \`fn_task_show\` to inspect details before deciding. -If a task already covers the same work (even if worded differently), do NOT -write a PROMPT.md. Instead, write a single line to the output file: +If a task already covers the same work (even if worded differently), do not write a spec. +Instead you MUST still write PROMPT.md, with its ENTIRE contents being this one line and nothing else: \`DUPLICATE: {existing-task-id}\` +Writing that file IS how you report the duplicate. Reporting it only in your reply is not recorded: +the engine reads the verdict from PROMPT.md, so a duplicate announced in prose with no file written +reads as a planner that produced no plan, and the task is re-planned in a loop instead of being +parked for the operator's keep-or-delete decision. ## Dependency awareness When you plan to list a task in the \`## Dependencies\` section, first call \`fn_task_show\` on that task ID to read its PROMPT.md.