mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-14 04:02:26 +00:00
fix(gateway): preserve max turns after env reload
This commit is contained in:
parent
2c14d3b9b0
commit
8308d18339
2 changed files with 86 additions and 7 deletions
|
|
@ -299,6 +299,36 @@ _env_path = _hermes_home / '.env'
|
||||||
load_hermes_dotenv(hermes_home=_hermes_home, project_env=Path(__file__).resolve().parents[1] / '.env')
|
load_hermes_dotenv(hermes_home=_hermes_home, project_env=Path(__file__).resolve().parents[1] / '.env')
|
||||||
|
|
||||||
|
|
||||||
|
def _reload_runtime_env_preserving_config_authority() -> None:
|
||||||
|
"""Reload .env for fresh credentials without letting stale .env override config.
|
||||||
|
|
||||||
|
Gateway processes are long-lived, so per-turn code reloads ~/.hermes/.env to
|
||||||
|
pick up rotated API keys. config.yaml remains authoritative for agent budget
|
||||||
|
settings such as agent.max_turns; otherwise a stale HERMES_MAX_ITERATIONS in
|
||||||
|
.env can replace the startup bridge on later turns.
|
||||||
|
"""
|
||||||
|
load_hermes_dotenv(
|
||||||
|
hermes_home=_hermes_home,
|
||||||
|
project_env=Path(__file__).resolve().parents[1] / '.env',
|
||||||
|
)
|
||||||
|
|
||||||
|
config_path = _hermes_home / 'config.yaml'
|
||||||
|
if not config_path.exists():
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
import yaml as _yaml
|
||||||
|
with open(config_path, encoding="utf-8") as f:
|
||||||
|
cfg = _yaml.safe_load(f) or {}
|
||||||
|
from hermes_cli.config import _expand_env_vars
|
||||||
|
cfg = _expand_env_vars(cfg)
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
|
||||||
|
agent_cfg = cfg.get("agent", {})
|
||||||
|
if isinstance(agent_cfg, dict) and "max_turns" in agent_cfg:
|
||||||
|
os.environ["HERMES_MAX_ITERATIONS"] = str(agent_cfg["max_turns"])
|
||||||
|
|
||||||
|
|
||||||
_DOCKER_VOLUME_SPEC_RE = re.compile(r"^(?P<host>.+):(?P<container>/[^:]+?)(?::(?P<options>[^:]+))?$")
|
_DOCKER_VOLUME_SPEC_RE = re.compile(r"^(?P<host>.+):(?P<container>/[^:]+?)(?::(?P<options>[^:]+))?$")
|
||||||
_DOCKER_MEDIA_OUTPUT_CONTAINER_PATHS = {"/output", "/outputs"}
|
_DOCKER_MEDIA_OUTPUT_CONTAINER_PATHS = {"/output", "/outputs"}
|
||||||
|
|
||||||
|
|
@ -13524,13 +13554,9 @@ class GatewayRunner:
|
||||||
combined_ephemeral = (combined_ephemeral + "\n\n" + self._ephemeral_system_prompt).strip()
|
combined_ephemeral = (combined_ephemeral + "\n\n" + self._ephemeral_system_prompt).strip()
|
||||||
|
|
||||||
# Re-read .env and config for fresh credentials (gateway is long-lived,
|
# Re-read .env and config for fresh credentials (gateway is long-lived,
|
||||||
# keys may change without restart).
|
# keys may change without restart). Keep config.yaml authoritative for
|
||||||
try:
|
# runtime budget settings bridged into env vars.
|
||||||
load_dotenv(_env_path, override=True, encoding="utf-8")
|
_reload_runtime_env_preserving_config_authority()
|
||||||
except UnicodeDecodeError:
|
|
||||||
load_dotenv(_env_path, override=True, encoding="latin-1")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
model, runtime_kwargs = self._resolve_session_agent_runtime(
|
model, runtime_kwargs = self._resolve_session_agent_runtime(
|
||||||
|
|
|
||||||
53
tests/gateway/test_runtime_env_reload_config_authority.py
Normal file
53
tests/gateway/test_runtime_env_reload_config_authority.py
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
"""Regression tests for gateway per-turn env reload preserving config authority.
|
||||||
|
|
||||||
|
Issue #19158: startup bridges config.yaml agent.max_turns into
|
||||||
|
HERMES_MAX_ITERATIONS, but a later per-turn load_dotenv(..., override=True)
|
||||||
|
can restore a stale .env HERMES_MAX_ITERATIONS value before the next turn.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from gateway import run as gateway_run
|
||||||
|
|
||||||
|
|
||||||
|
def test_reload_runtime_env_preserves_config_max_turns(tmp_path: Path, monkeypatch) -> None:
|
||||||
|
hermes_home = tmp_path / ".hermes"
|
||||||
|
hermes_home.mkdir()
|
||||||
|
(hermes_home / "config.yaml").write_text(
|
||||||
|
yaml.safe_dump({"agent": {"max_turns": 9000}}),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
(hermes_home / ".env").write_text(
|
||||||
|
"HERMES_MAX_ITERATIONS=90\nOPENROUTER_API_KEY=fresh-key\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
monkeypatch.setattr(gateway_run, "_hermes_home", hermes_home)
|
||||||
|
monkeypatch.setenv("HERMES_MAX_ITERATIONS", "9000")
|
||||||
|
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
|
||||||
|
|
||||||
|
gateway_run._reload_runtime_env_preserving_config_authority()
|
||||||
|
|
||||||
|
assert os.environ["OPENROUTER_API_KEY"] == "fresh-key"
|
||||||
|
assert os.environ["HERMES_MAX_ITERATIONS"] == "9000"
|
||||||
|
|
||||||
|
|
||||||
|
def test_reload_runtime_env_keeps_env_max_iterations_when_config_omits_key(
|
||||||
|
tmp_path: Path, monkeypatch
|
||||||
|
) -> None:
|
||||||
|
hermes_home = tmp_path / ".hermes"
|
||||||
|
hermes_home.mkdir()
|
||||||
|
(hermes_home / "config.yaml").write_text(yaml.safe_dump({"agent": {}}), encoding="utf-8")
|
||||||
|
(hermes_home / ".env").write_text("HERMES_MAX_ITERATIONS=123\n", encoding="utf-8")
|
||||||
|
|
||||||
|
monkeypatch.setattr(gateway_run, "_hermes_home", hermes_home)
|
||||||
|
monkeypatch.delenv("HERMES_MAX_ITERATIONS", raising=False)
|
||||||
|
|
||||||
|
gateway_run._reload_runtime_env_preserving_config_authority()
|
||||||
|
|
||||||
|
assert os.environ["HERMES_MAX_ITERATIONS"] == "123"
|
||||||
Loading…
Add table
Add a link
Reference in a new issue