Skip to content

fix(moa): advisory references end on a user turn + get a reference-role system prompt - #54007

Merged
teknium1 merged 2 commits into
mainfrom
hermes/hermes-36fe5816
Jun 28, 2026
Merged

fix(moa): advisory references end on a user turn + get a reference-role system prompt#54007
teknium1 merged 2 commits into
mainfrom
hermes/hermes-36fe5816

Conversation

@teknium1

@teknium1 teknium1 commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Two related fixes to MoA reference-model calls:

  1. Reference calls no longer 400 on Anthropic models that reject assistant prefill. The advisory reference view now always ends with a user turn instead of a trailing assistant turn.
  2. Reference models now know their role. Each reference call is prefixed with an advisory system prompt so the model acts as an analyst advising the aggregator, instead of assuming it is the acting agent and refusing ("I can't access repositories/URLs from here") or trying to call tools it doesn't have.

Root cause — prefill 400

_reference_messages() in agent/moa_loop.py builds an advisory transcript by keeping user/assistant text turns and dropping the system prompt and tool messages. Mid-tool-loop, the last assistant turn carries reasoning text plus tool calls — its text survives the trim while the following tool result is dropped, leaving a trailing assistant turn. Anthropic (and OpenRouter→Anthropic) treat that as an assistant prefill to continue, and no-prefill models (e.g. Claude Opus 4.8) reject it with 400 ... must end with a user message. GPT-5.5 tolerates it, so only the Anthropic reference failed.

Root cause — role confusion

References got the bare trimmed conversation with no framing, so they assumed they were the acting agent being asked to do the task.

Changes

  • agent/moa_loop.py:
    • _reference_messages() strips trailing assistant turns so the advisory view ends on a user turn; intervening assistant context is preserved.
    • New _REFERENCE_SYSTEM_PROMPT prepended in _run_reference() — frames the model as an advisory analyst (no execution, no tools, reason about state, hand guidance to the aggregator).
  • tests/run_agent/test_moa_loop_mode.py: updated the test that encoded the buggy trailing-assistant shape; added test_reference_messages_ends_with_user_not_assistant_prefill and test_run_reference_prepends_advisory_system_prompt; updated the facade test for the leading system message.

Validation

Before After
Reference view ending trailing assistant (prefill) trailing user
Anthropic no-prefill reference (Opus 4.8) 400 must end with a user message succeeds
Reference role framing none (acts as the agent) advisory analyst system prompt
MoA test suite 15/15 green

Infographic

moa-reference-prefill-fix

MoA reference calls failed with Anthropic models that don't support
assistant prefill (e.g. Claude Opus 4.8): '400 ... must end with a user
message'. The advisory view built by _reference_messages() kept the last
assistant turn's text while dropping the following tool result, leaving a
trailing assistant turn — which Anthropic (and OpenRouter->Anthropic)
interpret as an assistant prefill to continue. References are advisory and
must end on the user turn they answer.

Strip trailing assistant turns from the advisory view (preserving
intervening ones). Update the existing test that encoded the buggy shape
and add a mid-tool-loop regression test.
@github-actions

github-actions Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-36fe5816 vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 11611 on HEAD, 11611 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 6094 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

