feat(auto/loop): mid-loop self-feedback inline-fix dispatcher
Some checks are pending
CI / detect-changes (push) Waiting to run
CI / docs-check (push) Blocked by required conditions
CI / lint (push) Blocked by required conditions
CI / build (push) Blocked by required conditions
CI / integration-tests (push) Blocked by required conditions
CI / windows-portability (push) Blocked by required conditions
CI / rtk-portability (linux, blacksmith-4vcpu-ubuntu-2404) (push) Blocked by required conditions
CI / rtk-portability (macos, macos-15) (push) Blocked by required conditions
CI / rtk-portability (windows, blacksmith-4vcpu-windows-2025) (push) Blocked by required conditions

Bootstrap drains the triage queue once at session_start (headless.ts:
647 "[headless] autonomous: draining self-feedback triage queue
first..."). Entries filed DURING the autonomous run previously sat
until the next sf restart — defeating the self-heal thesis for
long-running sessions like the 3-day dogfood the user is running now.

dispatchSelfFeedbackInlineFixIfNeeded already exists in the extension
(self-feedback-drain.js:277) and is wired into bootstrap/register-
hooks at session_start. It selects high/critical candidates, debounces
via a claim file (so concurrent invocations skip), and on the headless
surface spawns a child `sf headless triage --apply` fire-and-forget —
the autonomous loop continues unblocked while triage runs in a child.

Hook it into the auto-loop top-of-iteration so it fires every
MID_LOOP_TRIAGE_INTERVAL=5 iterations. The dispatcher's own claim-file
debounce prevents re-dispatch of in-flight entries; pre-bootstrap-
drained entries get re-evaluated only when something new shows up.

Also ignores scripts/tmp-check-test-imports in biome — the check-
test-imports.test.mjs self-test creates regression fixtures there and
they triggered formatter errors on dirty exits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mikael Hugo 2026-05-17 00:50:03 +02:00
parent e8bbb477e6
commit 41276a7b7a
2 changed files with 33 additions and 1 deletions

View file

@ -16,7 +16,8 @@
"!!**/rust-engine/npm",
"!!**/*.min.js",
"!!packages/coding-agent/src/core/export-html/template.css",
"!!src/resources/skills/create-sf-extension/templates"
"!!src/resources/skills/create-sf-extension/templates",
"!!scripts/tmp-check-test-imports"
]
},
"formatter": {

View file

@ -18,6 +18,7 @@ import { resolveEngine } from "../engine-resolver.js";
import { getErrorMessage } from "../error-utils.js";
import { NOTICE_KIND } from "../notification-store.js";
import { sfRoot } from "../paths.js";
import { dispatchSelfFeedbackInlineFixIfNeeded } from "../self-feedback-drain.js";
import { recordSelfFeedback } from "../self-feedback.js";
import { getDatabase } from "../sf-db.js";
import {
@ -613,6 +614,12 @@ export async function autoLoop(ctx, pi, s, deps) {
recentUnits: persisted.recentUnits,
stuckRecoveryAttempts: persisted.stuckRecoveryAttempts,
consecutiveFinalizeTimeouts: 0,
// Iteration at which the autonomous loop last invoked the
// self-feedback inline-fix dispatcher. Used by the mid-loop
// triage-drain hook below to avoid spamming the dispatcher on
// every iteration while still picking up entries filed during
// the run (which previously sat until next session_start).
lastInlineFixDispatchIteration: 0,
};
let consecutiveErrors = 0;
let consecutiveCooldowns = 0;
@ -632,6 +639,30 @@ export async function autoLoop(ctx, pi, s, deps) {
}
iteration++;
debugLog("autoLoop", { phase: "loop-top", iteration });
// ── Mid-loop self-feedback inline-fix dispatcher ──────────────────
// Bootstrap drains the triage queue once at session_start (see
// src/headless.ts:647). Entries filed DURING the autonomous run
// previously sat until the next sf restart — defeats the
// self-heal thesis for long-running sessions. Re-invoke the
// inline-fix dispatcher every MID_LOOP_TRIAGE_INTERVAL iterations
// so any high/critical entry filed mid-run gets a triage pass
// without waiting for restart. The dispatcher already debounces
// via claim file (see dispatchSelfFeedbackInlineFixIfNeeded) and
// is fire-and-forget on the headless surface, so this is safe to
// call on every Nth iteration.
const MID_LOOP_TRIAGE_INTERVAL = 5;
if (
iteration > 1 &&
iteration - loopState.lastInlineFixDispatchIteration >=
MID_LOOP_TRIAGE_INTERVAL
) {
try {
dispatchSelfFeedbackInlineFixIfNeeded(s.basePath, ctx, pi);
} catch {
// Never block the loop on triage-dispatch failure.
}
loopState.lastInlineFixDispatchIteration = iteration;
}
// ── Halt watchdog: detect idle/stuck iterations ──
const { stuck, elapsedMs } = watchdog.check(ctx, iteration);
if (stuck) {