fix(agent): nudge past progress-placeholder responses after tool call… - #57610
fix(agent): nudge past progress-placeholder responses after tool call…#57610ithelpm wants to merge 3 commits into
Conversation
teknium1
left a comment
There was a problem hiding this comment.
Thanks for targeting the post-tool placeholder gap from #42503; current main still takes non-empty no-tool replies through the normal final-response path (agent/conversation_loop.py:4941-5272), and the existing intent-ack tests explicitly exclude histories where a tool already ran (tests/agent/test_intent_ack_continuation.py:166-174).
Problems
agent/conversation_loop.py:4995-5007adds synthetic scaffold rows, but the cleanup at:5023-5033removes only a trailing suffix. If the nudge produces another tool call, those rows become interior.agent/turn_finalizer.py:175saves trajectories before session persistence, andrun_agent.py:2034converts the unfiltered messages, sosave_trajectories=Truerecords the supposedly ephemeral rows.
Suggested changes
- Filter/remove this scaffold from trajectory output as well as session persistence.
- Add the tool-call-after-nudge regression; the current persistence test patches
_persist_session, so it does not cover the buried-scaffold case.
Automated hermes-sweeper review.
| assistant_message, "incomplete" | ||
| ) | ||
| interim_msg["_post_tool_placeholder_synthetic"] = True | ||
| messages.append(interim_msg) |
There was a problem hiding this comment.
Blocking: if this nudge leads to another tool call, this flagged pair becomes interior rather than trailing, so the cleanup at lines 5023-5033 cannot remove it. The finalizer saves trajectories before session persistence filtering, so save_trajectories=True records these supposedly ephemeral rows. Filter/remove them from trajectory output and add a tool-call-after-nudge regression.
NousResearch#42503) After executing tool calls, some models (observed cross-provider, most often local/smaller models) close the turn with a short progress note ("Working on it...", "I'll now update the file…") instead of continuing to the next step or delivering the result. The conversation loop treats any non-empty no-tool-call response as the final answer, so the task silently ends unfinished — no error, no hint (NousResearch#42503). The existing intermediate-ack continuation deliberately skips turns that already ran tools, and the post-tool EMPTY-response nudge only covers blank content, so this failure mode fell between the two recoveries. Fix, mirroring the empty-response nudge: - detect progress placeholders after a tool round (short, non-question, explicit progress phrase OR future commitment with an open ending) - nudge the model to continue, capped at 2 per tool round - if the nudges are exhausted, keep the note but mark the turn post_tool_placeholder_response so the turn-completion explainer appends a visible "Incomplete turn" footer instead of ending silently - flag the synthetic nudge pair as ephemeral scaffolding so it is popped before the real final response and never persisted Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…trajectories Review follow-up (NousResearch#57610): the terminal scaffold pop before the final response only strips a trailing suffix. When the placeholder nudge succeeds via ANOTHER tool call, the synthetic assistant/user pair gets buried under the new tool turns, survives in the live context (replayed to the model on every later call), and — because trajectory conversion ran on the unfiltered message list — save_trajectories=True recorded the fake turns as if the model had really said them. Two-part fix: - conversation_loop: pop the _post_tool_placeholder_synthetic pair in the tool-call branch, before the new assistant tool-call turn is appended — the nudge has served its purpose, so the rows never become interior in the first place. - convert_to_trajectory_format: sweep ALL ephemeral scaffolding flags at the top, mirroring the per-message filter the session-store flush already applies. This makes the "scaffolding never persists" invariant structural for trajectories too (same philosophy as NousResearch#57491's terminal sweep) instead of depending on every loop path cleaning up positionally. Adds the tool-call-after-nudge regression the review asked for: one end-to-end loop test asserting no buried synthetic rows remain, and one direct conversion test asserting an interior scaffold pair never reaches trajectory output. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
07d2186 to
4d17ac5
Compare
|
Thanks for the catch - the trailing-suffix pop indeed missed the case where the nudge succeeds via another tool call, leaving the synthetic pair buried under the new tool turns and leaking into Addressed in the follow-up commit (branch also rebased onto current main):
|
Self-review follow-up to the buried-scaffold fix: the tool-call branch cleaned prefill rows and placeholder-nudge rows with two sequential single-flag loops. When the recoveries interleave — tool round → thinking-only response (prefill row) → progress placeholder (nudge pair) → tool calls — the stack is [prefill, placeholder, nudge]: the prefill loop sees the nudge on top and no-ops, the placeholder loop then pops the pair and only NOW exposes the prefill row, which its loop can no longer reach. The stranded row replays as a fake assistant turn (and a consecutive-assistant pair) in the live context on every later call. Reproduced empirically before the fix. Merge the two pops into one loop over both flags, mirroring the combined terminal pop on the final-response path; _had_prefill is set only when a prefill row is actually popped, so the retry-counter reset semantics are unchanged. Adds the interleaved-recovery regression. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Self-review addendum: the tool-call-path cleanup had an interleaving gap. With tool round -> thinking-only response (prefill row) -> progress placeholder (nudge pair) -> tool calls, the stack is Fixed in 2d541ee by merging the pops into one loop over both flags, mirroring the combined terminal pop on the final-response path ( |
Draft Comment for PR #57610Status: DRAFT — needs user review before postingThanks for this PR — it addresses a real gap (#42503) that we've also been working on locally. We have a complementary implementation that covers an adjacent failure mode not addressed by the progress-placeholder detection in this PR: Signature A — lost tool-call preamble. When the model emits a short text ending with CJK intent marker corpus. Our implementation includes ~30 CJK intent markers (继续/接下来/下一步/然后/开始/...) with done/wait/question exclusion lists that were production-hardened over several weeks of testing with K3 and GLM models. These could extend #57610's English-only phrase detection. What we'd like to contribute:
What we won't do:
Would this sequencing work for you? Happy to share the code or more details if useful. |
…ool calls When an LLM produces finish_reason=stop with text indicating intent to continue (a colon-preamble that lost its tool_calls, or a narrated continuation) right after a tool round, the conversation silently ends without executing the intended tool call (NousResearch#42503). Extract the inlined detection logic into a dedicated agent/false_stop.py module following the verification_stop.py / kanban_stop.py pattern: - build_false_stop_nudge(): pure function returning nudge text or None - false_stop_detection_enabled(): config + env gate, mirrors verify_on_stop_enabled() with surface-aware "auto" default - Module-level marker constants (24 CJK + 13 English intent markers, done/wait/question exclusion guards) — previously rebuilt per iteration Two detection signatures: - Signature A: short colon-preamble (<120 chars) after a tool round - Signature B: narrated continuation (≤200 chars, CJK ending, intent markers, no done/wait/question markers) after a tool round Both gated by _was_in_tool_round (last 8 messages). Bounded to 2 nudges per turn, resets on tool round and genuine completion. Synthetic nudge flagged _false_stop_synthetic in _EPHEMERAL_SCAFFOLDING_FLAGS for transcript hygiene. Complementary to intent_ack_continuation (turn-start, not post-tool) and NousResearch#57610 (progress-placeholder text). Config: agent.false_stop_detection: "auto" (on for CLI/TUI/desktop/ codex/local, off for messaging platforms). Env: HERMES_FALSE_STOP_DETECTION. Tests: 39 tests in 5 categories (gating, Signature A, Signature B, budget/reset, transcript hygiene). No regressions on verification_stop, verification_stop_caching, or verification_continuation_budget.
…s (#42503)
After executing tool calls, some models (observed cross-provider, most often local/smaller models) close the turn with a short progress note ("Working on it...", "I'll now update the file…") instead of continuing to the next step or delivering the result. The conversation loop treats any non-empty no-tool-call response as the final answer, so the task silently ends unfinished — no error, no hint (#42503).
The existing intermediate-ack continuation deliberately skips turns that already ran tools, and the post-tool EMPTY-response nudge only covers blank content, so this failure mode fell between the two recoveries.
Fix, mirroring the empty-response nudge:
What does this PR do?
Related Issue
Fixes #
Type of Change
Changes Made
How to Test
Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests passDocumentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/AFor New Skills
hermes --toolsets skills -q "Use the X skill to do Y"Screenshots / Logs