Files
hermes-agent/tests/run_agent/test_moa_fanout_cadence.py
T
Teknium 6b81590c55 test: prune low-value tests suite-wide (wave 1) — 46,820 → 28,106 test functions
Systematic prune per AGENTS.md test policy, one pass over every major
test tree (gateway, hermes_cli, tools, agent, run_agent, plugins, cli,
cron, tui_gateway, honcho/openviking, root-level):

- DELETE: source-reading tests (read_text/getsource on prod files),
  change-detector tests (exact catalog counts, model-name snapshots,
  config version literals), mock-echo tests (assert a mock returns what
  it was told), assertion-free/trivial tests, near-duplicate
  parametrizations (boundaries + one representative kept), async/sync
  twin duplicates, cosmetic within-file variations.
- KEEP (mandatory): security/redaction/approval guards, message-role
  alternation invariants, prompt-caching/deterministic-call-id
  invariants, issue-number regression tests (deduped), E2E tests.
- 6 test files deleted outright (script-style/no-assert or fully
  redundant); conftest.py, fakes/, fixtures/ untouched.
- tests/acp/conftest.py added: autouse fixture stubs the live
  models.dev/GitHub/Copilot/Anthropic inventory fetches that ACP server
  tests performed on every session create — test_server.py 147s → 3.4s,
  and the tests are now genuinely hermetic.
- Sleep-based slowness shrunk where safe (codex_ttfb_watchdog,
  compression_concurrent_fork, etc.); no wall-clock assertion tightened.

Verification: full hermetic suite via scripts/run_tests.sh —
2439 files, 31,130 tests passed, 0 failed, 0 flaky retries, 315s wall
(baseline: 583s wall, 13,564s subprocess CPU).
2026-07-29 13:10:23 -07:00

146 lines
4.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""every_n fanout cadence: advisors refresh every Nth tool iteration and
off-cadence iterations reuse the cached guidance from the last on-cadence run.
Redesigned from PR #63448's intent (issue #63393 — advisor fan-out multiplies
turn latency/cost by the tool-iteration count). Unlike the submitted shape
(which dropped references entirely on off-cadence iterations), off-cadence
iterations here still feed the aggregator the LAST advisor guidance via the
same cache-reuse mechanism the user_turn fanout uses.
"""
from types import SimpleNamespace
def _response(content="done", *, tool_calls=None):
message = SimpleNamespace(content=content, tool_calls=tool_calls or [])
choice = SimpleNamespace(message=message, finish_reason="stop")
return SimpleNamespace(choices=[choice], usage=None, model="fake-model")
def _cadence_config(home, fanout="every_n:3"):
home.mkdir()
(home / "config.yaml").write_text(
f"""
moa:
default_preset: review
presets:
review:
fanout: "{fanout}"
reference_models:
- provider: openai-codex
model: gpt-5.5
aggregator:
provider: openrouter
model: anthropic/claude-opus-4.8
""".strip(),
encoding="utf-8",
)
def _install_fake_llm(monkeypatch, ref_runs):
def fake_call_llm(**kwargs):
if kwargs["task"] == "moa_reference":
ref_runs.append(kwargs["model"])
return _response(f"advice #{len(ref_runs)}")
return _response("acted")
monkeypatch.setattr("agent.moa_loop.call_llm", fake_call_llm)
def _iteration_messages(base, iterations):
"""Yield message lists simulating a growing tool loop: the base user turn,
then one new (assistant tool_call, tool result) pair per iteration."""
msgs = list(base)
yield list(msgs)
for i in range(1, iterations):
msgs = msgs + [
{
"role": "assistant",
"content": "",
"tool_calls": [
{"id": f"c{i}", "function": {"name": "f", "arguments": "{}"}}
],
},
{"role": "tool", "tool_call_id": f"c{i}", "content": f"result {i}"},
]
yield list(msgs)
def test_every_n_cadence_runs_references_every_nth_iteration(monkeypatch, tmp_path):
"""With every_n:3, references run on iterations 1 and 4 of a 6-iteration
tool loop (1 on-cadence, then every 3rd), not on all 6."""
home = tmp_path / ".hermes"
_cadence_config(home, "every_n:3")
monkeypatch.setenv("HERMES_HOME", str(home))
ref_runs = []
_install_fake_llm(monkeypatch, ref_runs)
from agent.moa_loop import MoAChatCompletions
events = []
facade = MoAChatCompletions("review", reference_callback=lambda ev, **kw: events.append(ev))
base = [{"role": "user", "content": "do the thing"}]
for msgs in _iteration_messages(base, 6):
facade.create(messages=msgs, tools=[{"type": "function"}])
# 1 reference model × iterations {1, 4} on-cadence = 2 advisor runs.
assert len(ref_runs) == 2
# Display blocks only surface when references actually ran.
assert events.count("moa.reference") == 2
assert events.count("moa.aggregating") == 2
def test_every_n_off_cadence_iterations_reuse_cached_guidance(monkeypatch, tmp_path):
"""Off-cadence iterations must still give the aggregator the last
on-cadence advisor guidance (cache reuse), not run advisor-less."""
home = tmp_path / ".hermes"
_cadence_config(home, "every_n:3")
monkeypatch.setenv("HERMES_HOME", str(home))
ref_runs = []
_install_fake_llm(monkeypatch, ref_runs)
from agent.moa_loop import MoAChatCompletions
facade = MoAChatCompletions("review")
base = [{"role": "user", "content": "task"}]
prepared = [
facade.create(messages=msgs, tools=[], _moa_prepare_only=True)
for msgs in _iteration_messages(base, 3)
]
# Iteration 1 ran the references; iterations 2-3 are off-cadence.
assert len(ref_runs) == 1
# Every iteration's aggregator request carries reference guidance...
assert all(p["guidance"] for p in prepared)
# ...and the off-cadence ones reuse iteration 1's exact advice text.
assert "advice #1" in prepared[0]["guidance"]
assert prepared[1]["guidance"] == prepared[0]["guidance"]
assert prepared[2]["guidance"] == prepared[0]["guidance"]
def test_per_iteration_default_unchanged_by_cadence_state(monkeypatch, tmp_path):
"""Default fanout still re-runs references on every state change."""
home = tmp_path / ".hermes"
_cadence_config(home, "per_iteration")
monkeypatch.setenv("HERMES_HOME", str(home))
ref_runs = []
_install_fake_llm(monkeypatch, ref_runs)
from agent.moa_loop import MoAChatCompletions
facade = MoAChatCompletions("review")
base = [{"role": "user", "content": "task"}]
for msgs in _iteration_messages(base, 3):
facade.create(messages=msgs, tools=[])
assert len(ref_runs) == 3