Repository navigation
ReActV2: finish: :tools, and history_replay: :actions - #351
Merged
Merged
Conversation
finish: :tools ends a turn only through a finish_on tool. Text without a tool call is a thought followed by one ordinary step and a line naming the ending tools (unsent_note: replaces it); a second thought, an empty step, the step limit or a failed step ends the turn :incomplete with nothing forced. A call may give its own finish:. history_replay: :actions shows earlier turns as inputs, calls with results, and outputs, without the text and reasoning beside the calls. Opt-in; the default :trajectory replays whole, as DSPy's ReActV2 does.
- A task input named finish is the input, not the per-call option. - :actions keeps the current turn whole on a resume without new inputs: it starts at the last step that carries inputs, so in-turn reasoning stays. - Under :tools, a failed request gets one more ordinary step, once per turn; a passed deadline ends the turn :deadline_exceeded. - :tools refuses last_request_note/last_request_tools and a blank unsent_note; a call's :tools without finish_on returns an error; loading validates the same; the unsent_note load error names unsent_note. - The written-tool agent line under :tools no longer says the tools produce the outputs. - Docs: per-call finish, :tools in how a turn ends, the retry, :actions' current-turn rule and output steps, reasoning on earlier turns (Kimi K3 through OpenRouter accepted it in a probe), :tools context recovery. - Tests discriminate: distinct results, reasoning fields, a mid-turn resume.
…st reads every passed turn as earlier
…pt; docs and dates match the retry
deepfates
enabled auto-merge (squash)
October 16, 2026 21:09
deepfates
added a commit
that referenced
this pull request
Oct 16, 2026
Version 0.15.0 with install lines ~> 0.15.0 and tag links for v0.15.0. The changelog's Unreleased section goes under 0.15.0, and the release notes describe #351. A minor release: finish: :tools, history_replay: :actions and unsent_note: are new options; programs that use none of them send and save exactly as in 0.14.1.
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
Some programs act only through tools. What they say goes out through a tool, and their text is read by nobody. Two problems follow from that.
finish: :auto, a step that writes text and calls no tool ends the turn, so these programs end on notes to themselves. The notes are stored as answers, and the model reads them back on every later turn as its own last word.Change
finish: :toolsOnly a
finish_ontool ends a turn. A program built with it needs at least onefinish_ontool.There is no
submit. The outputs come from thefinish_onfunctions.The guidance says "The turn ends only when you call
aorb. Text you write is not sent."A step with text and no call is a thought, as under
finish: :submit. One ordinary step follows: every tool,tool_choice: "auto", the thought kept as the model's own message, then one line.declineorsend; to keep working, call a tool."unsent_note:replaces it with the host's own words.Nothing is forced, and there is no last request. A step whose request fails gets one more ordinary step, once per turn. Each of these ends the turn
:incomplete, with itstermination_causeand no further request:The cause is
:deadline_exceededwhen the deadline has passed.Refused, each with a reason:
last_request_noteandlast_request_tools(never used under:tools), and a blankunsent_note. Loading a saved program applies the same checks.Forcing an ending tool would make the program act without having decided to, so the host decides what an unfinished turn means.
A call may give its own
finish:, like its ownmax_iters. It is an option only when the task has no input namedfinish; otherwise it stays the input, as on main. A call's:toolswithoutfinish_onreturns{:error, ...}. A:toolsprogram that needs one text answer for one call, such as a summary, calls withfinish: :auto, max_iters: 0and a note.A history recorded under
:autoreplays unchanged: an answer step is an answer, as in 0.14.1.history_replay: :actions(opt-in; the default:trajectoryis unchanged)last_request_note(a summary request, say, reads every passed turn as earlier). On a resume with neither, it starts at the last step that carries inputs. Reasoning within the turn (DeepSeekreasoning_content, Anthropic thinking, Gemini signatures) is therefore kept.next_thoughtand calls to the history and renders them all. That is why it is opt-in;decisions.mdrecords it.Saving. Both options, and
unsent_note, are saved. A program that uses neither saves exactly as before.Tests
test/react_v2_finish_tools_test.exsandtest/react_v2_history_replay_test.exs. Each fails on main, where the options do not exist. They cover:finish_ontool ends the turn: the request shape, the guidance line, the default note;:incompleteafter 2 requests;:incompletewith nothing forced;:toolswithoutfinish_onis refused;finishworks both ways;:autoanswer replays without a note;:actionsdrops earlier narration and thoughts and keeps the current turn and the record;:trajectoryreplays whole.:autoand:submitrequests are unchanged: the full suite passes, including the exact-replay test from #349.mix checkpasses.priv/public_api.jsonis regenerated for the new options.Review fixes (second commit)
finishstays an input.:actionskeeps the current turn whole on a resume mid-turn.:tools::deadline_exceeded;unsent_notenow gets an error namingunsent_note.:toolsagent line no longer says the tools produce the outputs.finishinput name;Mutating the projection or the turn boundary now fails them.
mix checkandmix quality.checkpass.