fix(skill): back off crystallization retries after repeated failures - #2326
fix(skill): back off crystallization retries after repeated failures#2326smoryan wants to merge 3 commits into
Conversation
A crystallize or verify failure leaves the policy untouched, so every skill tick retried the same policy forever. The 2026-08-28 audit found 2,640 repeated failures over 25 days — the bulk of wasted skill invocations. Count consecutive failures per policy in kv. After 3 consecutive failures the policy skill_eligible flag is turned off and the eligibility gate skips it with a dedicated reason that distinguishes backoff trips from manual toggles. A successful crystallization clears the counter, and the new setSkillEligible repo method deliberately leaves updated_at untouched so the rebuild heuristic for existing skills is not triggered as a side effect. The llm-disabled skip reason is a global configuration state, not a policy failure, and never counts toward the backoff.
🤖 Open Code ReviewTarget: PR #2326 🔍 OpenCodeReview found 1 issue(s) in this PR. 1.
|
…ets a fresh window Co-Authored-By: LamzQ <linxlam@foxmail.com>
|
Thanks — confirmed and fixed in 3d2fe48: the counter is now cleared when the backoff trips, so a manual re-enable starts a fresh window instead of instantly re-tripping off the stale count. Locked by a new test (gives a manually re-enabled policy a fresh backoff window) plus two assertion updates reflecting the cleared-on-trip state. skill domain: 74 passed (12 files), tsc clean. |
…en at 3 Co-Authored-By: LamzQ <linxlam@foxmail.com>
|
Summary
A crystallize or verify failure leaves the policy untouched, so every skill
tick retried the same policy forever. On one long-running production install,
the 2026-08-28 audit counted 2,640 repeated crystallize failures over
25 days — the bulk of wasted skill invocations. This PR bounds that retry
cost: after 3 consecutive failures a policy is taken off the crystallization
path; at any earlier point a successful crystallization clears the counter
and failures start counting from zero again.
Problem
runSkilltreats both failure modes (crystallizer skip and verifierrejection) as advisory: it logs a warning and moves on, but the policy's
skill_eligibleflag staystrue. The next trigger re-selects the samepolicy and the same failure repeats — indefinitely for policies whose failure
cause is permanent (e.g. evidence that can never satisfy the verifier).
Change
core/skill/skill.ts— count consecutive failures per policy in kv(
skill.failCount:<id>); both failure paths callbumpFailureBackoff()before
continue, and a successful crystallization clears the counter.After
SKILL_FAILURE_BACKOFF_LIMIT(3) consecutive failures the policy'sskill_eligibleflag is turned off. The one deliberate exception is"llm-disabled": that skip reason is a global configuration state, not afailure of the policy, so it never counts (otherwise switching the LLM off
would trip the whole candidate pool with no recovery path).
core/skill/eligibility.ts—decide()now reportsskill_eligible=falseas its own skip reason (
"policy.skillEligible=false (backoff or manual)"),so a tripped backoff stays distinguishable from a manual toggle; the hidden
check inside
hasSuccessAnchor()is removed so the two sources don't blur.core/storage/repos/policies.ts— newsetSkillEligible()repo method. Itdeliberately leaves
updated_atuntouched: bumping it would flip therebuild heuristic for an existing skill as a side effect.
tests/unit/skill/backoff.test.ts— new regression suite (5 tests): tripafter the 3rd consecutive failure and not before, tripped policies are
skipped by the eligibility gate with the counter cleared on trip, a
successful crystallization clears the counter, a manually re-enabled
policy gets a fresh backoff window, and
llm-disabledticks never count.The counter is cleared when the backoff trips, so re-enabling the
skill_eligibleflag gives the policy a fresh 3-failure window.Tests
npx vitest run tests/unit/skill→ 74 passed (12 files) (69pre-existing + 5 new)
npx tsc --noEmit→ clean (exit 0)Related
failures (validator rejections of valid-but-incomplete drafts); this PR
bounds the retry cost of the failures that remain (LLM refusal, verifier
mismatches, permanently unqualifying evidence). Fixes fix: skill crystallization retries the same failing policies forever #2319.
Type of change
How Has This Been Tested?
npx vitest run tests/unit/skill— 74 passed)tsc --noEmitclean)Checklist