Files
correx/TASK.md
T
2026-08-02 14:13:26 +04:00

2.4 KiB

Vikunja: #460 — Retry budget: track all seen gate fingerprints, charge on repeat (cycle detection)

Problem

DefaultRetryCoordinator.decide compares the current failure fingerprint against only the previous one (core/kernel/src/main/kotlin/com/correx/core/kernel/retry/DefaultRetryCoordinator.kt:29):

val prevFingerprint = state.gateFailureFingerprints[gate]
val progressed = prevFingerprint != fingerprint
val charged = !progressed

A single slot cannot see a cycle. Whack-a-mole (fix A breaks B, fix B breaks A) alternates two fingerprints forever, every round reads as progress, nothing is ever charged, and the per-gate budget never triggers. DefaultSessionOrchestratorStep.kt:296 already acknowledges this ("can be fooled into treating as still progressing indefinitely") but guards only inside the recovery stage. A normal implementer stage has no equivalent guard.

Fix

Make OrchestrationState.gateFailureFingerprints[gate] a Set<String> of every fingerprint seen for that gate, and charge when the current one is a repeat:

val seen = state.gateFailureFingerprints[gate]
val charged = fingerprint in seen

Touches: OrchestrationState field type, the reducer that writes it, DefaultRetryCoordinator.decide.

Why not count-based progress

Rejected: "charge unless the failing-item count went down". A syntax error masks later errors — fix '}' expected and tsc parses further, reporting 5 real type errors where there was 1. Count goes 1 to 5 on the most productive edit of the run, and the guard would charge the budget for genuine progress.

Cases

  • Unmasking, {syntax} to {5 type errors}: new fingerprint, unseen, free. Correct.
  • Grinding down, 3 to 2 to 1 errors: each state distinct, all free. Correct.
  • Whack-a-mole, A/B/A: round 3 repeats round 1, charged. This is the case the single slot misses today.

Known limit

Detects cycles, not progress. A stage emitting a fresh distinct failure every round is still unbounded; only stageCount catches it. Worth measuring with scripts/artread.py over a few failed sessions before assuming that pattern matters.

Acceptance criteria

  • (fill in before starting)

Quality gate

# the command that must pass, e.g. make check

Rules

  • Do not edit this file.
  • One task, one session, one PR. Keep the diff under ~300 lines.
  • Finish with task pr.