fix(swarm): recognize unit-specific completion tools as implicit complete

Detected via supervisory check 2026-05-17: SF stuck in degenerate reassess-
roadmap loop on M010/S03 (5 iterations in 8min, all returning
outcome=continue). Root cause: synthesized-checkpoint in runUnitViaSwarm
only treats the generic `checkpoint` tool as a completion signal — but
units routinely complete via their unit-specific tool (reassess_roadmap
with verdict=roadmap-confirmed, validate_milestone, complete_milestone,
complete_slice, save_summary). The LLM correctly emitted the unit's
specific completion tool + assistant text "<turn_status>complete</turn_status>",
but workerSignaledOutcome stayed null → synthesized checkpoint fell back
to continue → solver re-iterated.

Fix: recognize UNIT_COMPLETION_TOOLS = {reassess_roadmap,
validate_milestone, complete_milestone, complete_slice, save_summary}
as implicit "complete" signals. The check fires when those tools are
called and an earlier explicit checkpoint hasn't already said
"complete" or "blocked".

This resolves sf-mp94lth4-ew26om and should prevent future
degenerate-iteration loops on reassess-roadmap and milestone completion
units. 13/13 existing M010 tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mikael Hugo 2026-05-17 03:59:30 +02:00
parent 460db52504
commit 0c0608fa50

View file

@ -562,6 +562,32 @@ async function runUnitViaSwarm(ctx, _pi, s, unitType, unitId, prompt, options) {
input: toolCall.arguments ?? {},
});
// #sf-mp94lth4-ew26om: Recognize unit-specific completion tools as
// implicit "complete" signals. Workers often call the unit's specific
// completion tool (reassess_roadmap with verdict, validate_milestone,
// complete_milestone, complete_slice, save_summary) and skip the
// generic checkpoint tool. Without this, the synthesized checkpoint
// falls back to outcome=continue and the solver iterates indefinitely
// despite the worker having genuinely finished (witnessed on
// reassess-roadmap M010/S03 — 5 iterations in 8min, all confirming
// completion in their text but never calling `checkpoint`).
const UNIT_COMPLETION_TOOLS = new Set([
"reassess_roadmap",
"validate_milestone",
"complete_milestone",
"complete_slice",
"save_summary",
]);
if (
UNIT_COMPLETION_TOOLS.has(toolCall.name) &&
workerSignaledOutcome !== "complete" &&
workerSignaledOutcome !== "blocked"
) {
// Mark as complete unless an earlier explicit checkpoint already
// said otherwise (don't override blocked / explicit progress).
workerSignaledOutcome = "complete";
}
// Detect the canonical completion-signal tool: "checkpoint".
// This is the same tool inspected by the legacy runUnit path's solver pass
// (phases-unit.js line ~989: activeToolsAllowlist: ["checkpoint"]).