Repository navigation
ReActV2 finish: :submit: a thought-only step continues before submit is forced - #346
Merged
Merged
Conversation
…e submit is forced Under finish: :submit a step that writes a thought and calls no tool is followed by an ordinary step (every tool, tool_choice auto, the thought as the request's last user message). A second thought-only step in a row, or one that says nothing, is the :empty_tool_calls interruption and forces submit; max_iters and the deadline still do. Measured on 42 real final steps from 11 models: most submitted the thought's reply unforced, Meta's models included; some went on with the announced work; 5 of 42 wrote text again. The forced submit is not sent once the deadline has passed, as the last text request already was not. An :extracted turn's history now ends with the extracted outputs, recorded as a step that called no tool.
…n ends on the user side thought_as_request looks past a last step that called nothing and wrote nothing (the extracted outputs), so resuming after an :extracted turn no longer ends on the forced step's thought as an assistant message. No continued step, required retry or extraction request is sent once the deadline has passed. A whitespace-only thought is not continued. Docs: the deadline ends the turn incomplete rather than forcing submit; with written tool calls the request ends on the tool listing; the react.md example makes the lookup it announces.
deepfates
enabled auto-merge (squash)
October 16, 2026 05:09
deepfates
added a commit
that referenced
this pull request
Oct 16, 2026
…say it was not sent (#347) * ReActV2 finish: :submit: keep the thought as the model's message and say it was not sent On the step that follows a thought-only step, the thought now stays the model's own (assistant) message and one user message follows it: "Your message above was not sent. To reply, call `submit` with <the outputs>; to keep working, call a tool." It follows every thought-only step, in later requests and on replay, so each request extends the previous one. The forced submit keeps its shape: its last thought is shown as the request's last user message, with no note after it. Measured on 21 real final steps x 2 samples across 11 model families: the thought shown back as a user message had 5 samples write text again, and some models took it for an echo and submitted nothing; with the note, none wrote text again and replies were submitted intact. Also widens the margins of the deadline tests from #346, which could fail on a loaded machine. * ReActV2: no unsent note after an answered thought; drop it on the forced request only when the thought moves A thought the turn's extracted outputs directly follow was answered, so on replay it gets no "Your message above was not sent" line; the extracted outputs are still not rendered. The forced submit drops the line after its last thought only when thought_as_request moves that thought to the user side, so a forced request with new inputs keeps it. Docs that said Imp adds nothing to the last request now name the unsent line as the exception.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
User story
A host runs a program with
finish: :submit, so the person sees only what goes throughsubmit. Today, when the model writes a thought and calls no tool, Imp immediately sends the forcedsubmitwith every other tool removed. This goes wrong in three ways:last_request_failedwith no fallback, and so does a spend stop. Neither does the forcedsubmitcheck whether the deadline has passed, as the last text request does.Change
A thought-only step continues the loop (
finish: :submitonly).tool_choice: "auto".[[ ## next_thought ## ]](the rendering from ReActV2: finish: :submit opt-in; forced submit never ends on the model's turn #343).:empty_tool_callsinterruption, and the forcedsubmitfollows as before. So is a step that says nothing, or only whitespace: there is no thought to show, and the same request would be sent again.max_itersstill ends the turn with the forcedsubmit.submitunderfinish: :autokeep DSPy's ending, which forces at once. This scope is deliberate: the measurement below covers single-text final steps only.submit. If a host needs a different bound, it can become an option then.No request after the deadline. If the process's
Imp.Deadlinehas passed, none of these is sent:submit;"required"retry;The turn ends
:incompletewithtermination_cause: :deadline_exceeded, as the last text request already does.Extracted outputs are recorded in the history. On an
:extractedturn, the history used to end on the forced step's thought. It now ends with the extracted outputs, recorded as a step that called no tool, together with any inputs no step spent. The host's record then holds what the turn produced, and the episode is delimited by its outputs as asubmitturn's is.thought_as_requesttherefore looks past it.:extractedturn would end on the forced step's thought as an assistant message. Mistral and Gemini refuse such a request.What changes under
finish: :auto. The default is not entirely unchanged. For every signature withsubmit::extractedturn's history now ends with the extracted outputs.The continuing step applies only under
finish: :submit.Evidence
Probe 5 (2026-10-16): 42 real final steps from 11 models, sent through OpenRouter in the request shape above. On the ordinary step after a thought-only step:
submitwith the thought's reply, copied or paraphrased. Meta's models did so 6 of 6 with nothing forced, which matters because their provider cannot force a tool. Kimi, GPT and Gemini models did the same.The measurement is recorded in
decisions.md, with react.md sections 2 and 3 and the termination tables updated.Open
On replay, the extracted outputs are in the record but not shown to the model. On a continued conversation, the model reads the forced step's thought, shown as a user message, but not the answer the extractor produced. A
submitturn shows its answer through thesubmitcall.Rendering the extracted outputs would show the model an answer it did not write itself. It would also change the prompt for every replayed history that holds one. I chose to look past the step instead, and the trade-off is left here for review.
Tests
test/react_v2_thought_continues_test.exs, plus one updated test intest/react_v2_finish_submit_test.exs. Each fails on main::submit; the second request isautowith every tool and ends on the thought);submit(:forced_submit,:empty_tool_calls);submit: every request isauto, and the tool call resets the count;submit(:max_iters);submit(:incomplete,:deadline_exceeded, one request);"required"retry or the extractor sends no further request;:extractedturn does not end on the model's turn.mix checkandmix quality.checkpass.