Skip to content

fix(moa): add tool_call_batch_cadence for periodic advisor refresh (#63393) - #63448

Closed
webtecnica wants to merge 1 commit into
NousResearch:mainfrom
webtecnica:fix/63393-moa-cadence
Closed

fix(moa): add tool_call_batch_cadence for periodic advisor refresh (#63393)#63448
webtecnica wants to merge 1 commit into
NousResearch:mainfrom
webtecnica:fix/63393-moa-cadence

Conversation

@webtecnica

Copy link
Copy Markdown
Contributor

Summary

Adds tool_call_batch_cadence — a new per-preset setting that controls how many tool-call iterations pass between reference advisor refreshes. Default 0 = run every iteration (current behavior, fully backward compatible).

Changes

hermes_cli/moa_config.py

  • _default_preset(): add "tool_call_batch_cadence": 0
  • _normalize_preset(): coerce via existing _coerce_int(raw.get("tool_call_batch_cadence"), 0)
  • normalize_moa_config(): flatten into active view

agent/moa_loop.py

  • MoAChatCompletions.__init__: add _tool_call_batch_count counter
  • create(): increment counter on each call; when cadence > 0 and the counter doesn't align, skip the reference fan-out entirely — the aggregator runs alone with its own context

How it works

presets:
  default:
    tool_call_batch_cadence: 3   # run advisors every 3rd tool iteration

With cadence: 3:

  • Iterations 1, 4, 7, ... → advisors refreshed, aggregator gets fresh guidance
  • Iterations 2, 3, 5, 6, ... → no advisor re-run, aggregator acts alone

Values:

  • 0 (default): every iteration — no behavioral change
  • 1: every iteration (same as 0)
  • N > 1: run every Nth iteration

Testing

All 76 existing MoA tests pass:

22 passed (test_moa_config.py)
39 passed (test_moa_loop_mode.py, test_moa_switch_api_mode.py, test_moa_aggregator_*)
15 passed (test_moa_streaming.py, test_moa_reference_emit.py, test_moa_trace_streamed_capture.py)

Closes #63393

Adds a new preset-level setting  that controls
how many tool-call iterations pass between reference advisor refreshes.

- : add field with _coerce_int (default 0)
- : add counter + skip logic in create()

tool_call_batch_cadence: 0 (default) = run advisors every iteration
(current behavior unchanged). Values N > 0 thin the reference fan-out to
run only every Nth tool iteration, reducing cost and latency for rapid
tool chains by letting the aggregator act alone on intermediate steps.

Closes NousResearch#63393

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing the real latency gap between per_iteration and user_turn.

Problems

  • The off-cadence branch clears reference_models. On current main, the reference-model labels are part of the cache key (agent/moa_loop.py:986), so this misses the prior cache and yields empty outputs; guidance is attached only for nonempty outputs (agent/moa_loop.py:1062-1077). The aggregator therefore loses prior advisor guidance rather than reusing it, contrary to #63393's requested behavior.
  • The setting is not preserved by Dashboard saves: MoaPresetPayload and the persistence reconstruction omit it (hermes_cli/web_server.py:1001-1009, hermes_cli/web_server.py:5713-5720), and the Dashboard response type omits it too (web/src/lib/api.ts:2306-2323).
  • The two-file diff adds no regression coverage for cadence timing, prior-guidance reuse, or configuration round trips.

Suggested changes

  • Build the cadence atop the existing reference cache so skipped iterations reuse cached outputs but do not rerun or re-account advisors.
  • Wire the setting through Dashboard payload/types/editor and document its interaction with fanout.
  • Add runtime and config/API tests, including a parallel tool-call batch.

Automated hermes-sweeper review.

Comment thread agent/moa_loop.py
tool_call_batch_cadence = preset.get("tool_call_batch_cadence", 0)
self._tool_call_batch_count += 1
if tool_call_batch_cadence > 0 and (self._tool_call_batch_count - 1) % tool_call_batch_cadence != 0:
reference_models = []

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clearing reference_models also changes the current cache key, whose third element is the reference-model labels (agent/moa_loop.py:986 on main). That prevents reuse of the prior advisor outputs and leaves the aggregator with no reference guidance on skipped iterations. Preserve cached outputs while suppressing only the new advisor fan-out.

@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard area/config Config system, migrations, profiles sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Jul 15, 2026
@teknium1 teknium1 added the sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users label Jul 16, 2026
teknium1 added a commit that referenced this pull request Jul 24, 2026
Extends the fanout enum with 'every_n:<N>' (N >= 2): advisors run on the
first iteration of each user turn and every Nth tool iteration after it;
off-cadence iterations REUSE the cached guidance from the last on-cadence
run via the same cache mechanism the user_turn fanout uses, so the
aggregator still gets advice on every step. The cadence counter is scoped
per user turn (resets on a new user message) and only advances when the
advisory state actually changes, so streaming retries never consume a
cadence slot. Mapping form {mode: every_n, n: N} normalizes to the
canonical string. Unknown/degenerate values fall back to per_iteration.

Addresses issue #63393 (advisor fan-out multiplies turn latency/cost by
the tool-iteration count). Redesigned from PR #63448: the submitted shape
skipped references entirely on off-cadence iterations (aggregator ran
advice-less); this version keeps the last advice in play, credited for
the idea and cadence framing.

Config-gated, default-off (default fanout remains per_iteration).

Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com>
teknium1 added a commit that referenced this pull request Jul 24, 2026
Extends the fanout enum with 'every_n:<N>' (N >= 2): advisors run on the
first iteration of each user turn and every Nth tool iteration after it;
off-cadence iterations REUSE the cached guidance from the last on-cadence
run via the same cache mechanism the user_turn fanout uses, so the
aggregator still gets advice on every step. The cadence counter is scoped
per user turn (resets on a new user message) and only advances when the
advisory state actually changes, so streaming retries never consume a
cadence slot. Mapping form {mode: every_n, n: N} normalizes to the
canonical string. Unknown/degenerate values fall back to per_iteration.

Addresses issue #63393 (advisor fan-out multiplies turn latency/cost by
the tool-iteration count). Redesigned from PR #63448: the submitted shape
skipped references entirely on off-cadence iterations (aggregator ran
advice-less); this version keeps the last advice in play, credited for
the idea and cadence framing.

