pub fn settle(
task: &mut Task,
verdict: Verdict,
detail: &str,
max_attempts: usize,
)Expand description
Record a finished run against the task it came from.
Kept pure and separate from the loop because this mapping is the retry policy, and a policy that can only be exercised by spawning a graph is a policy nobody checks. The table:
| run status | task becomes | attempt spent |
|---|---|---|
| parked at a boundary | Failed (requeued) | no |
Merged, Ready | Done | yes |
Stalled, quota hit | Failed (requeued) | no |
Failed, quota hit, no viable cand. | Failed (requeued) | no |
Stalled, no quota | Failed, or Held | yes |
Blocked with a PR | Held | yes |
Blocked, Failed otherwise | Failed, or Held | yes |
VerifiedNoop | Held | yes |
| anything non-terminal | Failed, or Held | yes |
The VerifiedNoop row is independent of the Failed-quota row above it,
deliberately: every candidate agreeing there is nothing to write is not a
machine fact about a rate limit, it is an unverified claim about the
task that a human still has to check — see RunStatus::VerifiedNoop’s
own doc and Task::handed_off. Held rather than Done on purpose: the
claim could be wrong (a misread instruction, a stale check), and closing
the task automatically on an implementer’s say-so would be the exact
failure mode task 391f’s own audit was raised to avoid. attempt spent is
yes here for the same reason it is on the Blocked-with-a-PR row just
above, which settles through the same Task::handed_off: Held is not
Failed-and-requeued, so nothing retries this task on the same unverified
claim regardless of whether the one already-spent attempt is refunded, and
Task::release resets the count to zero anyway the moment a human looks
at the evidence and lets it run again.
The Stalled-quota and Failed-quota rows are the ones worth reading
twice, together. A quorum lost to rate limits is a property of the machine
and not of the task, so the attempt is refunded and a reset quota picks
the work up where it stopped — and that is just as true when every
implement seat lost the same race and after_implement bails with nothing
to judge, which surfaces as Failed rather than Stalled but is the same
machine fact. The no_viable_candidates guard is what keeps that row
narrow: a Failed run that produced a real candidate which then lost for
some other reason still spends the attempt, exactly like the quorum lost
to judges that answered with the wrong shape is ordinary flakiness, and
refunding that takes the bound off the retry loop entirely: run e633
stalled with quota: [] after two judges wrote unusable JSON, was
refunded, and the next attempt paid for a fresh hour-long implement wave
before it could fail the same way. max_attempts exists precisely so
that cannot repeat forever.
A non-terminal status means execute returned while the graph was still
mid-flight, which is a bug rather than a verdict; it is treated as a
failure so that a task cannot loop on it either.
left_pr splits the Blocked row, and it is the difference between a run
that failed and a run that finished into a gate. See Task::handed_off.