Reference models received the bare trimmed conversation with no role
framing, so they assumed they were the acting agent and refused ("I can't
access repositories/URLs from here") or tried to call tools they don't have.

Prepend a dedicated advisory system prompt to every reference call: the
model is an analyst, not the actor — it cannot execute, should not
apologize for lacking tools, and should reason about the presented state to
advise the aggregator/orchestrator on approach, next steps, tool-use
strategy, risks, and anything the acting agent missed. Its output is private
guidance for the aggregator, not a user-facing answer.
@teknium1 teknium1 changed the title fix(moa): reference advisory view must end with a user turn fix(moa): advisory references end on a user turn + get a reference-role system prompt Jun 28, 2026
@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P3 Low — cosmetic, nice to have labels Jun 28, 2026
@teknium1
teknium1 merged commit 1fa4418 into main Jun 28, 2026
30 checks passed
@teknium1
teknium1 deleted the hermes/hermes-36fe5816 branch June 28, 2026 05:52

@OutThisLife OutThisLife left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed the full diff against current main. Both fixes are correct, the root-cause analysis holds against the actual code, and the diff is minimal and well-scoped — it fixes the whole bug class (both _reference_messages consumers) rather than one call site.

Fix 1 — trailing assistant strip (the 400)

Traced it in agent/moa_loop.py: _reference_messages() keeps assistant text even when it accompanied tool_calls, then drops the following tool result, so mid-tool-loop the advisory view ends on an assistant turn → Anthropic reads it as a prefill and Opus 4.8 rejects. GPT-5.5 tolerating it explains why only the Anthropic reference failed. Accurate.

Two things I specifically checked and they hold:

  • Ordering with the fallback — the while strip runs before the if not trimmed: degenerate fallback, so an assistant-only view that strips to empty still falls back to the latest user turn.
  • Intervening assistants preserved — only trailing assistants pop, so prior-turn Q&A context survives.

Bonus not mentioned in the description: this also stabilizes the per-turn reference cache. Before, each tool-loop iteration appended an assistant turn → new _ref_cache_key signature → references re-ran every iteration (and re-hit the 400). After, the advisory view is constant across a turn, so references run once. Net cost reduction on top of the correctness fix.

Fix 2 — advisory system prompt

Correctly added in _run_reference() at dispatch time, after _reference_messages() builds the cached/signed view — so the constant prompt doesn't perturb the cache key, and it's the only system message the reference sees. New list per call, dicts only read across threads → no aliasing in the fan-out. No prompt-cache concern (auxiliary call_llm calls; a constant prefix is cache-friendly).

Nits (non-blocking)

  • The updated facade test asserts "reference advisor" in ref_msgs[0]["content"].lower() — mild change-detector coupling to prompt wording. The sibling test_run_reference_prepends_advisory_system_prompt already asserts against the _REFERENCE_SYSTEM_PROMPT constant, which is the robust form; the facade test could do the same.
  • On the P3 label: defensible since _run_reference catches the 400 and returns [failed: ...], so it degrades gracefully rather than crashing. But "silently loses a reference's advice on every multi-iteration MoA turn with an Anthropic reference" is arguably closer to P2.

pai-scaffolde pushed a commit to pai-scaffolde/hermes-agent that referenced this pull request Jun 28, 2026
…le system prompt (NousResearch#54007)

* fix(moa): reference advisory view must end with a user turn

MoA reference calls failed with Anthropic models that don't support
assistant prefill (e.g. Claude Opus 4.8): '400 ... must end with a user
message'. The advisory view built by _reference_messages() kept the last
assistant turn's text while dropping the following tool result, leaving a
trailing assistant turn — which Anthropic (and OpenRouter->Anthropic)
interpret as an assistant prefill to continue. References are advisory and
must end on the user turn they answer.

Strip trailing assistant turns from the advisory view (preserving
intervening ones). Update the existing test that encoded the buggy shape
and add a mid-tool-loop regression test.

* feat(moa): give reference models an advisory-role system prompt

Reference models received the bare trimmed conversation with no role
framing, so they assumed they were the acting agent and refused ("I can't
access repositories/URLs from here") or tried to call tools they don't have.

Prepend a dedicated advisory system prompt to every reference call: the
model is an analyst, not the actor — it cannot execute, should not
apologize for lacking tools, and should reason about the presented state to
advise the aggregator/orchestrator on approach, next steps, tool-use
strategy, risks, and anything the acting agent missed. Its output is private
guidance for the aggregator, not a user-facing answer.
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
…le system prompt (NousResearch#54007)

* fix(moa): reference advisory view must end with a user turn

MoA reference calls failed with Anthropic models that don't support
assistant prefill (e.g. Claude Opus 4.8): '400 ... must end with a user
message'. The advisory view built by _reference_messages() kept the last
assistant turn's text while dropping the following tool result, leaving a
trailing assistant turn — which Anthropic (and OpenRouter->Anthropic)
interpret as an assistant prefill to continue. References are advisory and
must end on the user turn they answer.

Strip trailing assistant turns from the advisory view (preserving
intervening ones). Update the existing test that encoded the buggy shape
and add a mid-tool-loop regression test.

* feat(moa): give reference models an advisory-role system prompt

Reference models received the bare trimmed conversation with no role
framing, so they assumed they were the acting agent and refused ("I can't
access repositories/URLs from here") or tried to call tools they don't have.

Prepend a dedicated advisory system prompt to every reference call: the
model is an analyst, not the actor — it cannot execute, should not
apologize for lacking tools, and should reason about the presented state to
advise the aggregator/orchestrator on approach, next steps, tool-use
strategy, risks, and anything the acting agent missed. Its output is private
guidance for the aggregator, not a user-facing answer.
Jasper6439 pushed a commit to Jasper6439/hermes-agent that referenced this pull request Jul 5, 2026
…le system prompt (NousResearch#54007)

* fix(moa): reference advisory view must end with a user turn

MoA reference calls failed with Anthropic models that don't support
assistant prefill (e.g. Claude Opus 4.8): '400 ... must end with a user
message'. The advisory view built by _reference_messages() kept the last
assistant turn's text while dropping the following tool result, leaving a
trailing assistant turn — which Anthropic (and OpenRouter->Anthropic)
interpret as an assistant prefill to continue. References are advisory and
must end on the user turn they answer.

Strip trailing assistant turns from the advisory view (preserving
intervening ones). Update the existing test that encoded the buggy shape
and add a mid-tool-loop regression test.

* feat(moa): give reference models an advisory-role system prompt

Reference models received the bare trimmed conversation with no role
framing, so they assumed they were the acting agent and refused ("I can't
access repositories/URLs from here") or tried to call tools they don't have.

Prepend a dedicated advisory system prompt to every reference call: the
model is an analyst, not the actor — it cannot execute, should not
apologize for lacking tools, and should reason about the presented state to
advise the aggregator/orchestrator on approach, next steps, tool-use
strategy, risks, and anything the acting agent missed. Its output is private
guidance for the aggregator, not a user-facing answer.
habarmc1223-sudo pushed a commit to habarmc1223-sudo/hermes-agent-fluxmem that referenced this pull request Jul 8, 2026
…le system prompt (NousResearch#54007)

* fix(moa): reference advisory view must end with a user turn

MoA reference calls failed with Anthropic models that don't support
assistant prefill (e.g. Claude Opus 4.8): '400 ... must end with a user
message'. The advisory view built by _reference_messages() kept the last
assistant turn's text while dropping the following tool result, leaving a
trailing assistant turn — which Anthropic (and OpenRouter->Anthropic)
interpret as an assistant prefill to continue. References are advisory and
must end on the user turn they answer.

Strip trailing assistant turns from the advisory view (preserving
intervening ones). Update the existing test that encoded the buggy shape
and add a mid-tool-loop regression test.

* feat(moa): give reference models an advisory-role system prompt

Reference models received the bare trimmed conversation with no role
framing, so they assumed they were the acting agent and refused ("I can't
access repositories/URLs from here") or tried to call tools they don't have.

Prepend a dedicated advisory system prompt to every reference call: the
model is an analyst, not the actor — it cannot execute, should not
apologize for lacking tools, and should reason about the presented state to
advise the aggregator/orchestrator on approach, next steps, tool-use
strategy, risks, and anything the acting agent missed. Its output is private
guidance for the aggregator, not a user-facing answer.
santhreal pushed a commit to santhreal/hermes-agent that referenced this pull request Jul 13, 2026
…le system prompt (NousResearch#54007)

* fix(moa): reference advisory view must end with a user turn

MoA reference calls failed with Anthropic models that don't support
assistant prefill (e.g. Claude Opus 4.8): '400 ... must end with a user
message'. The advisory view built by _reference_messages() kept the last
assistant turn's text while dropping the following tool result, leaving a
trailing assistant turn — which Anthropic (and OpenRouter->Anthropic)
interpret as an assistant prefill to continue. References are advisory and
must end on the user turn they answer.

Strip trailing assistant turns from the advisory view (preserving
intervening ones). Update the existing test that encoded the buggy shape
and add a mid-tool-loop regression test.

* feat(moa): give reference models an advisory-role system prompt

Reference models received the bare trimmed conversation with no role
framing, so they assumed they were the acting agent and refused ("I can't
access repositories/URLs from here") or tried to call tools they don't have.

Prepend a dedicated advisory system prompt to every reference call: the
model is an analyst, not the actor — it cannot execute, should not
apologize for lacking tools, and should reason about the presented state to
advise the aggregator/orchestrator on approach, next steps, tool-use
strategy, risks, and anything the acting agent missed. Its output is private
guidance for the aggregator, not a user-facing answer.
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
…le system prompt (NousResearch#54007)

* fix(moa): reference advisory view must end with a user turn

MoA reference calls failed with Anthropic models that don't support
assistant prefill (e.g. Claude Opus 4.8): '400 ... must end with a user
message'. The advisory view built by _reference_messages() kept the last
assistant turn's text while dropping the following tool result, leaving a
trailing assistant turn — which Anthropic (and OpenRouter->Anthropic)
interpret as an assistant prefill to continue. References are advisory and
must end on the user turn they answer.

Strip trailing assistant turns from the advisory view (preserving
intervening ones). Update the existing test that encoded the buggy shape
and add a mid-tool-loop regression test.

* feat(moa): give reference models an advisory-role system prompt

Reference models received the bare trimmed conversation with no role
framing, so they assumed they were the acting agent and refused ("I can't
access repositories/URLs from here") or tried to call tools they don't have.

Prepend a dedicated advisory system prompt to every reference call: the
model is an analyst, not the actor — it cannot execute, should not
apologize for lacking tools, and should reason about the presented state to
advise the aggregator/orchestrator on approach, next steps, tool-use
strategy, risks, and anything the acting agent missed. Its output is private
guidance for the aggregator, not a user-facing answer.
leewenjie pushed a commit to leewenjie/hermes-agent that referenced this pull request Aug 7, 2026
…le system prompt (NousResearch#54007)

* fix(moa): reference advisory view must end with a user turn

MoA reference calls failed with Anthropic models that don't support
assistant prefill (e.g. Claude Opus 4.8): '400 ... must end with a user
message'. The advisory view built by _reference_messages() kept the last
assistant turn's text while dropping the following tool result, leaving a
trailing assistant turn — which Anthropic (and OpenRouter->Anthropic)
interpret as an assistant prefill to continue. References are advisory and
must end on the user turn they answer.

Strip trailing assistant turns from the advisory view (preserving
intervening ones). Update the existing test that encoded the buggy shape
and add a mid-tool-loop regression test.

* feat(moa): give reference models an advisory-role system prompt

Reference models received the bare trimmed conversation with no role
framing, so they assumed they were the acting agent and refused ("I can't
access repositories/URLs from here") or tried to call tools they don't have.

Prepend a dedicated advisory system prompt to every reference call: the
model is an analyst, not the actor — it cannot execute, should not
apologize for lacking tools, and should reason about the presented state to
advise the aggregator/orchestrator on approach, next steps, tool-use
strategy, risks, and anything the acting agent missed. Its output is private
guidance for the aggregator, not a user-facing answer.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P3 Low — cosmetic, nice to have type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants