Use read-only config loader and honor HERMES_IGNORE_USER_CONFIG in delegation config

This commit is contained in:
Shannon Sands 2026-07-08 08:57:02 +10:00 committed by Teknium
parent 0263f1d12e
commit bf7639138e
2 changed files with 51 additions and 11 deletions

View file

@ -2412,7 +2412,9 @@ class TestConcurrencyDefaults(unittest.TestCase):
}
with patch.dict("sys.modules", {"cli": stale_cli}):
with patch("hermes_cli.config.load_config", return_value=active_config):
with patch(
"hermes_cli.config.load_config_readonly", return_value=active_config
):
self.assertEqual(_load_config()["max_concurrent_children"], 50)
self.assertEqual(_get_max_concurrent_children(), 50)
@ -2426,9 +2428,35 @@ class TestConcurrencyDefaults(unittest.TestCase):
}
with patch.dict("sys.modules", {"cli": fallback_cli}):
with patch("hermes_cli.config.load_config", side_effect=RuntimeError("boom")):
with patch(
"hermes_cli.config.load_config_readonly",
side_effect=RuntimeError("boom"),
):
self.assertEqual(_load_config()["max_concurrent_children"], 8)
def test_load_config_prefers_cli_config_when_user_config_ignored(self):
# `hermes chat --ignore-user-config` sets HERMES_IGNORE_USER_CONFIG=1,
# which only load_cli_config() honors. The delegation loader must keep
# CLI_CONFIG authoritative under the flag so user config.yaml
# delegation keys stay suppressed.
ignoring_cli = types.ModuleType("cli")
ignoring_cli.CLI_CONFIG = {
"delegation": {
"max_iterations": 45,
"max_concurrent_children": 4,
}
}
user_config = {"delegation": {"max_concurrent_children": 50}}
with patch.dict("sys.modules", {"cli": ignoring_cli}):
with patch.dict(os.environ, {"HERMES_IGNORE_USER_CONFIG": "1"}):
with patch(
"hermes_cli.config.load_config_readonly",
return_value=user_config,
) as mock_loader:
self.assertEqual(_load_config()["max_concurrent_children"], 4)
mock_loader.assert_not_called()
@patch("tools.delegate_tool._load_config", return_value={})
def test_default_is_three(self, mock_cfg):
# Clear env var if set

View file

@ -3105,16 +3105,28 @@ def _load_config() -> dict:
points that cannot import the shared loader; importing it first can return
an old default ``delegation`` block and hide user-set keys such as
``max_concurrent_children``.
"""
try:
from hermes_cli.config import load_config
full = load_config()
cfg = full.get("delegation") or {}
if isinstance(cfg, dict):
return cfg
except Exception:
pass
Uses ``load_config_readonly()``: every consumer of this dict is read-only
(``.get()`` lookups), and this runs on each ``get_definitions()`` schema
rebuild via ``_get_max_concurrent_children``, so skipping the defensive
deepcopy matters. Do NOT mutate the returned dict.
``HERMES_IGNORE_USER_CONFIG=1`` (``hermes chat --ignore-user-config``) is
only honored by the legacy ``cli`` loader, not the shared one, so when the
flag is set we keep ``cli.CLI_CONFIG`` authoritative to preserve the
flag's contract of suppressing user config.yaml settings.
"""
prefer_legacy = os.environ.get("HERMES_IGNORE_USER_CONFIG") == "1"
if not prefer_legacy:
try:
from hermes_cli.config import load_config_readonly
full = load_config_readonly()
cfg = full.get("delegation") or {}
if isinstance(cfg, dict):
return cfg
except Exception:
pass
try:
from cli import CLI_CONFIG