fix(notice): repair exhaustion is a refused reply, not an outage #716

Merged
coilyco-ops merged 1 commit from fix/repair-exhaustion-is-not-an-outage into main 2026-08-13 20:31:44 +00:00
Member

Slice of issue 651, acceptance criterion 1 only:

A turn whose model calls all returned 200 never reports model backend unavailable.

That criterion needs no policy call. Criteria 4 and 5 do, and stay on 651.

What happens

A member asked a 53-byte question. The model answered correctly twice, both HTTP 200 in under eight seconds, both outcome: ok from agent-proxy. ValidateNeutralStyle refused both for first-person voice, the repair path exhausted, and the error fell through turnFailureNotice's stage switch to stageModel. The member was told the backend was down.

The fix, and its precedent

turnFailureNotice already carries a case for exactly this shape:

// Ahead of the stage switch, because this happens at the model stage and
// the backend answered every call. See issue 258.
case errors.Is(cause, ErrToolRoundsExhausted):
    return noticeRoundsSpent

Repair exhaustion is the same: model stage, every call answered. A sentinel ErrResponseRepairExhausted and a case beside that one, plus the matching failureCause arm so the label and the phrase keep agreeing as that file documents.

No new member-facing wording. noticeReplyBlocked already exists and already says the true thing. Reusing it is routing, not authoring.

Acceptance

  • Repair exhaustion carries ErrResponseRepairExhausted, and every model call in the fixture returns 200.
  • The notice is reply blocked by response check, rephrase, not the outage phrase.
  • failureCause reports reply_refused rather than stage_failed, which is criterion 3's harness half.
  • The refusing check still appears in the error text, because naming it is what made 651 diagnosable.
  • A genuine 503 still reports an outage.
  • Each row fails under a mutation removing the notice case, in a run where the package compiles.

Next owner

Engineer. Closed by the pull request that lands the change.

ward exec gate PASS on all six steps. Mutation: removing the notice case fails TestTheMemberIsNotToldTheBackendIsDown in a run where the package compiles.

closes #715

Slice of [issue 651](https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/651), acceptance criterion 1 only: > A turn whose model calls all returned 200 never reports `model backend unavailable`. That criterion needs no policy call. Criteria 4 and 5 do, and stay on 651. ## What happens A member asked a 53-byte question. The model answered correctly twice, both HTTP 200 in under eight seconds, both `outcome: ok` from agent-proxy. `ValidateNeutralStyle` refused both for first-person voice, the repair path exhausted, and the error fell through `turnFailureNotice`'s stage switch to `stageModel`. The member was told the backend was down. ## The fix, and its precedent `turnFailureNotice` already carries a case for exactly this shape: ```go // Ahead of the stage switch, because this happens at the model stage and // the backend answered every call. See issue 258. case errors.Is(cause, ErrToolRoundsExhausted): return noticeRoundsSpent ``` Repair exhaustion is the same: model stage, every call answered. A sentinel `ErrResponseRepairExhausted` and a case beside that one, plus the matching `failureCause` arm so the label and the phrase keep agreeing as that file documents. **No new member-facing wording.** `noticeReplyBlocked` already exists and already says the true thing. Reusing it is routing, not authoring. ## Acceptance - Repair exhaustion carries `ErrResponseRepairExhausted`, and every model call in the fixture returns 200. - The notice is `reply blocked by response check, rephrase`, not the outage phrase. - `failureCause` reports `reply_refused` rather than `stage_failed`, which is criterion 3's harness half. - The refusing check still appears in the error text, because naming it is what made 651 diagnosable. - A genuine 503 still reports an outage. - Each row fails under a mutation removing the notice case, in a run where the package compiles. ## Next owner Engineer. Closed by the pull request that lands the change. `ward exec gate` PASS on all six steps. Mutation: removing the notice case fails `TestTheMemberIsNotToldTheBackendIsDown` in a run where the package compiles. closes #715
fix(notice): repair exhaustion is a refused reply, not an outage
All checks were successful
ci / image-build (pull_request) Successful in 24s
ci / test (pull_request) Successful in 37s
ci / publish-echo-image (pull_request) Has been skipped
ci / publish-observed (pull_request) Has been skipped
bc910e8c26
Every model call in the reported trace returned 200 in under eight seconds and
agent-proxy logged outcome ok. ValidateNeutralStyle refused both replies, the
repair path exhausted, and the error fell through turnFailureNotice's stage
switch to stageModel, so the member was told the backend was down.

turnFailureNotice already carries this exact shape for ErrToolRoundsExhausted,
with a comment saying why it sits ahead of the stage switch. This adds the
sentinel beside it, and the matching failureCause arm so the label and the
phrase keep agreeing as that file documents.

No new member-facing wording: noticeReplyBlocked already says the true thing.

closes #715

Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-gaming/sirens-echo!716
No description provided.