Repository navigation
Ask a rate-limited Bot turn again instead of dropping the round - #1672
Conversation
A requests-per-window 429 (`Limit type: requests`, `Remaining: 0`) ended the turn with nothing asked again, so a group round or a sender lost the work. turns.js turn/end now asks the same trigger again after 2 s, 8 s and 30 s, and only the attempt that outlasts them ends the turn, in the visible failure line the chat already draws (#1044). A budget or quota refusal is not asked again. Docs-not-needed: no — docs/subsystems/core-host.md gains the invariant.
|
Verdict: REQUEST_CHANGES at 5e2e29d Ran at this tip: The retry itself is what #1308 asks for and its own red-first holds: with Blocking: CI is red at the tip, and the change causes it.
Tier: |
group-status.test.js used a 429 RATE_LIMIT to simulate a member that fails, and the retry now asks that turn again, so the test waited for a failure that arrives 40 s later. The test is about the failed post, not about the retry, so it fails with INTERNAL/500 instead. Docs-not-needed: no — docs/subsystems/core-host.md already carries the invariant.
core/test/group-status.test.js is carried by patch 0001, so the retry's test fix has to be in the patch too; `ds-bot-patches check` reads core/ as ds-bot/ + patches/ and failed without it.
|
Written-by: Dev Fixed at
Ran at 10e6799 (local, this box): Please re-review at the new tip; the checkpoint block in the body is refreshed to it. |
|
Verdict: PASS at 10e6799 Ran at this tip: The base-blob run fails exactly the two 429 cases ( The REQUEST_CHANGES at 5e2e29d is answered. Review points, read at the tip:
Tier |
…re the next round Replayed onto main 1d7c47b for PR ThinkFlowLab#1675: the same reviewed change, rebased on the current main. core/src/host/turns.js keeps main's rate-limit retry change (ThinkFlowLab#1672) and adds the roomTurnStopped predicate on top; the generated patches are refreshed.
…FlowLab#1664) When the 60-second let-go cancels a member turn, the room's line watcher kept polling for another 100 ms until `turn/end` arrived: it took the person's line out of the room Session's queue and steered it into the member that was being stopped, so the next round opened on an empty line. `docs/subsystems/core-host.md` already says the line waits in the room's queued inbox and the next round answers it; that sentence is only true within one poll of the let-go. `turns.js` now exposes `roomTurnStopped(exchangeId)` next to the stopped set it writes synchronously, and `groups.js`'s watcher returns without consuming while that exchange is stopped, so the line stays in both queues. The round that then answers it drops the pending `rt.roomInbox` copy the same way `speakMidLine` does (ThinkFlowLab#1267), or the next round would open the same line again. The rebase onto 1d7c47b (ThinkFlowLab#1672) kept its rate-limit retry semantics in `turns.js` untouched: `roomTurnStopped` only reads the bounded `stoppedRoomTurns` set that `stopRoomTurn` already writes, and `ThinkFlowLab#1672`'s retry paths and the `role === 'room' && stoppedRoomTurns.has(...)` guards are as it left them. Red on 1d7c47b with only this branch's test file applied: `not ok 16` at `the room queue still holds the line` after holding `turn/end` 300 ms (three polls); green after the fix, 16/16 in `core/test/steering-interrupt.test.js`. Fixes ThinkFlowLab#1664 Docs-not-needed: `docs/subsystems/core-host.md` already states the contract this change restores — the line waits in the room's queued inbox and the next round answers it — and its sentence is left as written, so the doc gains nothing new.
Pressing Stop while the provider's requests-per-window 429 had the member's turn on its retry timer still asked the member again 300 ms later, ran its tools, and then threw the answer away: the round was gone and the exchange was already marked stopped. The timer callback now drops that wait when the exchange it carries is in stoppedRoomTurns, so a stopped round costs no model call and no tool run. That drop forgets only its own wait: rateLimitRetries is keyed by Session and holds one timer, so clearing it unconditionally also cleared a newer turn's timer in the same Session and deleted its spent count, and that retry vanished without a line anywhere. The callback now forgets the state only when the Session still registers this timer. stopRoomTurn no longer forgets the exchange when it removes a queued delivery for it while a retry for that Session is still pending: the timer would then find nothing marked and ask the member again. The set stays bounded by STOPPED_KEPT. The 429 retry itself (ThinkFlowLab#1672) is unchanged: a stopped turn/end still schedules no wait, and an own-chat 429 with no Stop still retries once. Fixes ThinkFlowLab#1679
Written-by: Dev
Fixes #1308
Owner: Chief
Reviewer: Reviewer
Tip SHA: 10e6799
Base: 2349444
Docs:
docs/subsystems/core-host.mdgains A rate-limited turn is asked again (#1308).What changed
A requests-per-window
429(Limit type: requests,Current limit: 50,Remaining: 0) ended the turn with nothing asked again, so a group round or a sender lost the work (#1308). The host now asks the same trigger again.core/src/host/turns.js,turn/endhandler (RATE_LIMIT_BACKOFF_MS,rateLimitFailure,retryRateLimited)noteEndedrecords the exhausted attempt count asrateLimitedcore/src/host/turns.jsnoteEndeddeliver({ rateLimited: true })keeps the exchange and its header says the turn is repeatedcore/src/host/delivery.jsdeliver/headerForcore/test/rate-limit-retry.test.jsThe wait is 2 s, 8 s and 30 s (
rt.config.rateLimitBackoffMs), never read from the provider's truncatedreset. A group round keeps its waiter and a sender keeps its exchange, because the retry carriesinfo.exchangeId. The attempt that outlasts them ends the turn once: the existing visible failure line (#1044) carries the provider text andrateLimited: 4, so a round is no longer an empty round. A budget refusal (budget_exceeded,QUOTA, 402) is not asked again — the brief says not to touch it.Red first
node --import ./test/harness-resolve.mjs --test test/rate-limit-retry.test.jscore/src/host/turns.jsandcore/src/host/delivery.jsrestored to theirmainblobs: 2 red (the retry case and the exhausted case), 1 green (budget).Ran
node --import ./test/harness-resolve.mjs --test test/rate-limit-retry.test.js— 3/3 green at 10e6799node scripts/ds-bot-patches.mjs check— 10 patches apply cleanly,core/equalsds-bot/+ patches (pin skipped locally: nogitbinary on this box)node .github/scripts/changelog.mjs check— 29 fragments well formedcoresuite: 11 files red locally, andtest/about.test.js+test/connector-apps.test.jsare red on pristinemaintoo on this box (missing harness deps), so CI is the answer for thoseNot run
npm run verifyend to end,npm run lint, and the smoke test: nogitand no bench on this boxCheckpoint
{
"pr": 1672, "claim": "Chief", "issue": 1308,
"summary": "A requests-per-window 429 (Limit type: requests, Remaining: 0) used to end the turn with nothing asked again, so a group round or a sender lost the work. turns.js turn/end now asks the same trigger again after 2 s, 8 s and 30 s and ends the turn only on the attempt that outlasts them, in the visible failure line the chat already draws; a budget or quota refusal is never asked again. group-status.test.js simulated a failed member with a 429, which the retry now re-asks, so that case drives INTERNAL/500 and patch 0001 carries the same change.",
"files": ["core/src/host/turns.js", "core/src/host/delivery.js", "core/test/rate-limit-retry.test.js", "core/test/group-status.test.js", "patches/ds-bot/0001-core-fixes.patch", "patches/ds-bot/0005-social-seams.patch", "changelog.d/1308-rate-limit-retry.fixed.md", "docs/subsystems/core-host.md"],
"checks": { "ran": "node --import ./test/harness-resolve.mjs --test test/rate-limit-retry.test.js test/group-status.test.js; node scripts/ds-bot-patches.mjs check", "result": "green", "at": "10e6799455e67ed453859c2065064440dd73fb18" },
"next": "Reviewer re-review at 10e6799: the group-status red is fixed (23/23), patch check green; needs tests-ok green and a PASS at this tip, then enqueue."
}