fix(multiplex): served profiles read their own sessions.* settings
gateway/run.py bridges the LAUNCH profile's sessions.cjk_fts / search_slow_ms into HERMES_CJK_FTS / HERMES_SEARCH_SLOW_MS at import (and re-bridged them per turn), and hermes_state_fts / hermes_state_search read os.getenv — so a served secondary always got the default profile's values. hermes_state_common.routed_sessions_setting() reads the routed profile's config.yaml under a HERMES_HOME override and the env bridge when unscoped; both consumers use it. The per-turn re-bridge is skipped inside a secondary's scope so it can no longer write the default's slots from a routed turn.
This commit is contained in:
+10
-6
@@ -1605,7 +1605,12 @@ def _reload_runtime_env_preserving_config_authority() -> None:
|
||||
|
||||
|
||||
def _bridge_max_turns_from_config(home: "Path") -> None:
|
||||
"""Re-bridge agent.max_turns (+ sessions.*) per turn; managed overlay applies or it reverts."""
|
||||
"""Re-bridge agent.max_turns (+ sessions.*) per turn; managed overlay applies or it reverts.
|
||||
Skipped inside a served secondary's scope: the env slots are the launch profile's and
|
||||
hermes_state reads the routed profile's ``sessions.*`` from its own config under scope."""
|
||||
from gateway.platforms._shared import profile_scoped
|
||||
if profile_scoped():
|
||||
return
|
||||
config_path = home / 'config.yaml'
|
||||
if not config_path.exists():
|
||||
return
|
||||
@@ -1658,15 +1663,14 @@ class HygieneTurnHoldExceeded(Exception):
|
||||
def _multiplex_profile_homes(config: object) -> list[tuple[str, "Path"]]:
|
||||
"""Return the authoritative profile set for one multiplex gateway config."""
|
||||
from hermes_cli.profiles import profiles_to_serve
|
||||
return list(profiles_to_serve(
|
||||
multiplex=True, profile_allowlist=getattr(config, "multiplex_profile_allowlist", None)))
|
||||
return list(profiles_to_serve(multiplex=True))
|
||||
|
||||
|
||||
def _cron_tick_profile_homes(config: object) -> list[tuple[str, "Path"]]:
|
||||
"""Profile homes the in-process ticker visits under multiplex: the served set PLUS the
|
||||
process-active profile. ``profiles_to_serve`` starts at default + allowlist, so a
|
||||
``--profile <name>`` multiplexer was omitted unless allowlisted — and allowlisting it would
|
||||
start a second adapter on its own bot token. Adapter startup already skips ``active``."""
|
||||
process-active profile: ``profiles_to_serve`` lists default + every live named profile, but a
|
||||
``--profile <name>`` multiplexer's own profile may sit outside ``profiles/`` (custom
|
||||
HERMES_HOME). Adapter startup already skips ``active``."""
|
||||
from hermes_cli.profiles import get_active_profile_name, get_profile_dir
|
||||
|
||||
homes = _multiplex_profile_homes(config)
|
||||
|
||||
@@ -23,6 +23,26 @@ _PREVIEW_SCAFFOLD_WINDOW = 400
|
||||
_PREVIEW_MAX_CHARS = 60
|
||||
|
||||
|
||||
def routed_sessions_setting(key: str, env_var: str) -> Any:
|
||||
"""``sessions.<key>`` for the profile whose state.db this process is touching.
|
||||
|
||||
``gateway/run.py`` bridges the LAUNCH profile's ``sessions.*`` into ``env_var`` (the cross-process
|
||||
carrier CLI/cron children read). Under a multiplexer a routed turn runs with a HERMES_HOME override
|
||||
and that env slot holds the default profile's value, so a served profile with different
|
||||
``sessions.*`` settings must read its own config.yaml. Unscoped: the env bridge, as before.
|
||||
Returns ``None`` when neither source sets the key.
|
||||
"""
|
||||
from hermes_constants import get_hermes_home_override
|
||||
|
||||
if get_hermes_home_override():
|
||||
try:
|
||||
from hermes_cli.config import load_config_readonly
|
||||
return (load_config_readonly().get("sessions") or {}).get(key)
|
||||
except Exception:
|
||||
return None
|
||||
return os.environ.get(env_var)
|
||||
|
||||
|
||||
def escape_like(text: str) -> str:
|
||||
"""Escape LIKE wildcards (``%``, ``_``) so derived text matches literally; pair with ``ESCAPE '\\'``.
|
||||
``_`` is common in branch names/titles/paths and a substring match must not silently widen."""
|
||||
|
||||
+5
-3
@@ -9,7 +9,8 @@ from pathlib import Path
|
||||
from typing import Sequence
|
||||
|
||||
from hermes_constants import get_hermes_home
|
||||
from hermes_state_common import FTS_CJK_STALE_KEY, FTS_STALE_KEY, _FTS_CJK_TRIGGERS, _FTS_TRIGGERS
|
||||
from hermes_state_common import (FTS_CJK_STALE_KEY, FTS_STALE_KEY, _FTS_CJK_TRIGGERS, _FTS_TRIGGERS,
|
||||
routed_sessions_setting)
|
||||
from hermes_state_errors import is_fts_scoped_corruption_error
|
||||
|
||||
# caplog tests pin the "hermes_state" logger name.
|
||||
@@ -102,8 +103,9 @@ def fts5_cjk_so_path() -> Path:
|
||||
|
||||
|
||||
def _cjk_fts_config_enabled() -> bool:
|
||||
"""config.yaml ``sessions.cjk_fts`` (default on), via its env bridge."""
|
||||
return os.getenv("HERMES_CJK_FTS", "1").strip().lower() not in ("0", "false", "off", "no")
|
||||
"""config.yaml ``sessions.cjk_fts`` (default on) for the profile being served."""
|
||||
value = routed_sessions_setting("cjk_fts", "HERMES_CJK_FTS")
|
||||
return value is None or str(value).strip().lower() not in ("0", "false", "off", "no")
|
||||
|
||||
|
||||
def load_fts5_cjk_extension(conn: sqlite3.Connection) -> bool:
|
||||
|
||||
+11
-3
@@ -12,17 +12,25 @@ import time
|
||||
from typing import Any, Callable, Collection, Dict, List, Optional, Tuple
|
||||
|
||||
from agent.skill_commands import describe_skill_invocation
|
||||
from utils import env_float
|
||||
from hermes_state_common import (
|
||||
FTS_CJK_STALE_KEY, FTS_SQL, FTS_STALE_KEY, FTS_STORAGE_VERSION, FTS_TOOL_CONTENT_PREFIX_CHARS,
|
||||
FTS_TOOL_FULL_CONTENT_HIGH_WATER_KEY, FTS_TRIGRAM_EXCLUDED_SOURCES, FTS_TRIGRAM_SQL,
|
||||
MAX_FTS5_QUERY_CHARS, SCHEMA_VERSION, _FTS_CJK_TRIGGERS,
|
||||
escape_like as _escape_like, fts_rebuild_admission, fts_trigram_session_sql,
|
||||
escape_like as _escape_like, fts_rebuild_admission, fts_trigram_session_sql, routed_sessions_setting,
|
||||
)
|
||||
|
||||
# Pre-split logger identity so log filtering/capture is unchanged.
|
||||
logger = logging.getLogger("hermes_state")
|
||||
|
||||
|
||||
def _search_slow_ms() -> float:
|
||||
"""``sessions.search_slow_ms`` for the served profile (default 1000; 0 logs every call)."""
|
||||
value = routed_sessions_setting("search_slow_ms", "HERMES_SEARCH_SLOW_MS")
|
||||
try:
|
||||
return 1000.0 if value is None or str(value).strip() == "" else float(value)
|
||||
except (TypeError, ValueError):
|
||||
return 1000.0
|
||||
|
||||
# Characters FTS5's query grammar rejects outside a quoted phrase (anything missing
|
||||
# reaches MATCH raw and raises -> zero results). ``%`` is deliberately excluded: the
|
||||
# CJK LIKE fallback needs it literal (that path escapes wildcards itself).
|
||||
@@ -1015,7 +1023,7 @@ class SessionSearchMixin:
|
||||
return rows
|
||||
finally:
|
||||
elapsed_ms = (time.time() - started) * 1000.0
|
||||
if elapsed_ms >= env_float("HERMES_SEARCH_SLOW_MS", 1000.0):
|
||||
if elapsed_ms >= _search_slow_ms():
|
||||
logger.info("slow session search: path=%s elapsed=%.0fms rows=%s query=%r",
|
||||
self._describe_search_path(query), elapsed_ms, len(rows) if rows is not None else "err",
|
||||
query[: 200])
|
||||
|
||||
Reference in New Issue
Block a user