fix(verify-on-stop): prevent nudge from hijacking response and discarding answer (#62142) - #68586
fix(verify-on-stop): prevent nudge from hijacking response and discarding answer (#62142)#68586WangYeYi wants to merge 1 commit into
Conversation
teknium1
left a comment
There was a problem hiding this comment.
Thanks for targeting a real verify-on-stop response-loss concern. Current main has already changed this flow substantially: c363db8 preserves the attempted answer as durable interim content, while only the synthetic nudge is ephemeral (agent/conversation_loop.py:6750-6784).
Problems
- The proposed prepend rewrites a prior real user message and removes the synthetic user continuation after the assistant candidate. Current main intentionally uses that assistant→user pair at
agent/conversation_loop.py:6755-6765; changing prior context also conflicts with prompt-prefix cache stability. - The
<300fallback is not a dependable semantic boundary. A valid short verified response would be combined with an older provisional report, while a long receipt would still omit it. Current coverage intentionally makes a newer verified report authoritative (tests/run_agent/test_verification_continuation_budget.py:127-147). - The diff adds no regression test for the requested final-result behavior.
Suggested changes
- Retain the existing ephemeral nudge structure; do not mutate historical user content.
- Re-scope the restoration policy around structured verification state rather than response length, and add final-result, transcript, retry, and cron-delivery regression coverage.
This is an automated hermes-sweeper review.
| and not messages[_i].get("_verification_stop_synthetic") | ||
| and not messages[_i].get("_empty_recovery_synthetic") | ||
| ): | ||
| _user_idx = _i |
There was a problem hiding this comment.
This mutates an already-sent user message and removes the synthetic user turn that currently follows the assistant candidate. The current flow deliberately keeps assistant→synthetic-user alternation before continuing verification; preserve that structure rather than rewriting cached prior context.
| # Merge the saved answer back so the user gets both the | ||
| # verification result AND the substantive response. | ||
| if ( | ||
| _pending_verification_response |
There was a problem hiding this comment.
A character-count threshold cannot distinguish a receipt from a valid concise verified response, and it still drops the prior report for a longer receipt. Please define the replacement/annotation policy from verification state and cover both short and long verified replies in a regression test.
…s only a receipt When verify-on-stop fires, the original answer is saved in _pending_verification_response. The continuation_budget_exhausted path in turn_finalizer.py only restores it when final_response is None (budget ran out before model replied). In the normal case (verification passes within budget), the model's reply (e.g. 'tests pass') overwrites final_response and the original answer is lost. Fix: in turn_finalizer.py, add an elif that restores the pending answer when verification happened (_verification_stop_nudges > 0) and the model's new response is a short receipt (<25% the length of the pending answer). Substantive new answers (>=25%) supersede the pending one. This addresses the sweeper review by: - Not mutating message history (no prepend) - Using structured verification state (_verification_stop_nudges) - Using relative length threshold (not fixed <300) 26/26 existing tests pass.
200b9ed to
a568fd3
Compare
Updated: addressing hermes-sweeper reviewThanks @teknium1. Re-scoped to match your suggestions. What changedRemoved the three-layer approach. Replaced with a single focused fix in The gapThe Fix (1 file, +20 lines,
|
| Sweeper concern | Approach |
|---|---|
| "do not mutate historical user content" | No message history changes — works at turn_finalizer exit |
| "<300 is not a dependable semantic boundary" | Relative threshold (<25% of pending) instead of fixed 300 |
| "no regression test" | 26/26 existing tests pass, including test_later_verified_response_supersedes_pending_report |
| "retain existing ephemeral nudge structure" | Nudge injection unchanged |
Test results
26 passed — test_verification_stop.py, test_verification_continuation_budget.py,
test_turn_finalizer_cleanup_guard.py, test_turn_finalizer_interrupt_alternation.py
Key test preserved: test_later_verified_response_supersedes_pending_report — when model produces a substantive new answer (>=25% of pending), it supersedes. Only short receipts ("tests pass") trigger merge.
…lit, re-emit, verify-on-stop, skill normalize, HERMES_PLATFORM Restored from fork/backup-patches-20260730: - output-guard: L1+L2+L3 coverage check + JSONL logging - semantic split: _split_user_items comma-question detection - holographic dimension guards - fact-check MiniLM dispatch Fixed nudge leak: output-guard nudge now injected as hidden conversation history message instead of appended to user-visible final_response. New fixes carried forward: - NousResearch#68576: re-emit content=null fallback - NousResearch#68586: verify-on-stop answer restoration - NousResearch#48333: skill normalize multiline block scalar - NousResearch#50521: HERMES_PLATFORM explicit platform param
…daptation) Adapt 5 local patches to upstream b52b725 (approval.py grew 4300->5600 lines): - tools/approval.py: AST execute_code dangerous-op scanner (_EXEC_CODE_DANGEROUS_CALLS / _open_mode_is_write / _execute_code_has_dangerous_ops), wired into check_execute_code_guard's non-gateway/non-ask branch, plus danger_note + _log_blocked_exec_code. Covers NousResearch#49578 (open/write + shutil bypass) and PR NousResearch#65592 review gaps. - agent/conversation_loop.py: verify-on-stop nudge prepend (NousResearch#68586 priority inversion) + JSON-wrapped BLOCKED detection for execute_code denials.
…daptation) Adapt 5 local patches to upstream b52b725 (approval.py grew 4300->5600 lines): - tools/approval.py: AST execute_code dangerous-op scanner (_EXEC_CODE_DANGEROUS_CALLS / _open_mode_is_write / _execute_code_has_dangerous_ops), wired into check_execute_code_guard's non-gateway/non-ask branch, plus danger_note + _log_blocked_exec_code. Covers NousResearch#49578 (open/write + shutil bypass) and PR NousResearch#65592 review gaps. - agent/conversation_loop.py: verify-on-stop nudge prepend (NousResearch#68586 priority inversion) + JSON-wrapped BLOCKED detection for execute_code denials.
…daptation) Adapt 5 local patches to upstream b52b725 (approval.py grew 4300->5600 lines): - tools/approval.py: AST execute_code dangerous-op scanner (_EXEC_CODE_DANGEROUS_CALLS / _open_mode_is_write / _execute_code_has_dangerous_ops), wired into check_execute_code_guard's non-gateway/non-ask branch, plus danger_note + _log_blocked_exec_code. Covers NousResearch#49578 (open/write + shutil bypass) and PR NousResearch#65592 review gaps. - agent/conversation_loop.py: verify-on-stop nudge prepend (NousResearch#68586 priority inversion) + JSON-wrapped BLOCKED detection for execute_code denials.
What does this PR do? / 这个 PR 做了什么?
Three-layer fix for verify-on-stop hijacking the agent's response direction
and discarding the original answer (#62142). Currently when verify-on-stop
fires, the
[System: You edited code...]nudge is appended as a new usermessage AFTER the real user question — the model sees the nudge as the most
recent instruction and prioritizes it over the user. After verification
passes, the original substantive answer is replaced by a short "tests pass"
receipt.
三层修复 verify-on-stop 劫持回答方向并丢弃原始答案(#62142):
Layer 1 (commit 1): prepend, not append
→ Nudge prepended to most recent real user message instead of appended after
it. User's original question stays as the last (highest-priority) instruction.
Layer 2 (commit 2): nudge text
→
[Coding] After verification, restate your original answer in full — do not replace it with a verification receipt.appended to nudge.Layer 3 (commit 3): code-gate fallback
→ After all nudge checks pass (verification succeeded), if
_pending_verification_responseexists and currentfinal_responseis short(<300 chars, likely a receipt), merge saved answer back into final_response.
This is the system-level guarantee — it does not depend on model behavior.
Related Issue / 关联 Issue
Our fix addresses both the priority inversion (Layer 1) and the answer
loss (Layers 2+3). fix(tui): verification-stop can discard streamed final answers and cron reports #62142 was originally filed for Desktop but our testing
confirms CLI is also affected.
Type of Change / 变更类型
Changes Made / 具体改动
agent/conversation_loop.py:messages.append(nudge)with prepend to real user message (Layer 1)_pending_verification_responseback iffinal_responseis short (Layer 3)agent/verification_stop.py:build_verify_on_stop_nudge(): append restate instruction (Layer 2)How to Test / 如何测试
(not replaced by "tests pass").
pytest tests/agent/test_verification_stop.py— 50/50 pass.Screenshots / Logs / 截图 / 日志
Checklist / 检查清单
Code