From a6fddd5465f4fff2c64a139747cbd0ee1b61fb68 Mon Sep 17 00:00:00 2001 From: xlx1212 Date: Thu, 6 Aug 2026 21:42:37 +0800 Subject: [PATCH] fix(compression): add 30s timeout to AI compression summary call Issue #1529: Context compression's AI model call blocks the conversation round loop synchronously. When the model is slow or unresponsive, the entire conversation freezes with no fallback. Wrap generate_compression_model_summary in tokio::time::timeout(30s). On timeout, return BitFunError::Timeout which falls into the existing generic error fallback path in build_planned_compression_result, using local structured compression instead of blocking indefinitely. This directly addresses the synchronous blocking concern raised in #1529 without changing compression trigger thresholds. --- .../src/agentic/execution/execution_engine.rs | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/crates/assembly/core/src/agentic/execution/execution_engine.rs b/src/crates/assembly/core/src/agentic/execution/execution_engine.rs index 0ce48d0e5..6017ee849 100644 --- a/src/crates/assembly/core/src/agentic/execution/execution_engine.rs +++ b/src/crates/assembly/core/src/agentic/execution/execution_engine.rs @@ -463,6 +463,11 @@ pub struct ExecutionEngine { impl ExecutionEngine { const AUTO_COMPRESSION_SAFETY_RESERVE_TOKENS: usize = 10_000; const MAX_COMPRESSION_OVERFLOW_ATTEMPTS: usize = 4; + /// Maximum wall-clock time to wait for an AI model compression summary. + /// If exceeded, fall back to local structured compression to avoid + /// blocking the conversation round loop. + const COMPRESSION_MODEL_SUMMARY_TIMEOUT: std::time::Duration = + std::time::Duration::from_secs(30); const MAX_MAIN_CONTEXT_OVERFLOW_RECOVERIES: usize = 2; const FINALIZE_AFTER_REPEATED_TOOL_FAILURES_REMINDER: &'static str = "This turn must end now because repeated tool failures have prevented further progress. Ignore any unfinished work. Your task now is to give the user a final answer. Do not call any more tools; any tool call will fail. Respond in plain text only. Summarize what was completed, what failed, the evidence available from the tool results, and the single best next step for the user."; const FINALIZE_AFTER_MAX_ROUNDS_REMINDER: &'static str = "This turn must end now because it has reached the round limit. Ignore any unfinished work. Your task now is to give the user a final answer. Do not call any more tools; any tool call will fail. Respond in plain text only. Summarize the most useful completed work and evidence collected so far, and clearly distinguish resolved items from anything still unresolved."; @@ -2070,8 +2075,9 @@ impl ExecutionEngine { plan.recent_tail_messages.len() ); - let summary_result = self - .generate_compression_model_summary(CompressionModelSummaryInput { + let summary_result = match tokio::time::timeout( + Self::COMPRESSION_MODEL_SUMMARY_TIMEOUT, + self.generate_compression_model_summary(CompressionModelSummaryInput { ai_client: ai_client.clone(), runtime_messages: &plan.summary_request_messages, dialog_turn_id, @@ -2080,8 +2086,22 @@ impl ExecutionEngine { prepended_prompt_reminders, primary_supports_image_understanding, trace_config: trace_config.clone(), - }) - .await; + }), + ) + .await + { + Ok(result) => result, + Err(_elapsed) => { + warn!( + "Compression model summary timed out after {:?}, falling back to structured local compression", + Self::COMPRESSION_MODEL_SUMMARY_TIMEOUT + ); + Err(BitFunError::Timeout(format!( + "Compression model summary timed out after {:?}", + Self::COMPRESSION_MODEL_SUMMARY_TIMEOUT + ))) + } + }; match summary_result { Ok(summary) => {