From 751c7f48395f125e125d3b827964a67704ce170e Mon Sep 17 00:00:00 2001 From: Leandro Barbosa Date: Wed, 18 Feb 2026 16:10:10 -0300 Subject: [PATCH] fix: add rate-limit retry with exponential backoff The retry loop only handled context/token errors, letting 429s and rate-limit responses fail immediately. Add detection for 429, rate_limit, resource_exhausted, overloaded, quota, and too_many_requests errors with exponential backoff (5s, 10s, 20s). Works regardless of whether fallback candidates are configured. --- pkg/agent/loop.go | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index ca763b97c..87fe5f0cf 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -617,8 +617,8 @@ func (al *AgentLoop) runLLMIteration(ctx context.Context, agent *AgentInstance, }) } - // Retry loop for context/token errors - maxRetries := 2 + // Retry loop for recoverable errors (context window + rate limits). + maxRetries := 3 for retry := 0; retry <= maxRetries; retry++ { response, err = callLLM() if err == nil { @@ -626,6 +626,34 @@ func (al *AgentLoop) runLLMIteration(ctx context.Context, agent *AgentInstance, } errMsg := strings.ToLower(err.Error()) + + // Rate-limit / transient errors: wait with exponential backoff. + isRateLimitError := strings.Contains(errMsg, "429") || + strings.Contains(errMsg, "rate limit") || + strings.Contains(errMsg, "rate_limit") || + strings.Contains(errMsg, "resource_exhausted") || + strings.Contains(errMsg, "resource exhausted") || + strings.Contains(errMsg, "too many requests") || + strings.Contains(errMsg, "overloaded") || + strings.Contains(errMsg, "quota") + + if isRateLimitError && retry < maxRetries { + backoff := time.Duration(1<