Skip to content

Commit 0841b69

Browse files
committed
fix(agents): reset rate-limit retry budget after recovery
1 parent 5d9bc1a commit 0841b69

3 files changed

Lines changed: 55 additions & 5 deletions

File tree

src/agents/embedded-agent-runner/run.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ import {
156156
resolveOverloadFailoverBackoffMs,
157157
resolveOverloadProfileRotationLimit,
158158
resolveRateLimitProfileRotationLimit,
159+
resolveNextSameModelRateLimitRetryCount,
159160
resolveSameModelRateLimitBackoffMs,
160161
type RuntimeAuthState,
161162
scrubAnthropicRefusalMagic,
@@ -1214,7 +1215,7 @@ export async function runEmbeddedAgent(
12141215
let lastContextBudgetStatus: EmbeddedAgentMeta["contextBudgetStatus"];
12151216
let runLoopIterations = 0;
12161217
let overloadProfileRotations = 0;
1217-
let sameModelRateLimitRetries = 0;
1218+
let consecutiveSameModelRateLimitRetries = 0;
12181219
let planningOnlyRetryAttempts = 0;
12191220
let reasoningOnlyRetryAttempts = 0;
12201221
let emptyResponseRetryAttempts = 0;
@@ -1376,12 +1377,12 @@ export async function runEmbeddedAgent(
13761377
}
13771378
};
13781379
const maybeRetrySameModelRateLimit = async (): Promise<boolean> => {
1379-
if (sameModelRateLimitRetries >= MAX_SAME_MODEL_RATE_LIMIT_RETRIES) {
1380+
if (consecutiveSameModelRateLimitRetries >= MAX_SAME_MODEL_RATE_LIMIT_RETRIES) {
13801381
return false;
13811382
}
1382-
const delayMs = resolveSameModelRateLimitBackoffMs(sameModelRateLimitRetries);
1383+
const delayMs = resolveSameModelRateLimitBackoffMs(consecutiveSameModelRateLimitRetries);
13831384
log.warn(
1384-
`rate-limit same-model retry ${sameModelRateLimitRetries + 1}/${MAX_SAME_MODEL_RATE_LIMIT_RETRIES} for ${sanitizeForLog(provider)}/${sanitizeForLog(modelId)}: delayMs=${delayMs}`,
1385+
`rate-limit same-model retry ${consecutiveSameModelRateLimitRetries + 1}/${MAX_SAME_MODEL_RATE_LIMIT_RETRIES} for ${sanitizeForLog(provider)}/${sanitizeForLog(modelId)}: delayMs=${delayMs}`,
13851386
);
13861387
try {
13871388
await sleepWithAbort(delayMs, params.abortSignal);
@@ -1393,7 +1394,10 @@ export async function runEmbeddedAgent(
13931394
}
13941395
throw err;
13951396
}
1396-
sameModelRateLimitRetries += 1;
1397+
consecutiveSameModelRateLimitRetries = resolveNextSameModelRateLimitRetryCount({
1398+
retriesSoFar: consecutiveSameModelRateLimitRetries,
1399+
retriedSameModelRateLimit: true,
1400+
});
13971401
return true;
13981402
};
13991403
// Resolve the context engine once and reuse across retries to avoid
@@ -2926,9 +2930,19 @@ export async function runEmbeddedAgent(
29262930
if (assistantFailoverOutcome.retryKind === "same_model_idle_timeout") {
29272931
sameModelIdleTimeoutRetries += 1;
29282932
}
2933+
if (assistantFailoverOutcome.retryKind !== "same_model_rate_limit") {
2934+
consecutiveSameModelRateLimitRetries = resolveNextSameModelRateLimitRetryCount({
2935+
retriesSoFar: consecutiveSameModelRateLimitRetries,
2936+
retriedSameModelRateLimit: false,
2937+
});
2938+
}
29292939
lastRetryFailoverReason = assistantFailoverOutcome.lastRetryFailoverReason;
29302940
continue;
29312941
}
2942+
consecutiveSameModelRateLimitRetries = resolveNextSameModelRateLimitRetryCount({
2943+
retriesSoFar: consecutiveSameModelRateLimitRetries,
2944+
retriedSameModelRateLimit: false,
2945+
});
29322946
if (assistantFailoverOutcome.action === "throw") {
29332947
traceAttempts.push({
29342948
provider: activeErrorContext.provider,

src/agents/embedded-agent-runner/run/helpers.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
buildErrorAgentMeta,
88
resolveFinalAssistantRawText,
99
resolveFinalAssistantVisibleText,
10+
resolveNextSameModelRateLimitRetryCount,
1011
resolveSameModelRateLimitBackoffMs,
1112
} from "./helpers.js";
1213

@@ -102,6 +103,34 @@ describe("resolveSameModelRateLimitBackoffMs", () => {
102103
});
103104
});
104105

106+
describe("resolveNextSameModelRateLimitRetryCount", () => {
107+
it("counts only consecutive same-model rate-limit retries", () => {
108+
let retriesSoFar = 0;
109+
110+
retriesSoFar = resolveNextSameModelRateLimitRetryCount({
111+
retriesSoFar,
112+
retriedSameModelRateLimit: true,
113+
});
114+
retriesSoFar = resolveNextSameModelRateLimitRetryCount({
115+
retriesSoFar,
116+
retriedSameModelRateLimit: true,
117+
});
118+
expect(retriesSoFar).toBe(2);
119+
120+
retriesSoFar = resolveNextSameModelRateLimitRetryCount({
121+
retriesSoFar,
122+
retriedSameModelRateLimit: false,
123+
});
124+
expect(retriesSoFar).toBe(0);
125+
126+
retriesSoFar = resolveNextSameModelRateLimitRetryCount({
127+
retriesSoFar,
128+
retriedSameModelRateLimit: true,
129+
});
130+
expect(retriesSoFar).toBe(1);
131+
});
132+
});
133+
105134
describe("buildErrorAgentMeta", () => {
106135
it("preserves active session file for error exits after transcript rotation", () => {
107136
// Error metadata follows the active session after transcript rotation so

src/agents/embedded-agent-runner/run/helpers.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,13 @@ export function resolveSameModelRateLimitBackoffMs(retriesSoFar: number): number
7070
return Math.min(SAME_MODEL_RATE_LIMIT_MAX_BACKOFF_MS, delay);
7171
}
7272

73+
export function resolveNextSameModelRateLimitRetryCount(params: {
74+
retriesSoFar: number;
75+
retriedSameModelRateLimit: boolean;
76+
}): number {
77+
return params.retriedSameModelRateLimit ? Math.max(0, params.retriesSoFar) + 1 : 0;
78+
}
79+
7380
const ANTHROPIC_MAGIC_STRING_TRIGGER_REFUSAL = "ANTHROPIC_MAGIC_STRING_TRIGGER_REFUSAL";
7481
const ANTHROPIC_MAGIC_STRING_REPLACEMENT = "ANTHROPIC MAGIC STRING TRIGGER REFUSAL (redacted)";
7582

0 commit comments

Comments
 (0)