diff --git a/plugins/memory/honcho/client.py b/plugins/memory/honcho/client.py index 770b8b95cc..a13b89c25b 100644 --- a/plugins/memory/honcho/client.py +++ b/plugins/memory/honcho/client.py @@ -142,6 +142,15 @@ def _parse_dialectic_depth_levels(host_val, root_val, depth: int) -> list[str] | return None +# Default HTTP timeout (seconds) applied when no explicit timeout is +# configured via HonchoClientConfig.timeout, honcho.timeout / requestTimeout, +# or HONCHO_TIMEOUT. Honcho calls happen on the post-response path of +# run_conversation; without a cap the agent can block indefinitely when +# the Honcho backend is unreachable, preventing the gateway from +# delivering the already-generated response. +_DEFAULT_HTTP_TIMEOUT = 30.0 + + def _resolve_optional_float(*values: Any) -> float | None: """Return the first non-empty value coerced to a positive float.""" for value in values: @@ -697,6 +706,11 @@ def get_honcho_client(config: HonchoClientConfig | None = None) -> Honcho: except Exception: pass + # Fall back to the default so an unconfigured install cannot hang + # indefinitely on a stalled Honcho request. + if resolved_timeout is None: + resolved_timeout = _DEFAULT_HTTP_TIMEOUT + if resolved_base_url: logger.info("Initializing Honcho client (base_url: %s, workspace: %s)", resolved_base_url, config.workspace_id) else: diff --git a/tests/honcho_plugin/test_client.py b/tests/honcho_plugin/test_client.py index 8b05ab199e..95180b2dce 100644 --- a/tests/honcho_plugin/test_client.py +++ b/tests/honcho_plugin/test_client.py @@ -600,6 +600,28 @@ class TestGetHonchoClient: mock_honcho.assert_called_once() assert mock_honcho.call_args.kwargs["timeout"] == 88.0 + @pytest.mark.skipif( + not importlib.util.find_spec("honcho"), + reason="honcho SDK not installed" + ) + def test_defaults_to_30s_when_no_timeout_configured(self): + from plugins.memory.honcho.client import _DEFAULT_HTTP_TIMEOUT + + fake_honcho = MagicMock(name="Honcho") + cfg = HonchoClientConfig( + api_key="test-key", + workspace_id="hermes", + environment="production", + ) + + with patch("honcho.Honcho", return_value=fake_honcho) as mock_honcho, \ + patch("hermes_cli.config.load_config", return_value={}): + client = get_honcho_client(cfg) + + assert client is fake_honcho + mock_honcho.assert_called_once() + assert mock_honcho.call_args.kwargs["timeout"] == _DEFAULT_HTTP_TIMEOUT + @pytest.mark.skipif( not importlib.util.find_spec("honcho"), reason="honcho SDK not installed"