diff --git a/gateway/run.py b/gateway/run.py index 7c0bc37efb..4fe3668fc8 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -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 `` 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 `` 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) diff --git a/hermes_state_common.py b/hermes_state_common.py index 441e8f39c2..e49f521deb 100644 --- a/hermes_state_common.py +++ b/hermes_state_common.py @@ -23,6 +23,26 @@ _PREVIEW_SCAFFOLD_WINDOW = 400 _PREVIEW_MAX_CHARS = 60 +def routed_sessions_setting(key: str, env_var: str) -> Any: + """``sessions.`` 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.""" diff --git a/hermes_state_fts.py b/hermes_state_fts.py index bde71ef790..af96e898ad 100644 --- a/hermes_state_fts.py +++ b/hermes_state_fts.py @@ -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: diff --git a/hermes_state_search.py b/hermes_state_search.py index a96c7c9fe1..0cbdfeb37f 100644 --- a/hermes_state_search.py +++ b/hermes_state_search.py @@ -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])