Merge pull request #18029 from NousResearch/bb/tui-max-iterations-salvage

fix(tui): respect max turns config
This commit is contained in:
brooklyn! 2026-04-30 10:28:58 -07:00 committed by GitHub
commit 29bcd2f6e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 141 additions and 3 deletions

View file

@ -3911,3 +3911,134 @@ def test_reload_env_rpc_surfaces_errors(monkeypatch):
assert "error" in resp
assert "env path locked" in resp["error"]["message"]
# ── max_iterations config reading ─────────────────────────────────────
def _setup_make_agent_mocks(monkeypatch, cfg):
monkeypatch.setattr(server, "_load_cfg", lambda: cfg)
monkeypatch.setattr(server, "_resolve_startup_runtime", lambda: ("test-model", None))
monkeypatch.setattr(
"hermes_cli.runtime_provider.resolve_runtime_provider",
lambda requested=None, target_model=None: {
"provider": None,
"base_url": None,
"api_key": None,
"api_mode": None,
"command": None,
"args": None,
"credential_pool": None,
},
)
monkeypatch.setattr(server, "_load_tool_progress_mode", lambda: "off")
monkeypatch.setattr(server, "_load_reasoning_config", lambda: None)
monkeypatch.setattr(server, "_load_service_tier", lambda: None)
monkeypatch.setattr(server, "_load_enabled_toolsets", lambda: None)
monkeypatch.setattr(server, "_get_db", lambda: None)
monkeypatch.setattr(server, "_agent_cbs", lambda sid: {})
def test_make_agent_reads_nested_max_turns(monkeypatch):
_setup_make_agent_mocks(monkeypatch, {"agent": {"max_turns": 200}})
with patch("run_agent.AIAgent") as mock_agent:
server._make_agent("sid1", "key1")
assert mock_agent.call_args.kwargs["max_iterations"] == 200
def test_make_agent_nested_max_turns_takes_priority(monkeypatch):
_setup_make_agent_mocks(monkeypatch, {"agent": {"max_turns": 500}, "max_turns": 100})
with patch("run_agent.AIAgent") as mock_agent:
server._make_agent("sid1", "key1")
assert mock_agent.call_args.kwargs["max_iterations"] == 500
def test_make_agent_defaults_to_90(monkeypatch):
_setup_make_agent_mocks(monkeypatch, {})
with patch("run_agent.AIAgent") as mock_agent:
server._make_agent("sid1", "key1")
assert mock_agent.call_args.kwargs["max_iterations"] == 90
def test_make_agent_handles_null_agent_config(monkeypatch):
_setup_make_agent_mocks(monkeypatch, {"agent": None, "max_turns": 80})
with patch("run_agent.AIAgent") as mock_agent:
server._make_agent("sid1", "key1")
assert mock_agent.call_args.kwargs["max_iterations"] == 80
class _FakeAgentForBackground:
base_url = None
api_key = None
provider = None
api_mode = None
acp_command = None
acp_args = None
model = "test-model"
enabled_toolsets = None
ephemeral_system_prompt = None
providers_allowed = None
providers_ignored = None
providers_order = None
provider_sort = None
provider_require_parameters = False
provider_data_collection = None
reasoning_config = None
service_tier = None
request_overrides = {}
_fallback_model = None
def test_background_agent_kwargs_reads_nested_max_turns(monkeypatch):
monkeypatch.setattr(server, "_load_cfg", lambda: {"agent": {"max_turns": 300}})
kwargs = server._background_agent_kwargs(_FakeAgentForBackground(), "task_1")
assert kwargs["max_iterations"] == 300
def test_background_agent_kwargs_falls_back_to_root_max_turns(monkeypatch):
monkeypatch.setattr(server, "_load_cfg", lambda: {"max_turns": 50})
kwargs = server._background_agent_kwargs(_FakeAgentForBackground(), "task_1")
assert kwargs["max_iterations"] == 50
def test_background_agent_kwargs_defaults_to_25(monkeypatch):
monkeypatch.setattr(server, "_load_cfg", lambda: {})
kwargs = server._background_agent_kwargs(_FakeAgentForBackground(), "task_1")
assert kwargs["max_iterations"] == 25
def test_background_agent_kwargs_handles_null_agent_config(monkeypatch):
monkeypatch.setattr(server, "_load_cfg", lambda: {"agent": None, "max_turns": 40})
kwargs = server._background_agent_kwargs(_FakeAgentForBackground(), "task_1")
assert kwargs["max_iterations"] == 40
def test_config_show_displays_nested_max_turns(monkeypatch):
monkeypatch.setattr(
server,
"_load_cfg",
lambda: {"agent": {"max_turns": 120}, "enabled_toolsets": [], "verbose": False},
)
monkeypatch.setattr(server, "_resolve_model", lambda: "test-model")
resp = server.handle_request({"id": "1", "method": "config.show", "params": {}})
sections = resp["result"]["sections"]
agent_rows = next(section["rows"] for section in sections if section["title"] == "Agent")
assert ["Max Turns", "120"] in agent_rows

View file

@ -1670,6 +1670,11 @@ def _apply_personality_to_session(
return False, None
def _cfg_max_turns(cfg: dict, default: int) -> int:
agent_cfg = cfg.get("agent") or {}
return int(agent_cfg.get("max_turns") or cfg.get("max_turns") or default)
def _background_agent_kwargs(agent, task_id: str) -> dict:
cfg = _load_cfg()
@ -1681,7 +1686,7 @@ def _background_agent_kwargs(agent, task_id: str) -> dict:
"acp_command": getattr(agent, "acp_command", None) or None,
"acp_args": getattr(agent, "acp_args", None) or None,
"model": getattr(agent, "model", None) or _resolve_model(),
"max_iterations": int(cfg.get("max_turns", 25) or 25),
"max_iterations": _cfg_max_turns(cfg, 25),
"enabled_toolsets": getattr(agent, "enabled_toolsets", None)
or _load_enabled_toolsets(),
"quiet_mode": True,
@ -1737,7 +1742,8 @@ def _make_agent(sid: str, key: str, session_id: str | None = None):
from hermes_cli.runtime_provider import resolve_runtime_provider
cfg = _load_cfg()
system_prompt = ((cfg.get("agent") or {}).get("system_prompt", "") or "").strip()
agent_cfg = cfg.get("agent") or {}
system_prompt = (agent_cfg.get("system_prompt", "") or "").strip()
model, requested_provider = _resolve_startup_runtime()
runtime = resolve_runtime_provider(
requested=requested_provider,
@ -1745,6 +1751,7 @@ def _make_agent(sid: str, key: str, session_id: str | None = None):
)
return AIAgent(
model=model,
max_iterations=_cfg_max_turns(cfg, 90),
provider=runtime.get("provider"),
base_url=runtime.get("base_url"),
api_key=runtime.get("api_key"),
@ -5394,7 +5401,7 @@ def _(rid, params: dict) -> dict:
{
"title": "Agent",
"rows": [
["Max Turns", str(cfg.get("max_turns", 25))],
["Max Turns", str(_cfg_max_turns(cfg, 90))],
["Toolsets", ", ".join(cfg.get("enabled_toolsets", [])) or "all"],
["Verbose", str(cfg.get("verbose", False))],
],