From 69ffaf34435717f248f9336931d295a28ffd37ff Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Sat, 18 Jul 2026 11:21:22 -0700 Subject: [PATCH] Release: byte-size threshold for reconnect tail optimization (#6260, @webtecnica) (#6263) * perf: optimize large session reconnect by adding file-size threshold to tail optimization (#6241) When a sidecar JSON file exceeds 500 KB, the display-path tail optimization now fires even if the message count is within the raw_budget. This prevents sessions with few messages but large tool outputs (multi-MB JSON) from forcing a full-scan merge of all messages on reconnect. Changes: - Added _sidecar_file_exceeds_threshold() helper - Added _SIDECAR_BYTE_TAIL_THRESHOLD = 500_000 constant - Fall-through to truncation in _state_db_since_timestamp_for_limited_display when the sidecar file exceeds the threshold, regardless of message count * CHANGELOG: byte-size reconnect tail optimization (#6260) --------- Co-authored-by: webtecnica Co-authored-by: nesquena-hermes --- CHANGELOG.md | 2 ++ api/routes.py | 18 +++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 720d2b0d..0a3011a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ ### Fixed +- **Reopening a WebUI tab after a large active session is fast again, even when the session has few messages but large tool outputs.** The reconnect display-path tail optimization only fired based on message *count*, so a session with a handful of messages but multi-MB tool-call JSON forced a slow full-scan merge (bootstrap could take tens of seconds). The optimization now also fires when the session sidecar file exceeds 500 KB, regardless of message count โ€” the same messages are returned (verified: identical message set, count, offset, and truncation metadata), just via the fast path. Thanks @webtecnica. (#6260) + - **OIDC allowlist values containing spaces (e.g. a group named `Hermes Users`) are no longer split into separate entries.** The allowlist parser split comma-separated values on all whitespace, so a multi-word group/user name became two spurious entries. Allowlist parsing now splits only on commas/newlines (multi-word values stay intact and match their exact signed claim โ€” strictly tightening, no security loosening), while OAuth **scope** parsing keeps its space-delimited behavior per RFC 6749 ยง3.3. A blank allowlist entry no longer bricks an OIDC-only deployment. Thanks @webtecnica. (#6244) - **`/sessions` and `/resume` typed in the composer now open the session browser instead of being sent as chat text.** Both commands appeared in the slash-command autocomplete (they're exposed by `/api/commands`) but the WebUI send-time dispatch didn't handle them, so they were posted to the agent as plain text. They now open the native WebUI session browser and clear the composer โ€” on desktop and (via the mobile-aware opener) on phone-width layouts. Thanks @webtecnica. (#6245, #6224) diff --git a/api/routes.py b/api/routes.py index 52b880a6..91d0b73a 100644 --- a/api/routes.py +++ b/api/routes.py @@ -8522,6 +8522,10 @@ def _parse_msg_limit(raw): return max(1, min(value, _MAX_MSG_LIMIT)) +# If a sidecar JSON file exceeds this threshold, the display-path tail +# optimization fires regardless of message count. Sessions with few messages +# but large tool outputs (multi-MB JSON) should not force a full-scan merge. +_SIDECAR_BYTE_TAIL_THRESHOLD = 500_000 # 500 KB # Defensive row backstop for the GET /api/session display path's state.db read. # This is NOT a semantic window (the display window counts visible rows # post-reconciliation via _message_window_for_display); it is a safety net so a @@ -8641,6 +8645,16 @@ def _limited_webui_messages_for_display_with_sidecar(session, sidecar_messages, ) +def _sidecar_file_exceeds_threshold(session_id, threshold_bytes) -> bool: + """Check if the sidecar JSON file for ``session_id`` exceeds ``threshold_bytes``.""" + from api.config import SESSION_DIR + try: + p = SESSION_DIR / f"{session_id}.json" + return os.path.isfile(p) and os.path.getsize(p) > threshold_bytes + except Exception: + return False + + def _state_db_since_timestamp_for_limited_display(session, msg_limit, msg_before=None): """Return (timestamp floor, sidecar messages) for bounded state.db tail reads. @@ -8671,7 +8685,9 @@ def _state_db_since_timestamp_for_limited_display(session, msg_limit, msg_before return None, sidecar_messages raw_budget = max(300, limit * 10) if len(sidecar_messages) <= raw_budget: - return None, sidecar_messages + _sid = getattr(session, "session_id", "") or "" + if not _sid or not _sidecar_file_exceeds_threshold(_sid, _SIDECAR_BYTE_TAIL_THRESHOLD): + return None, sidecar_messages floor = min(sidecar_timestamps[-raw_budget:]) sidecar_before_count = sum(1 for ts in sidecar_timestamps if ts < floor)