diff --git a/src/headless.ts b/src/headless.ts index dc924b79a..134a0215a 100644 --- a/src/headless.ts +++ b/src/headless.ts @@ -634,6 +634,41 @@ async function runHeadlessOnce( options.command = "new-milestone"; options.chainAutonomous = true; options.contextText = buildAutoBootstrapContext(process.cwd()); + } else if (!options.commandArgs.includes("--skip-triage")) { + // Auto-drain the self-feedback triage queue before entering the + // autonomous dispatch loop. Without this, items in + // .sf/self-feedback.jsonl sit unprocessed and SF can only work on + // the active milestone — defeating the self-heal thesis. + // Comment on headless-triage at line 917-921 acknowledges that + // autonomous-loop followUp delivery was unreliable (sf-mp4rxkwb-l4baga), + // hence the deterministic operator path. This wires the deterministic + // path BEFORE the dispatch loop so autonomous == triage-then-dispatch. + // Skipped when resuming (resumeSession check above) or when the user + // passes --skip-triage to opt out (e.g. to debug a specific milestone + // without backlog churn). + try { + const { handleTriage } = await import("./headless-triage.js"); + if (!options.json) { + process.stderr.write( + "[headless] autonomous: draining self-feedback triage queue first...\n", + ); + } + await handleTriage(process.cwd(), { + apply: true, + json: !!options.json, + max: 5, // bound the up-front cost; remainder flushes on next session_start + }); + } catch (err) { + // Triage failure must not block autonomous mode — the loop's own + // dispatch will keep going; backlog will just stay until next run. + if (!options.json) { + process.stderr.write( + `[headless] autonomous: triage drain failed (non-fatal): ${ + err instanceof Error ? err.message : String(err) + }\n`, + ); + } + } } } const isNewMilestone = options.command === "new-milestone";