Config-gated, default-off (default fanout remains per_iteration).

Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com>
@teknium1

Copy link
Copy Markdown
Contributor

Closing with credit — the cadence feature shipped via cluster PR #70284 (commit f7b90e6) as a redesigned implementation with you credited (Co-authored-by). The redesign folds the cadence into the existing fanout enum (fanout: every_n:N) and reuses cached advisor guidance on off-cadence iterations instead of leaving the aggregator with no advice, with the counter scoped per user turn. Your issue #63393 (multiplicative advisor latency) was the real problem and is now fixed. Thanks!

@teknium1 teknium1 closed this Jul 24, 2026
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
Extends the fanout enum with 'every_n:<N>' (N >= 2): advisors run on the
first iteration of each user turn and every Nth tool iteration after it;
off-cadence iterations REUSE the cached guidance from the last on-cadence
run via the same cache mechanism the user_turn fanout uses, so the
aggregator still gets advice on every step. The cadence counter is scoped
per user turn (resets on a new user message) and only advances when the
advisory state actually changes, so streaming retries never consume a
cadence slot. Mapping form {mode: every_n, n: N} normalizes to the
canonical string. Unknown/degenerate values fall back to per_iteration.

Addresses issue NousResearch#63393 (advisor fan-out multiplies turn latency/cost by
the tool-iteration count). Redesigned from PR NousResearch#63448: the submitted shape
skipped references entirely on off-cadence iterations (aggregator ran
advice-less); this version keeps the last advice in play, credited for
the idea and cadence framing.

Config-gated, default-off (default fanout remains per_iteration).

Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard P3 Low — cosmetic, nice to have sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MoA: add periodic advisor refresh cadence for tool-heavy turns

3 participants