* perf(transcript): enable DOM virtualization by default (re-enable #4346 fix) (#6151) (#6155) * fix: revert DOM virtualization default to opt-in, fix gate RED (#6155) * fix(#6351): lower default session cache cap * Release p1-batch: session cache cap 300->100 (#6362) + virtualization comment (#6318) --------- Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com> Co-authored-by: webtecnica <webtecnica@users.noreply.github.com> Co-authored-by: Rod Boev <rod.boev@gmail.com> Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
This commit is contained in:
5 files changed
+44
-12
No files matched your search
@@ -11,12 +11,16 @@
|
||||
|
||||
- **Internal: removed a dead `rowIndex` parameter from the settled-scene row projector.** `attachLiveStream`'s `pushRow` helper carried an unused positional index left over from before final-segment eligibility moved to a row-identity `WeakSet`; dropping it is a pure no-op refactor (differential execution confirms byte-identical settled scenes — ordering, dedupe, final-prefix suppression, and sequence assignment all unchanged). Thanks @webtecnica. (#6258)
|
||||
|
||||
- **Internal: clarified the transcript-virtualization boot comment (opt-in default retained).** The comment above the boot-time `_virtualizeTranscript` apply now records that the #4346 Phase B footer-jitter fix resolved the original scroll-up flicker root cause, while virtualization stays opt-in (default OFF) until battle-tested further. Zero behavior change — the apply predicate, settings-load fallback, checkbox, and server default all remain opt-in/OFF and in agreement. Thanks @webtecnica. (#6318, #6155)
|
||||
|
||||
- **Docs: reconciled the assistant-reply lifecycle RFCs/contracts with shipped behavior.** The Stable Assistant Turn Anchors and Live-to-Final RFCs, `docs/CONTRACTS.md`, and the Phase 0 architecture inventory were updated from "proposed/scaffold" framing to "accepted/implemented" to match what actually ships (single assistant-turn owner projecting one `activity_scene_v1` into Compact Worklog / Transparent Stream / Final answer). Documentation-only — no runtime behavior or CI-gate change; the one code touch is a corrected comment header in `static/assistant_turn_anchors.js`. Remaining hardening stays tracked under #3400. Thanks @franksong2702. (#6144)
|
||||
|
||||
- **The busy-time send behavior is now called "Default message mode," and new installs default to Steer.** The Settings → Preferences control formerly labeled "Busy input mode" is renamed to "Default message mode," and a fresh install now defaults to **Steer** (inject a mid-turn correction without interrupting) instead of Queue. Your existing choice is preserved — if you ever saved settings, your current mode (Queue/Interrupt/Steer) is migrated as-is and unchanged; only never-configured installs pick up the new Steer default. The saved preference still survives a reload or a brief server outage (the localStorage mirror from the previous release is intact). Thanks @rodboev. (#5162, #5145)
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Long-running installs use less memory: the default resident-session cache cap is lowered from 300 to 100.** The in-memory `SESSIONS` LRU (compact session objects) is a dominant contributor to WebUI RSS climbing over hours of continuous use; the shipped no-override default is now tuned for the common single-user install. Larger deployments can still raise it via `webui.sessions_cache_max` in `config.yaml` (preferred) or the `HERMES_WEBUI_SESSIONS_MAX` env fallback. Eviction safety is unchanged — only clean, persisted, non-active sessions are ever dropped, and an evicted session lazily reloads from its JSON sidecar on next access (no data loss). Thanks @rodboev. (#6362, #6351)
|
||||
|
||||
- **Completed image tool-results are compacted from persisted history, cutting long-session memory and save/load cost.** After a turn finishes, base64 image parts in persisted *tool* results are replaced with a small `[screenshot]` text placeholder, so JSON sidecar saves/loads and session API responses no longer scale with image bytes (the active model still gets the full image while the tool call is running). The transform preserves every non-image part in place (text, documents, provider-specific parts) without flattening or reordering, only touches `role: tool` messages (user attachments are untouched), runs on all live and settled persistence writebacks, and is idempotent. Thanks @sjungwon03. (#6315, #1217)
|
||||
|
||||
- **Large embedded images no longer slow down API response redaction.** Native raster image data URIs (`data:image/png|jpeg|gif|webp|bmp;base64,…`) are skipped by the credential-redaction pass instead of being scanned byte-by-byte on every response — a multi-megabyte image is opaque binary, not text a credential regex should rewrite. The fast path only applies after the *entire* base64 payload is validated (canonical round-trip + structural walk), so a string that merely starts with an image header but carries trailing or appended text still goes through full redaction — a credential can never ride along in an image's tail. Non-image strings, and sibling secrets in the same payload, are unaffected. Thanks @inch772. (#6311)
|
||||
|
||||
@@ -373,7 +373,7 @@ Full list of environment variables:
|
||||
| `HERMES_CONFIG_PATH` | `$HERMES_HOME/config.yaml` | Path to Hermes config file |
|
||||
| `HERMES_WEBUI_SERVER_CWD` | *(unset)* | Working directory for the server process. Defaults to the agent dir; point it at a writable workspace when the agent dir is read-only so fallback relative writes land somewhere writable |
|
||||
| `HERMES_WEBUI_AGENT_CACHE_MAX` | `25` | Max live agent instances kept warm in the in-memory LRU. Each pins a full conversation transcript, so this is the dominant lever on resident memory — lower it on installs with many long sessions to cap RAM (at the cost of more cold reloads) |
|
||||
| `HERMES_WEBUI_SESSIONS_MAX` | `300` | Legacy operator override for the max compact `Session` objects held in the in-memory LRU. Prefer the `webui.sessions_cache_max` key in `config.yaml` (which takes precedence); this env var remains a fallback. Bounds resident memory so long-running installs cannot accumulate every session ever touched and eventually crash (#4765/#2233/#4633). Eviction only ever drops clean, persisted, non-active sessions — an evicted session lazily reloads from its JSON sidecar on next access |
|
||||
| `HERMES_WEBUI_SESSIONS_MAX` | `100` | Legacy operator override for the max compact `Session` objects held in the in-memory LRU. Prefer the `webui.sessions_cache_max` key in `config.yaml` (which takes precedence); this env var remains a fallback. Bounds resident memory so long-running installs cannot accumulate every session ever touched and eventually crash (#4765/#2233/#4633). Eviction only ever drops clean, persisted, non-active sessions; an evicted session lazily reloads from its JSON sidecar on next access |
|
||||
|
||||
Extension deployments can inspect sanitized, authenticated diagnostics at `GET /api/extensions/status`; see [WebUI Extensions](docs/EXTENSIONS.md#diagnostics).
|
||||
|
||||
|
||||
+4
-2
@@ -8502,16 +8502,18 @@ _INDEX_HTML_PATH = get_index_html_path()
|
||||
|
||||
# ── Thread synchronisation ───────────────────────────────────────────────────
|
||||
LOCK = threading.Lock()
|
||||
# Max compact Session objects held in the in-memory LRU (issue #3506, #4765).
|
||||
# Max compact Session objects held in the in-memory LRU (issue #3506, #4765, #6351).
|
||||
# Lighter than the agent cache (no live agent runtime), but still bounded so a
|
||||
# long-running self-hosted install cannot accumulate every session it ever
|
||||
# touched in RAM and eventually segfault (the #4765/#2233/#4633 crash cluster).
|
||||
# The shipped default is tuned for the common single-user install; larger
|
||||
# deployments can keep raising it through config.yaml or the legacy env fallback.
|
||||
#
|
||||
# Precedence for the effective cap is resolved by get_sessions_cache_max():
|
||||
# 1. config.yaml webui.sessions_cache_max (preferred, no new env var)
|
||||
# 2. HERMES_WEBUI_SESSIONS_MAX env var (legacy operator override)
|
||||
# 3. DEFAULT_SESSIONS_CACHE_MAX (sane bounded default)
|
||||
DEFAULT_SESSIONS_CACHE_MAX = 300
|
||||
DEFAULT_SESSIONS_CACHE_MAX = 100
|
||||
SESSIONS_MAX = _env_int("HERMES_WEBUI_SESSIONS_MAX", DEFAULT_SESSIONS_CACHE_MAX)
|
||||
|
||||
|
||||
|
||||
+4
-2
@@ -3225,8 +3225,10 @@ window._mirrorSpeechSettingsFromServer=_mirrorSpeechSettingsFromServer;
|
||||
window._hideEmptyStateSuggestions=s.hide_empty_state_suggestions===true;
|
||||
applyEmptyStateSuggestionPref();
|
||||
// #4343: transcript virtualization is EXPERIMENTAL/opt-IN (default OFF).
|
||||
// It caused scroll-up flicker on long sessions, so it's off for everyone
|
||||
// unless explicitly opted in; long transcripts render in full by default.
|
||||
// #4346 Phase B (footer-jitter suppression during virtual-scroll
|
||||
// measurement re-renders) resolved the scroll-up flicker root cause,
|
||||
// but virtualization remains opt-in until battle-tested further.
|
||||
// Users can explicitly enable it via Settings → virtualize_transcript.
|
||||
window._virtualizeTranscript=s.virtualize_transcript===true;
|
||||
window._showTps=!!s.show_tps;
|
||||
window._fadeTextEffect=!!s.fade_text_effect;
|
||||
|
||||
@@ -111,19 +111,43 @@ def _insert(sid_session):
|
||||
|
||||
# ─────────────────────────── config knob ────────────────────────────────────
|
||||
|
||||
def test_default_sessions_cache_cap_is_100():
|
||||
"""The shipped no-override session cache default is 100 (#6351)."""
|
||||
from api import config as _cfg
|
||||
|
||||
assert _cfg.DEFAULT_SESSIONS_CACHE_MAX == 100
|
||||
|
||||
|
||||
def test_cache_cap_reads_config_yaml_key():
|
||||
"""The cap is configurable via config.yaml webui.sessions_cache_max (#4765)."""
|
||||
from api import config as _cfg
|
||||
|
||||
old_sessions_max = _cfg.SESSIONS_MAX
|
||||
try:
|
||||
_cfg.SESSIONS_MAX = 222
|
||||
assert _cfg.get_sessions_cache_max({"webui": {"sessions_cache_max": 42}}) == 42
|
||||
# Invalid / missing values must fall back, never disable the bound.
|
||||
fallback = _cfg.get_sessions_cache_max({"webui": {"sessions_cache_max": "nope"}})
|
||||
assert isinstance(fallback, int) and fallback >= 1
|
||||
assert _cfg.get_sessions_cache_max({"webui": {}}) >= 1
|
||||
assert _cfg.get_sessions_cache_max({}) >= 1
|
||||
# A zero/negative typo must not disable the cap.
|
||||
assert _cfg.get_sessions_cache_max({"webui": {"sessions_cache_max": 0}}) >= 1
|
||||
assert _cfg.get_sessions_cache_max({"webui": {"sessions_cache_max": -5}}) >= 1
|
||||
assert _cfg.get_sessions_cache_max({"webui": {"sessions_cache_max": "nope"}}) == 222
|
||||
assert _cfg.get_sessions_cache_max({"webui": {"sessions_cache_max": 0}}) == 222
|
||||
assert _cfg.get_sessions_cache_max({"webui": {"sessions_cache_max": -5}}) == 222
|
||||
finally:
|
||||
_cfg.SESSIONS_MAX = old_sessions_max
|
||||
|
||||
|
||||
def test_cache_cap_preserves_environment_fallback():
|
||||
"""The parsed env fallback still wins when config is absent or invalid (#6351)."""
|
||||
from api import config as _cfg
|
||||
|
||||
old_sessions_max = _cfg.SESSIONS_MAX
|
||||
try:
|
||||
_cfg.SESSIONS_MAX = 222
|
||||
assert _cfg.get_sessions_cache_max({"webui": {}}) == 222
|
||||
assert _cfg.get_sessions_cache_max({}) == 222
|
||||
_cfg.SESSIONS_MAX = _cfg.DEFAULT_SESSIONS_CACHE_MAX
|
||||
assert _cfg.get_sessions_cache_max({"webui": {}}) == _cfg.DEFAULT_SESSIONS_CACHE_MAX
|
||||
assert _cfg.get_sessions_cache_max({}) == _cfg.DEFAULT_SESSIONS_CACHE_MAX
|
||||
finally:
|
||||
_cfg.SESSIONS_MAX = old_sessions_max
|
||||
|
||||
|
||||
# ─────────────────────────── invariant 1: eviction ──────────────────────────
|
||||
|
||||
Reference in new issue
Block a user