fix(moa): add tool_call_batch_cadence for periodic advisor refresh (#63393) - #63448
fix(moa): add tool_call_batch_cadence for periodic advisor refresh (#63393)#63448webtecnica wants to merge 1 commit into
Conversation
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
left a comment
There was a problem hiding this comment.
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:
MoaPresetPayloadand 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.
| 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 = [] |
There was a problem hiding this comment.
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.
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>
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>
|
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! |
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>
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 viewagent/moa_loop.pyMoAChatCompletions.__init__: add_tool_call_batch_countcountercreate(): 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 contextHow it works
With
cadence: 3:Values:
0(default): every iteration — no behavioral change1: every iteration (same as 0)N > 1: run every Nth iterationTesting
All 76 existing MoA tests pass:
Closes #63393