From 9e398e1809dd30c26ed899e362af6bb04c948894 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Tue, 28 Apr 2026 22:42:17 -0500 Subject: [PATCH] perf(tui): avoid importing classic CLI during tool discovery TUI session readiness was still laggy after the gateway-ready fixes. Profiling session.create -> session.info showed the slow phase is background AIAgent construction (~1.1s). A cProfile run of tui_gateway.server::_make_agent showed model_tools/tool discovery importing tools.code_execution_tool, whose module-level EXECUTE_CODE_SCHEMA calls _get_execution_mode(), which imported cli.CLI_CONFIG. That pulled the classic interactive CLI stack (prompt_toolkit/Rich and REPL setup) into every agent startup path, including hermes --tui where it is not used. Replace that with hermes_cli.config.read_raw_config(), which is cached and reads only the raw code_execution section. Existing defaults still apply when the key is absent. Measurements on macOS Terminal.app: - import run_agent: ~466ms -> ~347ms - model_tools import: ~418ms -> ~272ms - _make_agent: ~1452ms -> ~1239ms - session.create -> session.info: ~1167ms -> ~999ms - full hermes --tui ready p50: ~1655ms -> ~1537ms Tests: - scripts/run_tests.sh tests/tools/test_code_execution_modes.py tests/tools/test_code_execution.py --- tests/tools/test_code_execution.py | 16 ++++++++++++---- tools/code_execution_tool.py | 16 +++++++++++++--- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/tests/tools/test_code_execution.py b/tests/tools/test_code_execution.py index 15f8faa9b..6f6260ffe 100644 --- a/tests/tools/test_code_execution.py +++ b/tests/tools/test_code_execution.py @@ -770,11 +770,19 @@ class TestLoadConfig(unittest.TestCase): def test_returns_code_execution_section(self): from tools.code_execution_tool import _load_config - mock_cli = MagicMock() - mock_cli.CLI_CONFIG = {"code_execution": {"timeout": 120, "max_tool_calls": 10}} - with patch.dict("sys.modules", {"cli": mock_cli}): + with patch("hermes_cli.config.read_raw_config", + return_value={"code_execution": {"timeout": 120, "max_tool_calls": 10}}): result = _load_config() - self.assertIsInstance(result, dict) + self.assertEqual(result, {"timeout": 120, "max_tool_calls": 10}) + + def test_does_not_import_interactive_cli(self): + from tools.code_execution_tool import _load_config + mock_cli = MagicMock() + mock_cli.CLI_CONFIG = {"code_execution": {"timeout": 999}} + with patch.dict("sys.modules", {"cli": mock_cli}), \ + patch("hermes_cli.config.read_raw_config", return_value={}): + result = _load_config() + self.assertEqual(result, {}) # --------------------------------------------------------------------------- diff --git a/tools/code_execution_tool.py b/tools/code_execution_tool.py index db706e6a4..3f83394c1 100644 --- a/tools/code_execution_tool.py +++ b/tools/code_execution_tool.py @@ -1309,10 +1309,20 @@ def _kill_process_group(proc, escalate: bool = False): def _load_config() -> dict: - """Load code_execution config from CLI_CONFIG if available.""" + """Load code_execution config without importing the interactive CLI. + + This helper is called while building the module-level execute_code schema + during tool discovery. Importing ``cli`` here pulls prompt_toolkit/Rich and + a large chunk of the classic REPL onto every agent startup path, including + ``hermes --tui`` where it is never used. Read the lightweight raw config + instead; the config layer already caches by (mtime, size), and an absent + key cleanly falls back to DEFAULT_EXECUTION_MODE. + """ try: - from cli import CLI_CONFIG - return CLI_CONFIG.get("code_execution", {}) + from hermes_cli.config import read_raw_config + + cfg = read_raw_config().get("code_execution", {}) + return cfg if isinstance(cfg, dict) else {} except Exception: return {}