diff --git a/agent/prompt_builder.py b/agent/prompt_builder.py index 0f9822804..7ba2edfa1 100644 --- a/agent/prompt_builder.py +++ b/agent/prompt_builder.py @@ -848,6 +848,27 @@ def build_environment_hints() -> str: if is_wsl(): hints.append(WSL_ENVIRONMENT_HINT) + + # Embedder-supplied environment description. Lets a host that wraps Hermes + # (e.g. a sandbox runner / managed platform) explain the environment the + # agent is running in — proxy, credential handling, mount layout — without + # forking the identity slot (SOUL.md). Read once at prompt-build time, so + # it's part of the stable, cache-safe system prompt. The env var is the + # build-time/embedder mechanism (set in a container ENV); config.yaml + # ``agent.environment_hint`` is the user-facing surface. Env var wins. + extra = (os.getenv("HERMES_ENVIRONMENT_HINT") or "").strip() + if not extra: + try: + from hermes_cli.config import load_config + + extra = str( + (load_config().get("agent", {}) or {}).get("environment_hint", "") + ).strip() + except Exception as e: + logger.debug("Could not read agent.environment_hint from config: %s", e) + if extra: + hints.append(extra) + return "\n\n".join(hints) diff --git a/hermes_cli/config.py b/hermes_cli/config.py index 690e00d9f..e2c59a694 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -683,6 +683,13 @@ DEFAULT_CONFIG = { # (docker/modal/ssh — they have their own probe). Set False to # disable entirely. "environment_probe": True, + # Embedder-supplied environment description appended to the system + # prompt's environment-hints block. Lets a host that wraps Hermes + # (sandbox runner, managed platform) explain the runtime environment + # — proxy, credential handling, mount layout — without editing the + # identity slot (SOUL.md). Empty by default. The HERMES_ENVIRONMENT_HINT + # env var overrides this (build-time/container mechanism). + "environment_hint": "", # Staged inactivity warning: send a warning to the user at this # threshold before escalating to a full timeout. The warning fires # once per run and does not interrupt the agent. 0 = disable warning. diff --git a/tests/agent/test_prompt_builder.py b/tests/agent/test_prompt_builder.py index e0370c309..f309c84e2 100644 --- a/tests/agent/test_prompt_builder.py +++ b/tests/agent/test_prompt_builder.py @@ -947,6 +947,58 @@ class TestEnvironmentHints: f"info is suppressed in the system prompt" ) + def test_environment_hint_from_env_var_is_appended(self, monkeypatch): + """HERMES_ENVIRONMENT_HINT lets an embedder describe the runtime env.""" + import agent.prompt_builder as _pb + monkeypatch.setattr(_pb, "is_wsl", lambda: False) + monkeypatch.delenv("TERMINAL_ENV", raising=False) + monkeypatch.setenv("HERMES_ENVIRONMENT_HINT", "Running inside an OpenShell sandbox.") + _pb._clear_backend_probe_cache() + result = _pb.build_environment_hints() + assert "Running inside an OpenShell sandbox." in result + # The factual host block must still come first. + assert result.index("Host:") < result.index("OpenShell") + + def test_environment_hint_env_var_overrides_config(self, monkeypatch): + """Env var wins over config.yaml agent.environment_hint.""" + import agent.prompt_builder as _pb + monkeypatch.setattr(_pb, "is_wsl", lambda: False) + monkeypatch.delenv("TERMINAL_ENV", raising=False) + monkeypatch.setenv("HERMES_ENVIRONMENT_HINT", "ENV-WINS") + monkeypatch.setattr( + "hermes_cli.config.load_config", + lambda: {"agent": {"environment_hint": "CONFIG-VALUE"}}, + ) + _pb._clear_backend_probe_cache() + result = _pb.build_environment_hints() + assert "ENV-WINS" in result + assert "CONFIG-VALUE" not in result + + def test_environment_hint_falls_back_to_config(self, monkeypatch): + """With no env var, the config.yaml value is used.""" + import agent.prompt_builder as _pb + monkeypatch.setattr(_pb, "is_wsl", lambda: False) + monkeypatch.delenv("TERMINAL_ENV", raising=False) + monkeypatch.delenv("HERMES_ENVIRONMENT_HINT", raising=False) + monkeypatch.setattr( + "hermes_cli.config.load_config", + lambda: {"agent": {"environment_hint": "CONFIG-VALUE"}}, + ) + _pb._clear_backend_probe_cache() + result = _pb.build_environment_hints() + assert "CONFIG-VALUE" in result + + def test_environment_hint_empty_by_default(self, monkeypatch): + """No hint configured anywhere → no embedder text, host block intact.""" + import agent.prompt_builder as _pb + monkeypatch.setattr(_pb, "is_wsl", lambda: False) + monkeypatch.delenv("TERMINAL_ENV", raising=False) + monkeypatch.delenv("HERMES_ENVIRONMENT_HINT", raising=False) + monkeypatch.setattr("hermes_cli.config.load_config", lambda: {"agent": {}}) + _pb._clear_backend_probe_cache() + result = _pb.build_environment_hints() + assert "Host:" in result + # ========================================================================= # Conditional skill activation