fix(moa): advisory references end on a user turn + get a reference-role system prompt - #54007
Conversation
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.
🔎 Lint report:
|
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.
OutThisLife
left a comment
There was a problem hiding this comment.
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
whilestrip runs before theif 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 siblingtest_run_reference_prepends_advisory_system_promptalready asserts against the_REFERENCE_SYSTEM_PROMPTconstant, which is the robust form; the facade test could do the same. - On the P3 label: defensible since
_run_referencecatches 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.
…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.
…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.
…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.
…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.
…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.
…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.
…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.
Summary
Two related fixes to MoA reference-model calls:
Root cause — prefill 400
_reference_messages()inagent/moa_loop.pybuilds an advisory transcript by keeping user/assistant text turns and dropping the system prompt andtoolmessages. Mid-tool-loop, the last assistant turn carries reasoning text plus tool calls — its text survives the trim while the followingtoolresult 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 with400 ... 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._REFERENCE_SYSTEM_PROMPTprepended 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; addedtest_reference_messages_ends_with_user_not_assistant_prefillandtest_run_reference_prepends_advisory_system_prompt; updated the facade test for the leading system message.Validation
assistant(prefill)user400 must end with a user messageInfographic