docs+test: document HERMES_WEBUI_VISIBLE_SESSION_LIMIT; probe it in a subprocess
- README env table + .env.example gain the new setting (default 20, fallback semantics), pinned by test_setting_is_documented. - tests/test_visible_session_limit_env.py no longer importlib.reload()s api.models in the shared pytest process (which recreated its module-level locks/caches/classes behind other modules' backs); each case now imports api.models in a fresh interpreter with a scrubbed env, matching test_issue3283_profiles_config_import_order.
This commit is contained in:
1 parent
63d024dc45
commit
899b89e1d3
3 files changed
+60
-18
No files matched your search
@@ -132,6 +132,11 @@
|
||||
# Extra sources for the notes drawer (see docs for the expected format).
|
||||
# HERMES_WEBUI_EXTERNAL_NOTES_SOURCES=
|
||||
|
||||
# Sidebar recency window: how many recent sessions are listed (default: 20).
|
||||
# Also caps how many delegated subagent children can nest at once; raise it
|
||||
# for wide fan-outs. Invalid or non-positive values fall back to 20.
|
||||
# HERMES_WEBUI_VISIBLE_SESSION_LIMIT=20
|
||||
|
||||
# Directory scanned for WebUI plugins (default: ~/.hermes/plugins).
|
||||
# HERMES_WEBUI_PLUGINS_DIR=~/.hermes/plugins
|
||||
|
||||
|
||||
@@ -373,6 +373,7 @@ Full list of environment variables:
|
||||
| `HERMES_HOME` | Windows: `%LOCALAPPDATA%\hermes`; POSIX: `~/.hermes` | Base directory for Hermes state (affects all paths) |
|
||||
| `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_VISIBLE_SESSION_LIMIT` | `20` | Size of the sidebar's interactive recency window (how many recent non-cron/webhook sessions are listed). Also bounds how many delegated subagent children can nest at once, since a child only renders when its row wins a slot in the window — raise it for wide fan-outs. Non-integer or non-positive values fall back to the default |
|
||||
| `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` | `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 |
|
||||
|
||||
|
||||
@@ -3,33 +3,69 @@
|
||||
The sidebar recency window also bounds how many delegated subagent children can
|
||||
render at once, since a child only nests when its row wins a slot in the same
|
||||
payload. Operators running wide fan-outs need to raise it without editing code.
|
||||
|
||||
The constant is bound once at import time, so each case imports ``api.models``
|
||||
in a fresh subprocess (same pattern as test_issue3283_profiles_config_import_order)
|
||||
rather than ``importlib.reload``-ing it in the shared test process — reloading
|
||||
would recreate the module's locks, caches, and classes while other modules keep
|
||||
references to the old objects.
|
||||
"""
|
||||
|
||||
import importlib
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
_PROBE = """
|
||||
import api.models
|
||||
print(api.models.CLI_VISIBLE_SESSION_LIMIT)
|
||||
"""
|
||||
|
||||
|
||||
def _reload_limit(monkeypatch, value):
|
||||
if value is None:
|
||||
monkeypatch.delenv("HERMES_WEBUI_VISIBLE_SESSION_LIMIT", raising=False)
|
||||
else:
|
||||
monkeypatch.setenv("HERMES_WEBUI_VISIBLE_SESSION_LIMIT", value)
|
||||
import api.models as models
|
||||
|
||||
return importlib.reload(models).CLI_VISIBLE_SESSION_LIMIT
|
||||
def _limit_in_fresh_interpreter(tmp_path, value):
|
||||
home = tmp_path / "home"
|
||||
home.mkdir()
|
||||
env = os.environ.copy()
|
||||
for key in list(env):
|
||||
if key.startswith("HERMES_WEBUI_") or key in ("HERMES_HOME", "HERMES_BASE_HOME"):
|
||||
env.pop(key)
|
||||
env["HOME"] = str(home)
|
||||
env["PYTHONPATH"] = str(REPO_ROOT)
|
||||
if value is not None:
|
||||
env["HERMES_WEBUI_VISIBLE_SESSION_LIMIT"] = value
|
||||
proc = subprocess.run(
|
||||
[sys.executable, "-c", _PROBE],
|
||||
cwd=REPO_ROOT,
|
||||
env=env,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
timeout=60,
|
||||
check=False,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
return int(proc.stdout.strip())
|
||||
|
||||
|
||||
def test_defaults_to_20_when_unset(monkeypatch):
|
||||
assert _reload_limit(monkeypatch, None) == 20
|
||||
def test_defaults_to_20_when_unset(tmp_path):
|
||||
assert _limit_in_fresh_interpreter(tmp_path, None) == 20
|
||||
|
||||
|
||||
def test_env_override_raises_the_window(monkeypatch):
|
||||
assert _reload_limit(monkeypatch, "64") == 64
|
||||
def test_env_override_raises_the_window(tmp_path):
|
||||
assert _limit_in_fresh_interpreter(tmp_path, "64") == 64
|
||||
|
||||
|
||||
def test_invalid_value_falls_back_to_default(monkeypatch):
|
||||
assert _reload_limit(monkeypatch, "bogus") == 20
|
||||
@pytest.mark.parametrize("value", ["bogus", "0", "-5"])
|
||||
def test_invalid_or_nonpositive_falls_back_to_default(tmp_path, value):
|
||||
assert _limit_in_fresh_interpreter(tmp_path, value) == 20
|
||||
|
||||
|
||||
def test_zero_and_negative_fall_back_to_default(monkeypatch):
|
||||
assert _reload_limit(monkeypatch, "0") == 20
|
||||
assert _reload_limit(monkeypatch, "-5") == 20
|
||||
def test_setting_is_documented():
|
||||
readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8")
|
||||
env_example = (REPO_ROOT / ".env.example").read_text(encoding="utf-8")
|
||||
assert "| `HERMES_WEBUI_VISIBLE_SESSION_LIMIT` | `20` |" in readme
|
||||
assert "# HERMES_WEBUI_VISIBLE_SESSION_LIMIT=20" in env_example
|
||||
Reference in new issue
Block a user