Skip to content

At the step limit, one request with no tools, and the prose is the answer - #137

Merged
deepfates merged 1 commit into
mainfrom
claude/last-action
Sep 22, 2026
Merged

deepfates merged 1 commit into
mainfrom
claude/last-action

Conversation

@deepfates

Copy link
Copy Markdown
Owner

Imp.Predict.ReActV2 ended a turn that reached max_iters with forced_submit: one more request with tool_choice naming the submit tool. For a host whose signature has a single text output and whose model already ends turns by writing prose, that asks the model to call a tool it never chose.

on_max_iters says what the step limit does.

  • :forced_submit, the default, is today's behaviour, so existing users are unchanged.
  • :last_prose makes exactly one more request with no tools in it: :tools and :tool_choice are dropped from the provider config, and for the chat completions profile a body with no tool roster carries neither key. The completion is parsed as prose the way the :answered path is, and the run finishes with the single text output set to that prose and termination_reason: :last_prose. An empty completion finishes with an empty answer rather than an error. The option needs a signature with exactly one output of type :string; anything else is refused at construction.

:last_prose is a separate reason from :answered: :answered is the model choosing to stop calling tools, :last_prose is the loop taking the tools away.

last_prose_note, nil or a string, puts one line of host text in front of that request as a user message. It goes through the same history mechanism forced_submit_notice uses, so the record of the run carries it, but the two options stay separate and a host passes one or the other. Imp writes no sentence of its own.

The last request and its response are emitted as events like any step's, and the prose becomes a history turn the way an answered step's does.

Both options dump and load; a dump written before them loads as :forced_submit.

Tests: test/react_v2_last_prose_test.exs covers three requests at max_iters: 2 with no :tools key in the third, the note present and absent, an empty answer, the event record, the construction refusal for a two-output signature, the default still forcing a submit, and the dump/load round trip including an older dump. The existing forced-submit and prose tests are unchanged.

Gates: mix format, mix compile --warnings-as-errors, mix check (2865 tests, 0 failures), mix dialyzer.check (150 errors, 150 skipped, 0 unnecessary skips).

on_max_iters says what the step limit does. The default, :forced_submit, is
unchanged: one more request with tool_choice naming submit. :last_prose makes
one more request with no tools in the provider config, so the only thing the
model can do is speak, and that prose is the single text output, with
termination_reason: :last_prose. An empty completion is an empty answer, not an
error. The option needs a signature with exactly one output of type :string and
is refused at construction otherwise.

last_prose_note puts one line of host text in front of that request as a user
message, through the history the forced_submit_notice uses, so the record of
the run carries it. Imp writes no sentence of its own.

Both options dump and load; a dump written without them loads as
:forced_submit.
@deepfates
deepfates merged commit df6fa5b into main Sep 22, 2026
10 checks passed
@deepfates
deepfates deleted the claude/last-action branch September 22, 2026 17:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant