fix(progress): back the wait narration off instead of editing every beat #1021

Merged
coilysiren merged 1 commit from aos/claude/progress-backoff into main 2026-08-19 02:15:25 +00:00
Member

Closes #934. Stacked on #1014, which unblocks a red main.

What the edits actually were

The 10 edits are not stage transitions. They are Watch's ticker calling refreshnarrateWait every turnProgressEvery, which is turnProgressAfter * 2 = 16 seconds, matching the observed cadence exactly. So the count was a function of turn duration and nothing else, which is the third bullet of this issue's "why it matters".

Stage edits are left alone. Those carry a real transition, and #934's own middle option says an edit carrying no new information is the pure cost. This targets exactly those.

The fix

waitBackoff doubles per timer edit: 16s, 32s, 64s, 128s. The eleven beats from the incident trace now cost 4 writes instead of 10, and a stage change resets the counter because a new stage is new information rather than the same wait continuing.

Why there is no hard cap, which this issue asked for

The "Done when" here is "a single turn cannot spend more than a small bounded number of Discord writes on progress, independent of how long it runs". A hard cap is the literal reading and it would revert #899.

That issue is recorded in the code and in the test I had to change:

Every beat edits. The indicator went quiet at the cap, which is what a member read as the turn stalling around 130 seconds.

So a cap trades "12 writes" for "the member thinks it died", on a public recording, which is the same class of harm this issue opens with. The backoff never stops.

Strictly, that means this does not meet the acceptance as written: cost is logarithmic in duration rather than independent of it. In practice SIRENS_ECHO_REQUEST_TIMEOUT bounds the turn, so the ceiling is a handful of writes. Flagging rather than quietly redefining it. If you want the hard cap anyway, it is a few lines and #899 should be reopened alongside.

Two existing tests changed rather than deleted

Both asserted one edit per beat, which is the behaviour this issue calls the defect, so they could not both stay true. What they were protecting is kept and still asserted:

  • lines stack rather than replacing, "the shape Kai drew: three stacked lines, not one changing number"
  • the column still bounds height, not duration, advancing in place once full
  • the narration keeps running over a long turn, which is #899's requirement

Two tests added: the backoff itself, driven with the incident's own numbers (eleven 16-second beats), and that a stage change restarts it.

Also corrected

This issue's first bullet was already withdrawn in its own comments: the lanes no longer share a bot token, so the rate-limit argument is per-lane. I did not rely on it. The reason to fix this is the recording, which is the argument that survived.

Verification

just gate passes: build, policy-check, vet, test, test-skips, pre-commit. go test -race ./internal/... clean, which matters since the counter is touched under the progress lock.

Not verified against Discord. The turn shape is driven by a fake clock and a recording sink, as the existing progress tests are.

Closes #934. **Stacked on #1014**, which unblocks a red `main`. ## What the edits actually were The 10 edits are not stage transitions. They are `Watch`'s ticker calling `refresh` → `narrateWait` every `turnProgressEvery`, which is `turnProgressAfter * 2` = **16 seconds**, matching the observed cadence exactly. So the count was a function of turn duration and nothing else, which is the third bullet of this issue's "why it matters". `Stage` edits are left alone. Those carry a real transition, and #934's own middle option says an edit carrying no new information is the pure cost. This targets exactly those. ## The fix `waitBackoff` doubles per timer edit: 16s, 32s, 64s, 128s. The eleven beats from the incident trace now cost **4 writes instead of 10**, and a stage change resets the counter because a new stage is new information rather than the same wait continuing. ## Why there is no hard cap, which this issue asked for The "Done when" here is "a single turn cannot spend more than a small bounded number of Discord writes on progress, **independent of how long it runs**". A hard cap is the literal reading and **it would revert #899**. That issue is recorded in the code and in the test I had to change: > Every beat edits. The indicator went quiet at the cap, which is what a member read as the turn stalling around 130 seconds. So a cap trades "12 writes" for "the member thinks it died", on a public recording, which is the same class of harm this issue opens with. The backoff never stops. **Strictly, that means this does not meet the acceptance as written**: cost is logarithmic in duration rather than independent of it. In practice `SIRENS_ECHO_REQUEST_TIMEOUT` bounds the turn, so the ceiling is a handful of writes. Flagging rather than quietly redefining it. If you want the hard cap anyway, it is a few lines and #899 should be reopened alongside. ## Two existing tests changed rather than deleted Both asserted one edit per beat, which is the behaviour this issue calls the defect, so they could not both stay true. What they were protecting is kept and still asserted: * lines **stack** rather than replacing, "the shape Kai drew: three stacked lines, not one changing number" * the column still **bounds height, not duration**, advancing in place once full * the narration **keeps running** over a long turn, which is #899's requirement Two tests added: the backoff itself, driven with the incident's own numbers (eleven 16-second beats), and that a stage change restarts it. ## Also corrected This issue's first bullet was already withdrawn in its own comments: the lanes no longer share a bot token, so the rate-limit argument is per-lane. I did not rely on it. **The reason to fix this is the recording**, which is the argument that survived. ## Verification `just gate` passes: build, policy-check, vet, test, test-skips, pre-commit. `go test -race ./internal/...` clean, which matters since the counter is touched under the progress lock. **Not verified against Discord.** The turn shape is driven by a fake clock and a recording sink, as the existing progress tests are.
fix(progress): back the wait narration off instead of editing every beat
All checks were successful
ci / image-build (pull_request) Successful in 32s
ci / test (pull_request) Successful in 1m9s
ci / publish-echo-image (pull_request) Has been skipped
ci / publish-observed (pull_request) Has been skipped
3937e025c8
One turn spent 1 post, 10 edits and 1 delete to deliver a 1,269-byte
reply. The edits are the Watch ticker narrating a wait every
turnProgressEvery, so the count scaled with turn duration rather than
with anything the turn did.

The interval now doubles per timer edit. The same eleven beats cost four
writes, and a stage change resets it because a new stage is new
information rather than the same wait continuing.

It deliberately never stops. #899 records that the indicator going quiet
at a cap is what a member read as the turn stalling around 130 seconds,
so a hard edit cap would trade this defect for that one. The request
timeout bounds the doubling in practice.

Two tests changed rather than deleted. Both asserted one edit per beat,
which is the behaviour this issue calls the defect. What they were
protecting survives: lines stack rather than replacing, the column
bounds height, the last line advances in place, and the narration keeps
running.

Closes #934.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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!1021
No description provided.