diff --git a/agent/moa_loop.py b/agent/moa_loop.py index f302fe3813..42d0c29bca 100644 --- a/agent/moa_loop.py +++ b/agent/moa_loop.py @@ -2230,8 +2230,24 @@ def build_moa_facade(agent, preset_name: Any = None) -> MoAClient: except Exception: pass + resolved_preset = preset_name + if resolved_preset is None and getattr(agent, "provider", None) == "moa": + resolved_preset = getattr(agent, "model", None) + + resolved_preset = str(resolved_preset or "default") + try: + from hermes_cli.config import load_config + from hermes_cli.moa_config import normalize_moa_config + + moa_cfg = normalize_moa_config(load_config().get("moa") or {}) + presets = moa_cfg.get("presets") or {} + if resolved_preset not in presets: + resolved_preset = moa_cfg.get("default_preset") or "default" + except Exception: + resolved_preset = "default" + return MoAClient( - str(preset_name or getattr(agent, "model", None) or "default"), + resolved_preset, reference_callback=_moa_reference_relay, # Thread the agent through so the reference fan-out wait can be # aborted on a user interrupt (see _run_references_parallel). diff --git a/tests/run_agent/test_moa_streaming.py b/tests/run_agent/test_moa_streaming.py index f40822e2b3..edacc5c6ac 100644 --- a/tests/run_agent/test_moa_streaming.py +++ b/tests/run_agent/test_moa_streaming.py @@ -90,6 +90,29 @@ def test_create_streams_aggregator_when_requested(monkeypatch, tmp_path): assert agg["tools"] is not None +def test_build_moa_facade_ignores_fallback_model_name_when_restoring(monkeypatch, tmp_path): + """A fallback restore must not turn the temporary fallback model name into + a MoA preset. Sessions that drifted to e.g. deepseek-v4-flash previously + crashed on restore with MoAPresetNotFoundError because build_moa_facade() + reused agent.model as the preset (#MoA restore path). + """ + home = tmp_path / ".hermes" + _write_cfg(home) + monkeypatch.setenv("HERMES_HOME", str(home)) + + from agent.moa_loop import build_moa_facade + + agent = SimpleNamespace( + provider="moa", + model="deepseek-v4-flash", + tool_progress_callback=None, + ) + + client = build_moa_facade(agent, None) + + assert client.chat.completions.preset_name == "review" + + def test_create_wraps_completed_aggregator_response_as_delta_chunk(monkeypatch, tmp_path): """When an aggregator adapter returns a completed response despite stream=True (Codex Responses compatibility shape), MoA must return a