Skip to main content

self-correction-loop

Invoke when an implementation attempt produces a failure signal (test failure, compile error, wrong output, crash, perf regression) and you must decide whether to retry, rethink, or back out — instead of hammering the same fix.

소스 정보

저장소
rlaope/ultraprompt
최근 소스 활동
2026년 9월 15일 02:53
감지된 SKILL.md 언어
영어
스타
33
포크
5

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

파일 탐색기
2 개 파일

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
self-correction-loop
description
Invoke when an implementation attempt produces a failure signal (test failure, compile error, wrong output, crash, perf regression) and you must decide whether to retry, rethink, or back out — instead of hammering the same fix.
# Self-Correction Loop > Status: v0.1 baseline draft — derived from the case catalog and axis definitions; awaiting trace evidence from Fable 5.1 case runs. ## When to apply - A test, build, benchmark, or runtime check just failed after a change you made. - You are about to re-run the same command after a fix and are not sure the fix addresses the *cause*, only the *message*. - You notice you have edited the same file or function more than twice for the same failing check. - Output is wrong but there is no error at all (silent bug: wrong numbers, flaky pass, loss not decreasing). - A fix made one check pass but broke a different one — the failure moved instead of dying. - You are inside an autonomous loop (CI bot, harness runner) and need explicit termination and escalation rules. ## Core loop 1. **Capture the signal verbatim before touching code.** Copy the exact error text, failing assertion, expected-vs-actual diff, or metric delta into working notes. Never work from a paraphrase of the failure — the paraphrase deletes the clue. 2. **Classify the failure into one of three tiers:** | Tier | Name | Definition | Typical evidence | |------|------|------------|------------------| | T1 | Mechanical | Typo, missing import, wrong arg order, off-by-one in a line you just wrote | Cause visible in the error text itself | | T2 | Wrong approach | Code does what you intended, but the intention can't solve the problem | Correct-looking code, structurally wrong result (wrong algorithm, wrong API contract, wrong concurrency model) | | T3 | Wrong problem model | Your understanding of the requirement, input data, or system behavior is false | Fixes keep failing in surprising ways; the spec in your head is the bug | 3. **Pick the response by tier, not by mood:** - T1 → fix inline and re-run immediately. One free retry, no ceremony. - T2 → stop editing. Write one sentence stating the alternative approach and why the current one is structurally unable to work. Then switch. - T3 → stop coding entirely. Re-read the spec, re-inspect the real input, or add an observation probe. Do not write a fix while your model of the problem is unverified. 4. **Log every attempt in one line before making it:** `attempt N: <hypothesis> → <change> → <result>` Keep this log in scratch notes or a comment block. Its purpose is to make cycling visible: if a new idea matches an old log line, that idea is banned. 5. **Enforce the loop budget.** Two similar failures at the same tier is a warning; the third forces escalation one tier up: - T1 → T2: "these aren't typos — the approach is wrong." - T2 → T3: "no approach works — my model of the problem is wrong." Never make a fourth attempt of the same kind. 6. **On escalation, widen the evidence before widening the edit.** Add a minimal probe from this menu, cheapest first: - Print or log the actual intermediate value at the suspected boundary. - Run the failing case in isolation (single test, single input, single request). - Bisect the input: does half the data still trigger it? - Diff behavior against a known-good reference (old commit, reference implementation, second environment). - Read the actual runtime state (debugger, strace, EXPLAIN, network capture) instead of inferring it from source. One new fact beats three new guesses. 7. **Locate the fix layer independently of the symptom layer.** Ask: "is the code that emitted this error the code that is wrong?" - A serializer exception is often a data-model bug. - A frontend render glitch is often an API-contract bug. - A flaky test is often a shared-state bug. Fix at the causing layer; at most add an assertion at the symptom layer. 8. **Decide fix-forward vs back-out explicitly.** Back out when any of these hold; otherwise fix forward: - The working-tree delta since the last green state is large or tangled with unrelated edits. - The attempt log shows ≥3 failed attempts layered on top of each other. - You can no longer state which of your edits are load-bearing and which are debris. Revert to the last known-good state and re-apply the smallest slice that reproduces the failure. Backing out is a move, not a defeat. 9. **After the fix, re-run the original failing check AND the checks that passed before.** A fix that migrates the failure elsewhere counts as attempt N+1 in the log, not as progress. 10. **Close the loop with a one-line cause statement:** "root cause: X, fixed at layer Y, guarded by check Z." If you cannot write that sentence, the failure is suppressed, not fixed — reopen. ## Heuristics - T1 fixes get exactly one free retry; if the "typo fix" fails, it was never a typo — reclassify as T2. - 3 similar failures = mandatory strategy change. Similar means: same error class, same file region, or same hypothesis family. - If two consecutive fixes each broke something the previous fix had fixed, you are in a whack-a-mole cycle: back out to last green and re-derive the invariant both call sites depend on. - A fix you cannot explain ("I moved the line and it passed") is not a fix — keep the failing check red and find the mechanism before proceeding. - Silent wrongness (bad output, no error raised) is T3 by default: instrument first, hypothesize second, edit third. - If the error message *changed* after your edit, that is progress even though it still fails — log it as a new failure family and reset the similar-failure counter. - Time-box T3 investigation: if 15–20 minutes of probing yields no new fact, state the top two hypotheses explicitly and design one experiment that discriminates between them. - When the same failure appears in multiple callers or tests, the fix almost never belongs in the callers — look one layer down for the shared dependency. - Cheap probes before expensive rewrites: a print statement, an isolated repro, or a `git stash` A/B run each cost minutes; a speculative refactor costs hours and destroys evidence. - In autonomous loops, hard-cap total iterations (e.g., 5) and require each iteration to change either the hypothesis or the evidence; an identical retry is grounds for termination, not repetition. - Distinguish "my change broke it" from "it was already broken": run the failing check on the pre-change state once before debugging your diff. ## Anti-patterns - **Brute-force retry.** Re-running the same failing command hoping for a different result, or re-applying a near-identical patch. → Corrective: consult the attempt log; if the idea matches a logged attempt, escalate tier instead of retrying. - **Error-message whack-a-mole.** Patching each error as it surfaces — add a null check here, catch an exception there, widen a type — without asking why the value is wrong. → Corrective: trace the bad value to where it was produced; fix production, not consumption. - **Paraphrase debugging.** Reasoning from memory of the error ("it was some kind of type error") instead of the verbatim text. → Corrective: re-run once, capture exact output, read it word by word — including the parts you skipped the first time. - **Sunk-cost tunneling.** Refusing to abandon an approach because hours of edits sit on it. → Corrective: the attempt log is the exit ramp — if it shows the approach failing structurally, revert to last green; the knowledge survives even when the code doesn't. - **Fix-at-symptom-layer.** Silencing the failure where it surfaced: skip the test, loosen the assertion, wrap in a retry. → Corrective: treat every urge to weaken a check as evidence you haven't found the cause; find the layer that produced the bad state. - **Unbounded self-correction.** An agent loop that patches → tests → patches forever with no termination rule. → Corrective: define the budget, the escalation ladder, and a "stop and report blockers" terminal state *before* entering the loop. ## Worked example *Illustrative construction, not a recorded run.* Task: a message-queue consumer's integration test fails. ``` FAILED test_consume_batch: assert processed == 100, got 97 ``` - Agent captures the verbatim output. 3 messages lost, no exception raised → silent wrongness → leans **T3**, but runs one cheap T1-level check first. - Logs `attempt 1: off-by-one in batch slicing? → read batching code → slicing correct, hypothesis dead`. - Two failure-free reads of the same code would be attempt cycling — so instead of re-reading, agent widens evidence: adds a probe logging message IDs at consume-time and at ack-time. Re-runs. - Observes: the 3 missing messages *were* consumed but never acked; each threw a JSON decode error swallowed by a bare `except: continue` in the consume loop. - Reclassifies as **T2** — the failure policy "silently skip poison messages" is the wrong approach — and notes the **layer split**: symptom lives in the test's counter assertion; cause lives in the consume loop's error handling. - Momentary temptation: change the assertion to `processed >= 97`. Rejected — that is fix-at-symptom-layer; the assertion is correctly reporting data loss. - Logs `attempt 2: poison messages swallowed → route decode failures to DLQ and count them → expect 97 processed + 3 dead-lettered`. - Implements DLQ routing at the consume loop. Re-runs the failing test *and* the previously green unit suite (guarding against failure migration). - Result: test updated to assert the new contract (97 processed, 3 in DLQ, 100 accounted for); all checks green, nothing else broke. - Closes: "root cause: bare except discarded undecodable messages; fixed at consume-loop error policy; guarded by DLQ-count assertion." - Loop terminates at attempt 2 of budget 5. Attempt log shows no repeated hypothesis. ## Trace evidence | Case | Session | What it showed | |------|---------|----------------| | _awaiting case runs_ | — | — |
GitHub에서 보기