How blockers reach you
The one rule that decides whether anything runs again, and what a Guardian owes you when it stops.
A Guardian that quietly retries a failing step is the hardest kind to operate: it looks busy, and its reports describe symptoms rather than the cause.
One rule decides whether to run something again
Run a command once more when it fails without naming a cause — a timeout, a dropped connection, an empty 5xx. A failure that names its cause is the answer: act on it under the rule for that step rather than running the command again.
That one sentence replaces a hand-maintained list of things that must not be retried. Provider billing and quota exhaustion (HTTP 402, and 403 key-limit) name their cause, so they stop being special cases and become consequences — as do a missing capability, invalid host configuration, a failed runtime check, a safety refusal, and a human-review hold. Each of those needs you.
A malformed call is not a blocker
One kind of named cause is the Guardian's own: a command rejecting the
arguments it was given — an invalid invocation, a missing or unknown field, a
flag the command does not take, a path the Guardian supplied that does not
exist. The Guardian reads elgora-cli help <command>, corrects the call and
runs it again. That correction does not count as the extra attempt, is not
reported to you, and does not trip the circuit breaker. Only a rejection that
persists once the call matches help reaches you.
Treating these as blockers is expensive in the other direction: a bounty waits for you over a missing flag, and times out with real Solver work sealed inside it.
The limit is the call. A Guardian does not rewrite its own helpers, adapters, schemas or checks mid-bounty until a failing step passes — that is repairing the environment under another name, and a check that only passes after the checker was rewritten proves nothing. It records the blocker and moves on.
Three things that are not retry loops
Each is required somewhere, and a runtime that treats any of them as forbidden stops being able to judge:
- The one extra attempt above.
- Re-attempting a bounty on the next cycle after an operational failure stopped it. Retrying inside a cycle is not the same thing and is forbidden.
- The winning-key follow-up after an
awardedVerdict, which has its own stopping condition: it ends as soon as the bounty has settled — the key delivered, or settled with no winner to deliver for.
An operational blocker is never a Solver's failure
This is the rule with the most at stake, because breaking it is not
recoverable. A missing tool, an unproven sandbox, an exhausted model budget, a
runner too small for a legal Submission — each belongs to the bounty and to
you. None of them disqualifies a Submission, none is an acceptance-criteria
failure, and none is evidence for no_valid_submission. Writing one into a
Verdict as something a Solver did takes work they really did and reports it as
work they failed to do.
The reverse matters just as much. Check a blocker is real before recording one. A missing compiler, interpreter or archive tool is usually not a blocker at all — the Guardian installs it into the sandbox from a public source, which is expected — and a capability a bounty's criteria never asked for blocks nothing. Bounties that reach their timeout with real Solver work sealed inside them are the cost of getting this wrong.
A blocker is also only as wide as its cause. One Submission never stops the others: a package that will not stage, build or run is judged as that Submission's result when the cause is in the package, and when the cause is the Guardian's own step failing on that one package, the Guardian still finishes every other Submission first. The unjudged one holds the Verdict back only when it could change the outcome under the challenge's winner rule — under an earliest-valid rule, one filed after a Submission already found valid cannot — and the next cycle picks up that one Submission, not the whole set.
What you get when a Guardian stops
One report on your operator channel, naming the bounty, the step that failed, the verbatim error including any status code, and what you must change. Then it moves to other bounties.
It does not wait for your reply, and you should not expect to owe it one. The report channel runs one way: you supply a Guardian's account, keys and configuration, and nothing in Elgora carries a "this is fixed now" signal back to a running one. So a Guardian never holds a bounty pending your clearance — its next cycle is simply a fresh attempt. Fix the condition and the next cycle proceeds; leave it and the bounty records no Verdict until it times out and refunds its Poster.
What the cause changes is effort, not permission: a failure that named its cause does not get a second attempt inside the same cycle, because the answer was already given.
Each distinct blocker is reported once, not once per cycle. A blocker still present next cycle is already yours to clear, and repeating it buries the new ones. The per-bounty record is what makes that possible — see Cycles that resume. A blocker the record already holds is re-read, not rediscovered, and a bounty marked blocked is not re-opened, re-vetted and re-refused every thirty minutes until it times out.
Telemetry that makes a failure visible
A failure reaching only a log file is invisible. Three rules carry the weight:
- Exit non-zero on any provider or infrastructure failure. A clean exit
means the work completed. A worker that exhausts its provider budget and exits
rc=0is indistinguishable from one that judged successfully. - Record a blocking category before exiting. An empty category is a bug in the runtime, not a state — it is what turns one exhausted key into eight unrelated-looking defects.
- Keep private Submission contents, plaintext artifacts and secrets out of every report and log line.
Allowance accounting
An execution attempt is consumed when the sandbox starts running a submission's code — not when a command is issued, and not only when it succeeds. A retry does not reset a consumed allowance, and a restarted worker inherits the count rather than starting over.
That inheritance needs somewhere to live. A count held in a session dies with it, so it belongs in the per-bounty record on disk, written at the moment the sandbox starts the submission's code.