feat(headless): autonomous mode auto-drains self-feedback triage queue first
Before this change, `sf headless autonomous` only dispatched units for
the active milestone — never touched .sf/self-feedback.jsonl. The
existing `sf headless triage --apply` was a manual operator path
required for self-feedback to become actionable work. Defeats the
"SF self-heals" thesis: 146 entries can sit in the queue indefinitely
while the autonomous loop happily cranks on M005.
Now: at autonomous startup (not on resume, not on initial bootstrap)
SF calls handleTriage({ apply: true, max: 5 }) to drain the top-5
candidates from the triage queue before entering the dispatch loop.
The bound at max=5 keeps the upfront cost bounded; remaining items
process on the next session_start.
The comment on the existing triage handler in headless.ts:917-921
explicitly acknowledged the gap — autonomous-loop followUp delivery
was broken (sf-mp4rxkwb-l4baga). Wiring the deterministic triage
path BEFORE the dispatch loop closes that gap.
Opt-out: pass --skip-triage on the autonomous command (e.g. when
debugging a specific milestone without backlog churn).
Triage failures are non-fatal — they log a warning and the
autonomous loop continues with its existing milestone dispatch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8d0f41436b
commit
5a57549591
1 changed files with 35 additions and 0 deletions
|
|
@ -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";
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue