From 4ffc8449a315504271f79953782d41fb33b68aa9 Mon Sep 17 00:00:00 2001 From: LFDM Core Date: Sun, 2 Aug 2026 14:45:47 -0700 Subject: [PATCH] fix(compression): overflow handlers pass overhead-aware token size to LCM recovery (issue 441) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root fix (Option A) for design-session "Context compression exhausted" crashes. The three compression-retry handlers after an API overflow/413/long-context error (conversation_loop.py ~4229/4488/4747) passed the tool-BLIND messages-only estimate (approx_tokens) to _compress_context, so hermes-lcm's forced-overflow recovery armed on the message count and missed overflows driven by tool-schema/system overhead. Now they pass estimate_request_tokens_rough(api_messages, tools=...) — the same overhead-aware estimator already used at :4580 — so recovery arms on the TRUE request size; LCM's _overflow_recovery_assembly_cap self-subtracts the overhead so the full request fits. Empirically validated on real failed session 6dddf1a67b76 (LCM engine, floor=24000/cap=248000): observed 256,359 >= 248,000 -> arms; recovery 231,313->208,559 msg-tokens -> full request 233,605 < 272,000 FITS. Prior messages-only path did NOT arm (231K < 248K) and crashed. Durable copy: ~/.hermes/local-patches/optionA-overflow-overhead-aware.patch (survives hermes update reset). Upstream PR pending. classify_api_error call at :3667 intentionally unchanged (not recovery). Co-Authored-By: Claude Opus 4.8 (1M context) --- agent/conversation_loop.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/agent/conversation_loop.py b/agent/conversation_loop.py index 1884c06489d9d..db04d95cc8352 100644 --- a/agent/conversation_loop.py +++ b/agent/conversation_loop.py @@ -4360,9 +4360,11 @@ def run_conversation( compression_attempts += 1 if compression_attempts <= max_compression_attempts: original_len = len(messages) + # Option A (LCM issue 441): overhead-aware request size so recovery arms on + # the true request (msgs + tools + system), not the tool-blind message count. messages, active_system_prompt = agent._compress_context( messages, system_message, - approx_tokens=approx_tokens, + approx_tokens=estimate_request_tokens_rough(api_messages, tools=agent.tools or None), task_id=effective_task_id, ) conversation_history = conversation_history_after_compression( @@ -4617,8 +4619,11 @@ def run_conversation( original_len = len(messages) original_tokens = estimate_messages_tokens_rough(messages) _overflow_input = messages + # Option A (LCM issue 441): overhead-aware request size so recovery arms on the + # true request (msgs + tools + system), not the tool-blind message count. messages, active_system_prompt = agent._compress_context( - messages, system_message, approx_tokens=approx_tokens, + messages, system_message, + approx_tokens=estimate_request_tokens_rough(api_messages, tools=agent.tools or None), task_id=effective_task_id, ) if messages is _overflow_input and compression_skipped_due_to_lock(agent): @@ -4878,8 +4883,13 @@ def run_conversation( original_len = len(messages) original_tokens = estimate_messages_tokens_rough(messages) _overflow_input = messages + # Option A (LCM issue 441): pass the OVERHEAD-AWARE request size (msgs + tool + # schemas + system), not the tool-blind message count, so LCM forced-overflow + # recovery arms on the TRUE request that overflowed. See hermes-lcm engine + # _should_force_overflow_recovery. (approx_tokens stays for the status display.) messages, active_system_prompt = agent._compress_context( - messages, system_message, approx_tokens=approx_tokens, + messages, system_message, + approx_tokens=estimate_request_tokens_rough(api_messages, tools=agent.tools or None), task_id=effective_task_id, ) if messages is _overflow_input and compression_skipped_due_to_lock(agent):