@@ -170,6 +170,88 @@ protected async Task<string> ExecuteChatCompletionAsync(
170170 var response = await ExecuteChatCompletionAsync ( systemPrompt , userPrompt , contextIdentifier ) ;
171171 return ( response , false , null ) ;
172172 }
173+ // ── Reasoning exhaustion catch (before transient error catch) ──
174+ catch ( ReasoningExhaustionException rex ) when ( UseResponsesApi && ResponsesClient != null )
175+ {
176+ var profile = ResponsesClient . Profile ;
177+ var maxExhaustionRetries = profile . ReasoningExhaustionMaxRetries ;
178+
179+ Logger . LogWarning (
180+ "[{Agent}] Reasoning exhaustion for {Context}: {Message}" ,
181+ AgentName , contextIdentifier , rex . Message ) ;
182+
183+ EnhancedLogger ? . LogBehindTheScenes ( "REASONING_EXHAUSTION" , "DETECTED" ,
184+ $ "max_output_tokens={ rex . MaxOutputTokens } , reasoning={ rex . ReasoningTokens } , " +
185+ $ "output={ rex . ActualOutputTokens } , effort='{ rex . ReasoningEffort } '", AgentName ) ;
186+
187+ // Escalation loop: increase tokens and promote reasoning effort
188+ var currentMaxTokens = rex . MaxOutputTokens ;
189+ var currentEffort = rex . ReasoningEffort ;
190+
191+ for ( int exhaustionRetry = 0 ; exhaustionRetry < maxExhaustionRetries ; exhaustionRetry ++ )
192+ {
193+ // Double the output tokens
194+ currentMaxTokens = ( int ) ( currentMaxTokens * profile . ReasoningExhaustionRetryMultiplier ) ;
195+ // Cap at profile maximum
196+ currentMaxTokens = Math . Min ( currentMaxTokens , profile . MaxOutputTokens ) ;
197+
198+ // Promote reasoning effort: low → medium → high
199+ if ( currentEffort == profile . LowReasoningEffort && currentEffort != profile . MediumReasoningEffort )
200+ currentEffort = profile . MediumReasoningEffort ;
201+ else if ( currentEffort == profile . MediumReasoningEffort && currentEffort != profile . HighReasoningEffort )
202+ currentEffort = profile . HighReasoningEffort ;
203+
204+ // ACTION 3: Thrash guard — if already at max tokens AND max effort, don't burn another API call
205+ if ( currentMaxTokens >= profile . MaxOutputTokens && currentEffort == profile . HighReasoningEffort
206+ && exhaustionRetry > 0 )
207+ {
208+ Logger . LogError (
209+ "[{Agent}] Thrash guard: already at max tokens ({Tokens}) and max effort ('{Effort}') " +
210+ "for {Context}. Failing fast — further retries are hopeless." ,
211+ AgentName , currentMaxTokens , currentEffort , contextIdentifier ) ;
212+
213+ EnhancedLogger ? . LogBehindTheScenes ( "REASONING_EXHAUSTION" , "THRASH_GUARD" ,
214+ $ "Stopped retrying: tokens={ currentMaxTokens } (max), effort='{ currentEffort } ' (max)", AgentName ) ;
215+
216+ break ;
217+ }
218+
219+ Logger . LogInformation (
220+ "[{Agent}] Reasoning exhaustion retry {Retry}/{MaxRetries} for {Context}: " +
221+ "max_output_tokens={Tokens}, effort='{Effort}'" ,
222+ AgentName , exhaustionRetry + 1 , maxExhaustionRetries ,
223+ contextIdentifier , currentMaxTokens , currentEffort ) ;
224+
225+ try
226+ {
227+ var retryResponse = await ResponsesClient . GetResponseAsync (
228+ systemPrompt , userPrompt , currentMaxTokens , currentEffort ) ;
229+
230+ EnhancedLogger ? . LogBehindTheScenes ( "REASONING_EXHAUSTION_RECOVERED" , "SUCCESS" ,
231+ $ "Recovered on retry { exhaustionRetry + 1 } with tokens={ currentMaxTokens } , effort='{ currentEffort } '",
232+ AgentName ) ;
233+
234+ ChatLogger ? . LogAIResponse ( AgentName , contextIdentifier , retryResponse ) ;
235+ return ( retryResponse , false , null ) ;
236+ }
237+ catch ( ReasoningExhaustionException )
238+ {
239+ // Still exhausted, continue escalation loop
240+ Logger . LogWarning (
241+ "[{Agent}] Still exhausted after retry {Retry} with tokens={Tokens}" ,
242+ AgentName , exhaustionRetry + 1 , currentMaxTokens ) ;
243+ }
244+ }
245+
246+ // All exhaustion retries failed
247+ lastException = rex ;
248+ Logger . LogError (
249+ "[{Agent}] All {MaxRetries} reasoning exhaustion retries failed for {Context}" ,
250+ AgentName , maxExhaustionRetries , contextIdentifier ) ;
251+
252+ return ( string . Empty , true , $ "Reasoning exhaustion: all { maxExhaustionRetries } escalation retries failed") ;
253+ }
254+ // ── END Reasoning exhaustion ──
173255 catch ( Exception ex ) when ( IsTransientError ( ex ) && attempt < maxRetries )
174256 {
175257 lastException = ex ;
0 commit comments