Coding standard for recovery/error paths: make failure and recovery behavior truthful, gated, and user-consistent.
Apply this rule set: 1) Ground messages and state on authoritative runtime outcomes (avoid predicates/flags that can drift from behavior). If the only way to know is to attempt (e.g., reset/clear), attempt and then report based on the result. 2) Choose degradation vs failure by comparing which one is less misleading to the user/UI.
Illustrative pattern (retry gating + bounded empty fallback):
if provider_errored || cancel_token.is_cancelled() {
surface_error(provider_err);
return;
}
let visible_content_streamed = last_assistant_text_not_empty || surfaced_thinking_in_turn || streamed_images;
if visible_content_streamed {
// Avoid duplicate/incoherent resend.
surface_error(provider_err);
return;
}
let empty_response = no_tools_called && last_assistant_text.is_empty();
if empty_response {
if empty_turn_retries < MAX_EMPTY_TURN_RETRIES {
empty_turn_retries += 1;
warn!("Empty provider response; retrying {}/{}", empty_turn_retries, MAX_EMPTY_TURN_RETRIES);
messages_to_add.clear(); // don’t persist empty turn
continue;
}
// After retries, surface a real message.
let msg = Message::assistant().with_text(EMPTY_TURN_MESSAGE);
yield AgentEvent::Message(msg.clone());
messages_to_add.push(msg);
}