mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
feat(moa): default advisor fanout to user_turn — the cheapest cadence
Flips the default fan-out cadence from per_iteration (advisors re-run on every tool iteration, multiplying advisor spend by tool-loop depth) to user_turn (advisors run once on the first message of each user turn; the acting aggregator works the rest of the tool loop with that turn's advice). Until per-mode benchmarks justify a costlier default, MoA defaults to the cheapest, lowest-impact cadence (#67199). One default for everyone — no split legacy/new-preset semantics; presets that want per-step advising set fanout: per_iteration explicitly. All three modes (user_turn / per_iteration / every_n:N) remain selectable; every_n:1 still collapses to per_iteration (semantic identity), while unparseable values now fall to user_turn (the default). Docs updated with a default-change note; the per-iteration rerun test pins its mode explicitly. Co-authored-by: skyer-flyyy <188930297+skyer-flyyy@users.noreply.github.com>
This commit is contained in:
parent
b0ef72a8a0
commit
23476207bc
7 changed files with 84 additions and 56 deletions
|
|
@ -672,8 +672,14 @@ def test_slot_max_tokens_absent_by_default():
|
|||
# --- fanout cadence normalization (every_n) ---
|
||||
|
||||
|
||||
def test_fanout_defaults_to_per_iteration():
|
||||
def test_fanout_defaults_to_user_turn():
|
||||
# Default is the cheapest cadence (#67199): advisors once per user turn.
|
||||
cfg = normalize_moa_config({})
|
||||
assert cfg["fanout"] == "user_turn"
|
||||
|
||||
|
||||
def test_fanout_per_iteration_still_selectable():
|
||||
cfg = normalize_moa_config({"fanout": "per_iteration"})
|
||||
assert cfg["fanout"] == "per_iteration"
|
||||
|
||||
|
||||
|
|
@ -689,14 +695,15 @@ def test_fanout_every_n_mapping_form_normalized_to_string():
|
|||
|
||||
|
||||
def test_fanout_every_n_degenerate_n_falls_back():
|
||||
# n=1 means "every iteration" — that IS per_iteration; n=0 / negative /
|
||||
# garbage must never produce a broken cadence string.
|
||||
# n=1 means "every iteration" — that semantically IS per_iteration;
|
||||
# n=0 / negative / garbage is unparseable and falls to the default
|
||||
# cadence (user_turn, the cheapest — #67199).
|
||||
assert normalize_moa_config({"fanout": "every_n:1"})["fanout"] == "per_iteration"
|
||||
assert normalize_moa_config({"fanout": "every_n:0"})["fanout"] == "per_iteration"
|
||||
assert normalize_moa_config({"fanout": "every_n:-2"})["fanout"] == "per_iteration"
|
||||
assert normalize_moa_config({"fanout": "every_n:x"})["fanout"] == "per_iteration"
|
||||
assert normalize_moa_config({"fanout": "every_n"})["fanout"] == "per_iteration"
|
||||
assert normalize_moa_config({"fanout": {"mode": "every_n"}})["fanout"] == "per_iteration"
|
||||
assert normalize_moa_config({"fanout": "every_n:0"})["fanout"] == "user_turn"
|
||||
assert normalize_moa_config({"fanout": "every_n:-2"})["fanout"] == "user_turn"
|
||||
assert normalize_moa_config({"fanout": "every_n:x"})["fanout"] == "user_turn"
|
||||
assert normalize_moa_config({"fanout": "every_n"})["fanout"] == "user_turn"
|
||||
assert normalize_moa_config({"fanout": {"mode": "every_n"}})["fanout"] == "user_turn"
|
||||
|
||||
|
||||
def test_fanout_every_n_round_trips_through_normalize():
|
||||
|
|
|
|||
|
|
@ -1177,10 +1177,11 @@ def test_references_parallel_interrupt_aborts_wait(monkeypatch):
|
|||
release_wedged.set() # don't leak a blocked thread past the test
|
||||
|
||||
|
||||
def _ref_config(home):
|
||||
def _ref_config(home, fanout: str | None = None):
|
||||
home.mkdir()
|
||||
fanout_line = f"\n fanout: {fanout}" if fanout else ""
|
||||
(home / "config.yaml").write_text(
|
||||
"""
|
||||
f"""
|
||||
moa:
|
||||
default_preset: review
|
||||
presets:
|
||||
|
|
@ -1192,7 +1193,7 @@ moa:
|
|||
model: anthropic/claude-opus-4.8
|
||||
aggregator:
|
||||
provider: openrouter
|
||||
model: anthropic/claude-opus-4.8
|
||||
model: anthropic/claude-opus-4.8{fanout_line}
|
||||
""".strip(),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
|
@ -1234,13 +1235,15 @@ def test_moa_facade_emits_reference_then_aggregating(monkeypatch, tmp_path):
|
|||
def test_moa_facade_reruns_references_on_new_tool_result(monkeypatch, tmp_path):
|
||||
"""References re-run when a new tool result advances the task state.
|
||||
|
||||
The agent loop calls create() once per tool-loop iteration. References must
|
||||
judge the LATEST state, so a new tool result is a cache MISS and re-runs the
|
||||
references — but a redundant create() call with the SAME state is a cache
|
||||
HIT (no re-run, no re-emit), so we don't fire on a pure no-op re-call.
|
||||
Pins fanout: per_iteration explicitly (the default became user_turn,
|
||||
#67199). In this mode the agent loop calls create() once per tool-loop
|
||||
iteration and references must judge the LATEST state, so a new tool
|
||||
result is a cache MISS and re-runs the references — but a redundant
|
||||
create() call with the SAME state is a cache HIT (no re-run, no
|
||||
re-emit), so we don't fire on a pure no-op re-call.
|
||||
"""
|
||||
home = tmp_path / ".hermes"
|
||||
_ref_config(home)
|
||||
_ref_config(home, fanout="per_iteration")
|
||||
monkeypatch.setenv("HERMES_HOME", str(home))
|
||||
|
||||
ref_runs = []
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue