Skip to content

Reasoning that finishes

An agent turn that thinks for 20,000 tokens and then repeats one paragraph forever costs a card slot and gives the agent nothing. ignis watches the reasoning as it is written and acts on three failures: a loop is cut back and nudged inside the same response, a turn that ends inside its own thinking is closed so its tool call reaches the client, and a thinking budget closes with a sentence that makes the model answer. None of this makes the model loop less. It keeps a loop from ending the turn.

The loop guard scores the reasoning as it streams. A window of 1,000 characters is normalized (digits are folded, so log lines with different numbers match) and compressed; a window that compresses far below the norm is repetition. A list, a log or a table is redundant by nature, so one low score is not enough: the guard acts only when 4 checks in a row score under the ratio. Then it cuts the reasoning back to the last clean window, appends a nudge in the model’s own voice, and the generation goes on in the same response. The client sees a tag where the loop was and rewound_tokens in usage; the reasoning it sends back is repaired to what the model saw, so the next turn renders token for token. The rewind resumes from the checkpoint the request published at its generation opener and prefills only the kept text plus the nudge, so its cost does not grow with the prompt. After two nudges the next loop closes the reasoning.

The second failure is quieter. The model can draw its end-of-turn token while the <think> block is still open, usually after writing the tool call inside the reasoning. The server reads tool calls only from the content channel, so the client would get an all-reasoning turn and the agent loop would stop. The leaf holds every successor token on the host before feeding it back, so a stop token drawn inside an open block is replaced by </think> there, and the model carries on into its answer.

The third is the budget. After model.thinking_budget reasoning tokens with the block open, or 2,048 tokens before max_tokens, the server forces a close: a sentence telling the model to answer from what it has, with no further tool call, then </think>. The wording was fixed after a measured failure: the first close let the model defer with another tool call, and under greedy sampling the same turn then repeated.

The mechanics, the modes and the tuning are in Reasoning and the loop guard.

Figure Measured on Source
About a third of first turns loop on a long-enumeration prompt at the model card’s sampling; the same with YaRN, speculation or the compressed KV off, and the same on the reference engine with the same weights. presence_penalty 1.5 gives 2/8 against 4/8 on the same seeds 27B NVFP4, the Playground’s defaults, 8 seeds per arm Reasoning loops are the model’s
25 of 25 harmful loops and 34 of 38 acute onsets caught recorded runs of the prompt the thresholds were tuned on (in-sample) A loop persists
6 of 7 harmful loops caught on 40 fresh seeds; the seventh is a numeric loop the raw gate misses, and 2 more harmful runs were long legitimate planning held-out run, guard in log mode, scored offline same
0 fires on 12,936 recorded agent reasoning parts (5,654 long enough to score); the first rule fired twice recorded sessions that also informed the thresholds same
A rewind restores a 79K-138K-token agent prompt in 0.08-0.10 s with little kept, 1.276 s with about 3,645 tokens kept; 9 of 16 runs escaped for good after the first nudge 27B, long agent prompts Reasoning loop guard
An end of turn inside open reasoning: 1 of 115 Flash-Next turns, 0 of 413 on the 27B one agent session database ADR 0048
The old close answered 2 of 8 sampled runs and always deferred under greedy; the new wording answered both captured turns and left no short deferral in 8 sampled runs two captured agent turns, 8 sampled reruns A close that answers
  • The loop is the model’s, not the engine’s. The guard mitigates; it does not remove looping.
  • A request with images is never rewound, and /v1/decide has no reasoning to guard.
  • The end-of-turn redirect applies only to a request that starts inside its reasoning with thinking on, and not under ignore_eos. There is no switch.
  • A guard that fires later than the first rule did (a median of about 500 characters) is the price of not touching loops that end by themselves.